@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 +1 -1
- package/dist/plugin.mjs +72 -58
- package/package.json +3 -3
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):
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
const
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
60
|
+
"void": "0.8.0"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "vp pack",
|