@void/react 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
@@ -11,6 +11,6 @@ type VoidReactOptions = Readonly<{
11
11
  viewTransitions?: boolean;
12
12
  prefetch?: PrefetchConfig;
13
13
  }>;
14
- declare function voidReact(options?: VoidReactOptions): ReadonlyArray<Plugin>;
14
+ declare function voidReact(options?: VoidReactOptions): Array<Plugin>;
15
15
  //#endregion
16
16
  export { VoidReactOptions, voidReact };
package/dist/plugin.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import react from "@vitejs/plugin-react";
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, extractCssImports, 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 "/";
@@ -141,10 +108,27 @@ const COMPONENT_EXTENSIONS = [
141
108
  ".ts",
142
109
  ".js"
143
110
  ];
144
- function islandsPlugin(pagesDir) {
111
+ const ISLAND_IMPORT_RE = /import\s+([$_A-Za-z][$_\w]*)\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*,?\s*\}/g;
112
+ function findIslandImports(code) {
113
+ const imports = [];
114
+ let match;
115
+ ISLAND_IMPORT_RE.lastIndex = 0;
116
+ while ((match = ISLAND_IMPORT_RE.exec(code)) !== null) {
117
+ const specifierRaw = match[2];
118
+ imports.push({
119
+ statement: match[0],
120
+ importName: match[1],
121
+ specifierRaw,
122
+ specifier: specifierRaw.slice(1, -1),
123
+ hydrate: match[3].slice(1, -1)
124
+ });
125
+ }
126
+ return imports;
127
+ }
128
+ function islandsPlugin(pagesDir, options) {
145
129
  let pageScan = null;
146
130
  let isDev = false;
147
- const root = dirname(pagesDir);
131
+ const root = options?.root ?? dirname(pagesDir);
148
132
  const islandManifest = /* @__PURE__ */ new Map();
149
133
  const islandCssFiles = /* @__PURE__ */ new Set();
150
134
  let viteRoot = "";
@@ -167,8 +151,7 @@ function islandsPlugin(pagesDir) {
167
151
  },
168
152
  async buildStart() {
169
153
  if (existsSync(pagesDir)) {
170
- pageScan = await scanPages(root);
171
- const importAttrRe = /import\s+\w+\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*\}/g;
154
+ pageScan = await scanPages(root, { pagesDir });
172
155
  const filesToScan = [
173
156
  ...pageScan.pages.map((page) => ({
174
157
  filePath: page.componentPath,
@@ -188,11 +171,9 @@ function islandsPlugin(pagesDir) {
188
171
  const filePath = resolve(pagesDir, entry.filePath);
189
172
  const code = readFileSync(filePath, "utf-8");
190
173
  const importerDir = dirname(filePath);
191
- importAttrRe.lastIndex = 0;
192
- let importMatch;
193
- while ((importMatch = importAttrRe.exec(code)) !== null) {
194
- const spec = importMatch[1].slice(1, -1);
195
- const strategy = importMatch[2].slice(1, -1);
174
+ for (const islandImport of findIslandImports(code)) {
175
+ const spec = islandImport.specifier;
176
+ const strategy = islandImport.hydrate;
196
177
  const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
197
178
  if (absPath) {
198
179
  const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
@@ -250,18 +231,15 @@ function islandsPlugin(pagesDir) {
250
231
  filter: { id: /\.island\.(tsx|jsx)$/ },
251
232
  handler(code, id) {
252
233
  if (/import\s+\{[^}]*\buseForm\b/.test(code)) this.warn(`islands: useForm cannot be imported in island page '${id}'. Use useIslandForm instead.`);
253
- const importAttrRe = /import\s+(\w+)\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*\}/g;
254
234
  const importerDir = dirname(id);
255
235
  let transformed = code;
256
236
  const wrappers = [];
257
237
  let idx = 0;
258
- let match;
259
- while ((match = importAttrRe.exec(code)) !== null) {
260
- const name = match[1];
261
- const specRaw = match[2];
262
- const strategyRaw = match[3];
263
- const spec = specRaw.slice(1, -1);
264
- const strategy = strategyRaw.slice(1, -1);
238
+ for (const islandImport of findIslandImports(code)) {
239
+ const name = islandImport.importName;
240
+ const specRaw = islandImport.specifierRaw;
241
+ const strategy = islandImport.hydrate;
242
+ const spec = islandImport.specifier;
265
243
  const rawName = `_raw_island_${idx++}`;
266
244
  const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
267
245
  if (absPath) {
@@ -274,7 +252,7 @@ function islandsPlugin(pagesDir) {
274
252
  }
275
253
  const islandId = absPath ? normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "") : spec;
276
254
  const replacement = `import ${rawName} from ${specRaw}`;
277
- transformed = transformed.replace(match[0], replacement);
255
+ transformed = transformed.replace(islandImport.statement, replacement);
278
256
  wrappers.push(`var ${name} = function ${name}(props) { return _React_island.createElement("div", { "data-island": ${JSON.stringify(islandId)}, "data-props": JSON.stringify(props), "data-hydrate": ${JSON.stringify(strategy)} }, _React_island.createElement(${rawName}, props)); };`);
279
257
  }
280
258
  if (wrappers.length > 0) {
@@ -411,6 +389,7 @@ const REACT_OPTIMIZE_DEPS = [
411
389
  "react-dom/client",
412
390
  "react-dom/server"
413
391
  ];
392
+ const WORKER_OPTIMIZE_DEPS = [...REACT_OPTIMIZE_DEPS, "void/live/client"];
414
393
  /** Check if the pages directory has any non-island page files (.tsx/.jsx without .island.). */
415
394
  function hasNonIslandPages(dir) {
416
395
  try {
@@ -430,11 +409,29 @@ function pagesPlugin(pagesDir, options) {
430
409
  let pageScan = null;
431
410
  let isDev = false;
432
411
  let viteConfig;
433
- const root = dirname(pagesDir);
412
+ const root = options?.root ?? dirname(pagesDir);
413
+ const normalizedPagesDir = normalizePath(pagesDir);
414
+ const pagesOptimizeEntries = `${relative(root, pagesDir)}/**/*.{js,jsx,ts,tsx}`;
415
+ let pendingRescan = null;
416
+ const rescanPages = async () => {
417
+ pageScan = existsSync(pagesDir) ? await scanPages(root, { pagesDir }) : null;
418
+ };
419
+ const runRescan = () => {
420
+ pendingRescan ??= rescanPages().finally(() => {
421
+ pendingRescan = null;
422
+ });
423
+ return pendingRescan;
424
+ };
425
+ const invalidatePageModules = (server) => {
426
+ for (const env of Object.values(server.environments)) for (const id of ["\0" + RENDERER_ID, "\0" + CLIENT_ENTRY_ID]) {
427
+ const mod = env.moduleGraph.getModuleById(id);
428
+ if (mod) env.moduleGraph.invalidateModule(mod);
429
+ }
430
+ };
434
431
  return {
435
432
  name: "void-react:pages",
436
433
  async buildStart() {
437
- if (existsSync(pagesDir)) pageScan = await scanPages(root);
434
+ await runRescan();
438
435
  },
439
436
  resolveId: {
440
437
  filter: { id: /^virtual:void-pages-(renderer|client)$/ },
@@ -444,7 +441,9 @@ function pagesPlugin(pagesDir, options) {
444
441
  },
445
442
  load: {
446
443
  filter: { id: /^\0virtual:void-pages-(renderer|client)$/ },
447
- handler(id) {
444
+ async handler(id) {
445
+ if (isDev && pendingRescan) await pendingRescan;
446
+ if (!pageScan && existsSync(pagesDir)) await runRescan();
448
447
  if (id === "\0" + RENDERER_ID) {
449
448
  if (!pageScan) return "export function renderPage() { throw new Error('No pages found'); }";
450
449
  return generateRendererModule(pageScan, pagesDir, isDev);
@@ -455,6 +454,18 @@ function pagesPlugin(pagesDir, options) {
455
454
  }
456
455
  }
457
456
  },
457
+ configureServer(server) {
458
+ const handlePagesChange = async (path, event) => {
459
+ const normalizedPath = normalizePath(path);
460
+ if (normalizedPath !== normalizedPagesDir && !normalizedPath.startsWith(normalizedPagesDir + "/")) return;
461
+ if (event === "change" && !normalizedPath.endsWith(".md")) return;
462
+ await runRescan();
463
+ invalidatePageModules(server);
464
+ };
465
+ server.watcher.on("add", (path) => handlePagesChange(path, "add"));
466
+ server.watcher.on("change", (path) => handlePagesChange(path, "change"));
467
+ server.watcher.on("unlink", (path) => handlePagesChange(path, "unlink"));
468
+ },
458
469
  configResolved(config) {
459
470
  viteConfig = config;
460
471
  },
@@ -471,7 +482,8 @@ function pagesPlugin(pagesDir, options) {
471
482
  ssr: { optimizeDeps: { include: REACT_OPTIMIZE_DEPS } },
472
483
  environments: { [WORKER_ENVIRONMENT_NAME]: { optimizeDeps: {
473
484
  exclude: ["@void/react"],
474
- include: REACT_OPTIMIZE_DEPS
485
+ entries: [pagesOptimizeEntries],
486
+ include: WORKER_OPTIMIZE_DEPS
475
487
  } } }
476
488
  };
477
489
  }
@@ -537,14 +549,16 @@ function generateClientEntry(scan, pagesDir, viewTransitions, prefetch, staticPa
537
549
  //#endregion
538
550
  //#region src/plugin.ts
539
551
  function voidReact(options) {
540
- const pagesDir = join(process.cwd(), "pages");
552
+ const root = process.cwd();
553
+ const pagesDir = resolvePagesDir(root);
541
554
  return [
542
555
  ...react(options?.react),
543
556
  pagesPlugin(pagesDir, {
557
+ root,
544
558
  viewTransitions: options?.viewTransitions,
545
559
  prefetch: options?.prefetch
546
560
  }),
547
- ...islandsPlugin(pagesDir)
561
+ ...islandsPlugin(pagesDir, { root })
548
562
  ];
549
563
  }
550
564
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@void/react",
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",
@@ -51,13 +51,13 @@
51
51
  "react-dom": "^19.2.6",
52
52
  "vite": "npm:@voidzero-dev/vite-plus-core@0.1.20",
53
53
  "vite-plus": "^0.1.20",
54
- "void": "0.7.12"
54
+ "void": "0.8.0"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "react": "^19.0.0",
58
58
  "react-dom": "^19.0.0",
59
59
  "vite": "^8.0.0",
60
- "void": "0.7.12"
60
+ "void": "0.8.0"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "vp pack",