@void/vue 0.7.12 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plugin.d.mts CHANGED
@@ -6,6 +6,6 @@ type VoidVueOptions = Readonly<{
6
6
  vue?: Parameters<typeof vue>[0];
7
7
  viewTransitions?: boolean;
8
8
  }>;
9
- declare function voidVue(options?: VoidVueOptions): ReadonlyArray<Plugin>;
9
+ declare function voidVue(options?: VoidVueOptions): Array<Plugin>;
10
10
  //#endregion
11
11
  export { VoidVueOptions, voidVue };
package/dist/plugin.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import vue from "@vitejs/plugin-vue";
2
+ import { generateComponentManifest, resolveEffectiveLayoutChain, resolvePagesDir, resolveStaticPageDataBuildOptions, scanPages } from "void/pages";
2
3
  import { existsSync, readFileSync, readdirSync } from "node:fs";
3
4
  import { normalizePath } from "vite";
4
- import { generateComponentManifest, resolveEffectiveLayoutChain, resolveStaticPageDataBuildOptions, scanPages } from "void/pages";
5
5
  import { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, needsIslandClientBundle, resolveSpecifier, rewriteCssImportForVirtualModule, watchPagesForRescan } from "void/pages-islands-plugin";
6
6
  //#region ../../node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.mjs
7
7
  const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
@@ -9,42 +9,9 @@ function normalizeWindowsPath(input = "") {
9
9
  if (!input) return input;
10
10
  return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
11
11
  }
12
- const _UNC_REGEX = /^[/\\]{2}/;
13
12
  const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
14
13
  const _DRIVE_LETTER_RE = /^[A-Za-z]:$/;
15
14
  const _ROOT_FOLDER_RE = /^\/([A-Za-z]:)?$/;
16
- const normalize = function(path) {
17
- if (path.length === 0) return ".";
18
- path = normalizeWindowsPath(path);
19
- const isUNCPath = path.match(_UNC_REGEX);
20
- const isPathAbsolute = isAbsolute(path);
21
- const trailingSeparator = path[path.length - 1] === "/";
22
- path = normalizeString(path, !isPathAbsolute);
23
- if (path.length === 0) {
24
- if (isPathAbsolute) return "/";
25
- return trailingSeparator ? "./" : ".";
26
- }
27
- if (trailingSeparator) path += "/";
28
- if (_DRIVE_LETTER_RE.test(path)) path += "/";
29
- if (isUNCPath) {
30
- if (!isPathAbsolute) return `//./${path}`;
31
- return `//${path}`;
32
- }
33
- return isPathAbsolute && !isAbsolute(path) ? `/${path}` : path;
34
- };
35
- const join = function(...segments) {
36
- let path = "";
37
- for (const seg of segments) {
38
- if (!seg) continue;
39
- if (path.length > 0) {
40
- const pathTrailing = path[path.length - 1] === "/";
41
- const segLeading = seg[0] === "/";
42
- if (pathTrailing && segLeading) path += seg.slice(1);
43
- else path += pathTrailing || segLeading ? seg : `/${seg}`;
44
- } else path += seg;
45
- }
46
- return normalize(path);
47
- };
48
15
  function cwd() {
49
16
  if (typeof process !== "undefined" && typeof process.cwd === "function") return process.cwd().replace(/\\/g, "/");
50
17
  return "/";
@@ -142,6 +109,23 @@ const COMPONENT_EXTENSIONS = [
142
109
  ".ts",
143
110
  ".js"
144
111
  ];
112
+ const ISLAND_IMPORT_RE = /import\s+([$_A-Za-z][$_\w]*)\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*,?\s*\}/g;
113
+ function findIslandImports(code) {
114
+ const imports = [];
115
+ let match;
116
+ ISLAND_IMPORT_RE.lastIndex = 0;
117
+ while ((match = ISLAND_IMPORT_RE.exec(code)) !== null) {
118
+ const specifierRaw = match[2];
119
+ imports.push({
120
+ statement: match[0],
121
+ importName: match[1],
122
+ specifierRaw,
123
+ specifier: specifierRaw.slice(1, -1),
124
+ hydrate: match[3].slice(1, -1)
125
+ });
126
+ }
127
+ return imports;
128
+ }
145
129
  function extractCss(filePath) {
146
130
  const code = readFileSync(filePath, "utf-8");
147
131
  const parts = [];
@@ -157,9 +141,9 @@ function extractCss(filePath) {
157
141
  }
158
142
  return parts.join("\n");
159
143
  }
160
- function islandsPlugin(pagesDir) {
144
+ function islandsPlugin(pagesDir, options) {
161
145
  let pageScan = null;
162
- const root = dirname(pagesDir);
146
+ const root = options?.root ?? dirname(pagesDir);
163
147
  const islandManifest = /* @__PURE__ */ new Map();
164
148
  const islandCssFiles = /* @__PURE__ */ new Set();
165
149
  let isDev = false;
@@ -184,8 +168,7 @@ function islandsPlugin(pagesDir) {
184
168
  },
185
169
  async buildStart() {
186
170
  if (existsSync(pagesDir)) {
187
- pageScan = await scanPages(root);
188
- const importAttrRe = /import\s+\w+\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*\}/g;
171
+ pageScan = await scanPages(root, { pagesDir });
189
172
  const filesToScan = [
190
173
  ...pageScan.pages.map((page) => ({
191
174
  filePath: page.componentPath,
@@ -205,11 +188,9 @@ function islandsPlugin(pagesDir) {
205
188
  const filePath = resolve(pagesDir, entry.filePath);
206
189
  const code = readFileSync(filePath, "utf-8");
207
190
  const importerDir = dirname(filePath);
208
- importAttrRe.lastIndex = 0;
209
- let importMatch;
210
- while ((importMatch = importAttrRe.exec(code)) !== null) {
211
- const spec = importMatch[1].slice(1, -1);
212
- const strategy = importMatch[2].slice(1, -1);
191
+ for (const islandImport of findIslandImports(code)) {
192
+ const spec = islandImport.specifier;
193
+ const strategy = islandImport.hydrate;
213
194
  const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
214
195
  if (absPath) {
215
196
  const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
@@ -267,18 +248,15 @@ function islandsPlugin(pagesDir) {
267
248
  filter: { id: /\.island\.vue$/ },
268
249
  handler(code, id) {
269
250
  if (/import\s+\{[^}]*\buseForm\b/.test(code)) this.warn(`islands: useForm cannot be imported in island page '${id}'. Use useIslandForm instead.`);
270
- const importAttrRe = /import\s+(\w+)\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*\}/g;
271
251
  const importerDir = dirname(id);
272
252
  let transformed = code;
273
253
  const wrappers = [];
274
254
  let idx = 0;
275
- let match;
276
- while ((match = importAttrRe.exec(code)) !== null) {
277
- const name = match[1];
278
- const specRaw = match[2];
279
- const strategyRaw = match[3];
280
- const spec = specRaw.slice(1, -1);
281
- const strategy = strategyRaw.slice(1, -1);
255
+ for (const islandImport of findIslandImports(code)) {
256
+ const name = islandImport.importName;
257
+ const specRaw = islandImport.specifierRaw;
258
+ const strategy = islandImport.hydrate;
259
+ const spec = islandImport.specifier;
282
260
  const rawName = `_raw_island_${idx++}`;
283
261
  const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
284
262
  if (absPath) {
@@ -291,7 +269,7 @@ function islandsPlugin(pagesDir) {
291
269
  }
292
270
  const islandId = absPath ? normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "") : spec;
293
271
  const replacement = `import ${rawName} from ${specRaw}`;
294
- transformed = transformed.replace(match[0], replacement);
272
+ transformed = transformed.replace(islandImport.statement, replacement);
295
273
  wrappers.push(`const ${name} = { inheritAttrs: false, render() { const props = this.$attrs; return _h_island("div", { "data-island": ${JSON.stringify(islandId)}, "data-props": JSON.stringify(props), "data-hydrate": ${JSON.stringify(strategy)} }, _h_island(${rawName}, props)); } };`);
296
274
  }
297
275
  if (wrappers.length > 0) {
@@ -472,8 +450,26 @@ function hasNonIslandPages(dir) {
472
450
  }
473
451
  function pagesPlugin(pagesDir, options) {
474
452
  let pageScan = null;
453
+ let isDev = false;
475
454
  let viteConfig;
476
- const root = dirname(pagesDir);
455
+ const root = options?.root ?? dirname(pagesDir);
456
+ const normalizedPagesDir = normalizePath(pagesDir);
457
+ let pendingRescan = null;
458
+ const rescanPages = async () => {
459
+ pageScan = existsSync(pagesDir) ? await scanPages(root, { pagesDir }) : null;
460
+ };
461
+ const runRescan = () => {
462
+ pendingRescan ??= rescanPages().finally(() => {
463
+ pendingRescan = null;
464
+ });
465
+ return pendingRescan;
466
+ };
467
+ const invalidatePageModules = (server) => {
468
+ for (const env of Object.values(server.environments)) for (const id of ["\0" + RENDERER_ID, "\0" + CLIENT_ENTRY_ID]) {
469
+ const mod = env.moduleGraph.getModuleById(id);
470
+ if (mod) env.moduleGraph.invalidateModule(mod);
471
+ }
472
+ };
477
473
  return [{
478
474
  name: "void-vue:pages-transform",
479
475
  enforce: "pre",
@@ -503,7 +499,7 @@ function pagesPlugin(pagesDir, options) {
503
499
  }, {
504
500
  name: "void-vue:pages",
505
501
  async buildStart() {
506
- if (existsSync(pagesDir)) pageScan = await scanPages(root);
502
+ await runRescan();
507
503
  },
508
504
  resolveId: {
509
505
  filter: { id: /^virtual:void-pages-(renderer|client)$/ },
@@ -513,7 +509,9 @@ function pagesPlugin(pagesDir, options) {
513
509
  },
514
510
  load: {
515
511
  filter: { id: /^\0virtual:void-pages-(renderer|client)$/ },
516
- handler(id) {
512
+ async handler(id) {
513
+ if (isDev && pendingRescan) await pendingRescan;
514
+ if (!pageScan && existsSync(pagesDir)) await runRescan();
517
515
  if (id === "\0" + RENDERER_ID) {
518
516
  if (!pageScan) return "export function renderPage() { throw new Error('No pages found'); }";
519
517
  return generateRendererModule(pageScan, pagesDir);
@@ -524,12 +522,27 @@ function pagesPlugin(pagesDir, options) {
524
522
  }
525
523
  }
526
524
  },
525
+ configureServer(server) {
526
+ const handlePagesChange = async (path, event) => {
527
+ const normalizedPath = normalizePath(path);
528
+ if (normalizedPath !== normalizedPagesDir && !normalizedPath.startsWith(normalizedPagesDir + "/")) return;
529
+ if (event === "change" && !normalizedPath.endsWith(".md")) return;
530
+ await runRescan();
531
+ invalidatePageModules(server);
532
+ };
533
+ server.watcher.on("add", (path) => handlePagesChange(path, "add"));
534
+ server.watcher.on("change", (path) => handlePagesChange(path, "change"));
535
+ server.watcher.on("unlink", (path) => handlePagesChange(path, "unlink"));
536
+ },
527
537
  configResolved(config) {
528
538
  viteConfig = config;
529
539
  },
530
540
  config(_, env) {
531
541
  if (!existsSync(pagesDir)) return;
532
- if (env.command === "serve") return { optimizeDeps: { include: ["vue"] } };
542
+ if (env.command === "serve") {
543
+ isDev = true;
544
+ return { optimizeDeps: { include: ["vue"] } };
545
+ }
533
546
  if (!hasNonIslandPages(pagesDir)) return;
534
547
  return { environments: { client: { build: {
535
548
  manifest: true,
@@ -702,11 +715,15 @@ start();
702
715
  //#endregion
703
716
  //#region src/plugin.ts
704
717
  function voidVue(options) {
705
- const pagesDir = join(process.cwd(), "pages");
718
+ const root = process.cwd();
719
+ const pagesDir = resolvePagesDir(root);
706
720
  return [
707
721
  vue(options?.vue),
708
- ...pagesPlugin(pagesDir, { viewTransitions: options?.viewTransitions }),
709
- ...islandsPlugin(pagesDir)
722
+ ...pagesPlugin(pagesDir, {
723
+ root,
724
+ viewTransitions: options?.viewTransitions
725
+ }),
726
+ ...islandsPlugin(pagesDir, { root })
710
727
  ];
711
728
  }
712
729
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@void/vue",
3
- "version": "0.7.12",
3
+ "version": "0.8.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/voidzero-dev/void.git",
@@ -44,12 +44,12 @@
44
44
  "vite": "npm:@voidzero-dev/vite-plus-core@0.1.20",
45
45
  "vite-plus": "^0.1.20",
46
46
  "vue": "^3.5.34",
47
- "void": "0.7.12"
47
+ "void": "0.8.0"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "vite": "^8.0.0",
51
51
  "vue": "^3.5.0",
52
- "void": "0.7.12"
52
+ "void": "0.8.0"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "vp pack",