@void/react 0.21.1 → 0.21.3

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.
@@ -1,5 +1,6 @@
1
1
  //#region src/runtime/deferred-shell.ts
2
2
  const DEFERRED_SHELL_END_ID = "__VOID_DEFERRED_SHELL_END__";
3
3
  const DEFERRED_SHELL_END_MARKER = `<template id="${DEFERRED_SHELL_END_ID}"></template>`;
4
+ const DEFERRED_SHELL_CANCEL_REASON = "Void deferred shell complete";
4
5
  //#endregion
5
- export { DEFERRED_SHELL_END_MARKER as n, DEFERRED_SHELL_END_ID as t };
6
+ export { DEFERRED_SHELL_END_ID as n, DEFERRED_SHELL_END_MARKER as r, DEFERRED_SHELL_CANCEL_REASON as t };
package/dist/plugin.mjs CHANGED
@@ -3,7 +3,7 @@ import { generateComponentManifest, resolveEffectiveLayoutChain, resolvePagesDir
3
3
  import { existsSync, readFileSync, readdirSync } from "node:fs";
4
4
  import nodePath from "node:path";
5
5
  import { normalizePath } from "vite";
6
- import { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, extractCssImports, needsIslandClientBundle, resolveSpecifier, rewriteCssImportForVirtualModule, watchPagesForRescan } from "void/pages-islands-plugin";
6
+ import { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, editIslandSource, extractCssImports, findIslandImports, findStaticCssImports, needsIslandClientBundle, resolveSpecifier, rewriteCssImportForVirtualModule, watchPagesForRescan } from "void/pages-islands-plugin";
7
7
  //#region ../../node_modules/.pnpm/pathslash@0.1.0/node_modules/pathslash/dist/index.mjs
8
8
  const BACKSLASH_RE = /\\/g;
9
9
  const isWindows = process.platform === "win32";
@@ -50,23 +50,6 @@ const COMPONENT_EXTENSIONS = [
50
50
  ".ts",
51
51
  ".js"
52
52
  ];
53
- const ISLAND_IMPORT_RE = /import\s+([$_A-Za-z][$_\w]*)\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*,?\s*\}/g;
54
- function findIslandImports(code) {
55
- const imports = [];
56
- let match;
57
- ISLAND_IMPORT_RE.lastIndex = 0;
58
- while ((match = ISLAND_IMPORT_RE.exec(code)) !== null) {
59
- const specifierRaw = match[2];
60
- imports.push({
61
- statement: match[0],
62
- importName: match[1],
63
- specifierRaw,
64
- specifier: specifierRaw.slice(1, -1),
65
- hydrate: match[3].slice(1, -1)
66
- });
67
- }
68
- return imports;
69
- }
70
53
  function islandsPlugin(pagesDir, options) {
71
54
  let pageScan = null;
72
55
  let isDev = false;
@@ -113,7 +96,7 @@ function islandsPlugin(pagesDir, options) {
113
96
  const filePath = resolve(pagesDir, entry.filePath);
114
97
  const code = readFileSync(filePath, "utf-8");
115
98
  const importerDir = dirname(filePath);
116
- for (const islandImport of findIslandImports(code)) {
99
+ for (const islandImport of findIslandImports(code, filePath)) {
117
100
  const spec = islandImport.specifier;
118
101
  const strategy = islandImport.hydrate;
119
102
  const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
@@ -174,10 +157,10 @@ function islandsPlugin(pagesDir, options) {
174
157
  handler(code, id) {
175
158
  if (/import\s+\{[^}]*\buseForm\b/.test(code)) this.warn(`islands: useForm cannot be imported in island page '${id}'. Use useIslandForm instead.`);
176
159
  const importerDir = dirname(id);
177
- let transformed = code;
160
+ const edits = [];
178
161
  const wrappers = [];
179
162
  let idx = 0;
180
- for (const islandImport of findIslandImports(code)) {
163
+ for (const islandImport of findIslandImports(code, id)) {
181
164
  const name = islandImport.importName;
182
165
  const specRaw = islandImport.specifierRaw;
183
166
  const strategy = islandImport.hydrate;
@@ -194,16 +177,17 @@ function islandsPlugin(pagesDir, options) {
194
177
  }
195
178
  const islandId = absPath ? normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "") : spec;
196
179
  const replacement = `import ${rawName} from ${specRaw}`;
197
- transformed = transformed.replace(islandImport.statement, replacement);
180
+ edits.push({
181
+ start: islandImport.start,
182
+ end: islandImport.end,
183
+ text: replacement
184
+ });
198
185
  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)); };`);
199
186
  }
200
- if (wrappers.length > 0) {
201
- transformed = `import _React_island from "react";\n` + transformed + "\n" + wrappers.join("\n");
202
- return {
203
- code: transformed,
204
- map: null
205
- };
206
- }
187
+ if (wrappers.length > 0) return editIslandSource(code, edits, {
188
+ prepend: "import _React_island from \"react\";\n",
189
+ append: "\n" + wrappers.join("\n")
190
+ });
207
191
  }
208
192
  },
209
193
  config(_, env) {
@@ -292,14 +276,9 @@ function generateIslandClientEntry(manifest, cssFiles, isDev) {
292
276
  for (const entry of entries) uniqueIslands.set(entry.islandId, entry.componentPath);
293
277
  const lines = [];
294
278
  lines.push("import { initIslands } from \"void/pages-client\";");
295
- if (!isDev) {
296
- const cssImportRe = /^\s*import\b[^"']*["']([^"']+\.(?:css|scss|sass|less|styl|stylus|pcss|postcss))["']/gm;
297
- for (const file of cssFiles) {
298
- const content = readFileSync(file, "utf-8");
299
- cssImportRe.lastIndex = 0;
300
- let match;
301
- while ((match = cssImportRe.exec(content)) !== null) lines.push(rewriteCssImportForVirtualModule(match[0], match[1], file));
302
- }
279
+ if (!isDev) for (const file of cssFiles) {
280
+ const content = readFileSync(file, "utf-8");
281
+ for (const item of findStaticCssImports(content, file)) lines.push(rewriteCssImportForVirtualModule(item.statement, item.specifier, file));
303
282
  }
304
283
  lines.push("const islands = {");
305
284
  for (const [id, path] of uniqueIslands) lines.push(` ${JSON.stringify(id)}: () => import(${JSON.stringify(path)}),`);
@@ -1,7 +1,7 @@
1
1
  import { a as SharedContext, i as RouterContext, n as NavigationContext, r as ParamsContext, t as ErrorsContext } from "../context-B0GL7p5m.mjs";
2
2
  import { n as setActionRouter } from "../action-Bq1iSG6m.mjs";
3
- import { t as DEFERRED_SHELL_END_ID } from "../deferred-shell-CBIT2Zwq.mjs";
4
- import React, { Suspense, startTransition, use, useCallback, useEffect, useInsertionEffect, useLayoutEffect, useMemo, useReducer, useRef, useTransition } from "react";
3
+ import { n as DEFERRED_SHELL_END_ID } from "../deferred-shell-DOuP2YAo.mjs";
4
+ import React, { Suspense, startTransition, use, useCallback, useEffect, useInsertionEffect, useLayoutEffect, useMemo, useReducer, useTransition } from "react";
5
5
  import { createPageNavigationEngine, createRouterFacade, idleNavigationState, isAbortError, isCallSiteActionError } from "void/pages-client";
6
6
  import { createRoot, hydrateRoot } from "react-dom/client";
7
7
  //#region src/runtime/pages-client.ts
@@ -174,7 +174,7 @@ function RouteResource({ resource, onCommit }) {
174
174
  const Layout = result.layouts[i];
175
175
  element = React.createElement(Layout, null, element);
176
176
  }
177
- return React.createElement(RouterContext.Provider, { value: routeRouter }, React.createElement(ParamsContext.Provider, { value: routeParams }, React.createElement(SharedContext.Provider, { value: pageData.shared }, React.createElement(ErrorsContext.Provider, { value: pageData.errors ?? {} }, element))));
177
+ return React.createElement(RouterContext.Provider, { value: routeRouter }, React.createElement(ParamsContext.Provider, { value: routeParams }, React.createElement(SharedContext.Provider, { value: pageData.shared ?? {} }, React.createElement(ErrorsContext.Provider, { value: pageData.errors ?? {} }, element))));
178
178
  }
179
179
  function startViewTransition(resource, reveal, enabled) {
180
180
  if (!enabled || typeof document.startViewTransition !== "function") {
@@ -193,7 +193,6 @@ function startViewTransition(resource, reveal, enabled) {
193
193
  }
194
194
  function App({ engine, router, viewTransitions, navigationFallback: NavigationFallback }) {
195
195
  const initialPageData = engine.initialPageData;
196
- const sharedStateRef = useRef(initialPageData.shared);
197
196
  const initialResource = useMemo(() => ({
198
197
  id: 0,
199
198
  url: initialPageData.url || currentUrl(),
@@ -203,10 +202,7 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
203
202
  options: {},
204
203
  immediate: false,
205
204
  shell: Promise.resolve({
206
- pageData: {
207
- ...initialPageData,
208
- shared: initialPageData.shared
209
- },
205
+ pageData: initialPageData,
210
206
  component: engine.initialComponent,
211
207
  layouts: engine.initialLayouts
212
208
  }),
@@ -222,19 +218,6 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
222
218
  navigation: idleNavigationState()
223
219
  });
224
220
  const [isTransitionPending, startTransition] = useTransition();
225
- const mergeShared = useCallback((result) => {
226
- if (!result) return result;
227
- sharedStateRef.current = {
228
- ...sharedStateRef.current,
229
- ...result.pageData.shared || {}
230
- };
231
- const shared = sharedStateRef.current;
232
- result.pageData = {
233
- ...result.pageData,
234
- shared
235
- };
236
- return result;
237
- }, []);
238
221
  const finishNavigation = useCallback((resource, result) => {
239
222
  if (committedResources.has(resource.id)) return;
240
223
  committedResources.add(resource.id);
@@ -299,7 +282,7 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
299
282
  });
300
283
  const next = {
301
284
  ...prepared,
302
- shell: prepared.shell.then(mergeShared).then((result) => {
285
+ shell: prepared.shell.then((result) => {
303
286
  if (!result) {
304
287
  const resolveViewTransition = viewTransitionWaiters.get(prepared.id);
305
288
  if (resolveViewTransition) {
@@ -394,7 +377,6 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
394
377
  return pagePromise;
395
378
  }, [
396
379
  engine,
397
- mergeShared,
398
380
  NavigationFallback,
399
381
  startTransition,
400
382
  viewTransitions
@@ -1,12 +1,101 @@
1
1
  import { a as SharedContext, i as RouterContext, n as NavigationContext, r as ParamsContext, t as ErrorsContext } from "../context-B0GL7p5m.mjs";
2
- import { n as DEFERRED_SHELL_END_MARKER, t as DEFERRED_SHELL_END_ID } from "../deferred-shell-CBIT2Zwq.mjs";
2
+ import { n as DEFERRED_SHELL_END_ID, r as DEFERRED_SHELL_END_MARKER, t as DEFERRED_SHELL_CANCEL_REASON } from "../deferred-shell-DOuP2YAo.mjs";
3
3
  import React from "react";
4
4
  import { createSsrRouter, idleNavigationState } from "void/pages-client";
5
5
  import { renderToReadableStream } from "react-dom/server";
6
6
  import { renderBodyAttrs, renderHeadToString, renderHtmlAttrs } from "void/pages-head";
7
7
  import { serializePageData } from "void/pages-server";
8
+ //#region src/runtime/page-streams.ts
9
+ function concatStreams(streams) {
10
+ let index = 0;
11
+ let reader;
12
+ let cancelled = false;
13
+ const cancelRemaining = async (reason) => {
14
+ const cancellations = [];
15
+ if (reader) cancellations.push(reader.cancel(reason));
16
+ for (let remaining = index + (reader ? 1 : 0); remaining < streams.length; remaining++) cancellations.push(streams[remaining].cancel(reason));
17
+ await Promise.allSettled(cancellations);
18
+ };
19
+ return new ReadableStream({
20
+ async pull(controller) {
21
+ try {
22
+ while (index < streams.length) {
23
+ reader ??= streams[index].getReader();
24
+ const { done, value } = await reader.read();
25
+ if (cancelled) return;
26
+ if (done) {
27
+ reader.releaseLock();
28
+ reader = void 0;
29
+ index++;
30
+ continue;
31
+ }
32
+ controller.enqueue(value);
33
+ return;
34
+ }
35
+ controller.close();
36
+ } catch (error) {
37
+ if (!cancelled) {
38
+ controller.error(error);
39
+ await cancelRemaining(error);
40
+ }
41
+ }
42
+ },
43
+ async cancel(reason) {
44
+ cancelled = true;
45
+ await cancelRemaining(reason);
46
+ }
47
+ }, { highWaterMark: 0 });
48
+ }
49
+ function readDeferredShell(stream) {
50
+ const reader = stream.getReader();
51
+ const decoder = new TextDecoder();
52
+ const encoder = new TextEncoder();
53
+ const keepLength = DEFERRED_SHELL_END_MARKER.length - 1;
54
+ let buffer = "";
55
+ let cancelled = false;
56
+ return new ReadableStream({
57
+ async pull(controller) {
58
+ try {
59
+ while (true) {
60
+ const { done, value } = await reader.read();
61
+ if (cancelled) return;
62
+ buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
63
+ const markerIndex = buffer.indexOf(DEFERRED_SHELL_END_MARKER);
64
+ if (markerIndex !== -1) {
65
+ const shell = buffer.slice(0, markerIndex + DEFERRED_SHELL_END_MARKER.length);
66
+ await reader.cancel(DEFERRED_SHELL_CANCEL_REASON);
67
+ if (!cancelled) {
68
+ controller.enqueue(encoder.encode(shell));
69
+ controller.close();
70
+ }
71
+ return;
72
+ }
73
+ if (done) {
74
+ if (buffer) controller.enqueue(encoder.encode(buffer));
75
+ controller.close();
76
+ return;
77
+ }
78
+ if (buffer.length > keepLength) {
79
+ controller.enqueue(encoder.encode(buffer.slice(0, -keepLength)));
80
+ buffer = buffer.slice(-keepLength);
81
+ return;
82
+ }
83
+ }
84
+ } catch (error) {
85
+ if (!cancelled) {
86
+ controller.error(error);
87
+ await reader.cancel(error).catch(() => {});
88
+ }
89
+ }
90
+ },
91
+ async cancel(reason) {
92
+ cancelled = true;
93
+ await reader.cancel(reason);
94
+ }
95
+ }, { highWaterMark: 0 });
96
+ }
97
+ //#endregion
8
98
  //#region src/runtime/pages-server.ts
9
- const DEFERRED_SHELL_CANCEL_REASON = "Void deferred shell complete";
10
99
  function createPendingDeferred() {
11
100
  return new Promise(() => {});
12
101
  }
@@ -23,59 +112,6 @@ function textStream(text) {
23
112
  controller.close();
24
113
  } });
25
114
  }
26
- function concatStreams(streams) {
27
- return new ReadableStream({ async start(controller) {
28
- try {
29
- for (const stream of streams) {
30
- const reader = stream.getReader();
31
- while (true) {
32
- const { done, value } = await reader.read();
33
- if (done) break;
34
- controller.enqueue(value);
35
- }
36
- }
37
- controller.close();
38
- } catch (error) {
39
- controller.error(error);
40
- }
41
- } });
42
- }
43
- function readDeferredShell(stream) {
44
- const encoder = new TextEncoder();
45
- return new ReadableStream({ async start(controller) {
46
- const reader = stream.getReader();
47
- const decoder = new TextDecoder();
48
- let buffer = "";
49
- const keepLength = DEFERRED_SHELL_END_MARKER.length - 1;
50
- const flush = (text) => {
51
- if (text) controller.enqueue(encoder.encode(text));
52
- };
53
- try {
54
- while (true) {
55
- const { done, value } = await reader.read();
56
- if (done) {
57
- buffer += decoder.decode();
58
- flush(buffer);
59
- break;
60
- }
61
- buffer += decoder.decode(value, { stream: true });
62
- const markerIndex = buffer.indexOf(DEFERRED_SHELL_END_MARKER);
63
- if (markerIndex !== -1) {
64
- flush(buffer.slice(0, markerIndex + DEFERRED_SHELL_END_MARKER.length));
65
- await reader.cancel(DEFERRED_SHELL_CANCEL_REASON);
66
- break;
67
- }
68
- if (buffer.length > keepLength) {
69
- flush(buffer.slice(0, -keepLength));
70
- buffer = buffer.slice(-keepLength);
71
- }
72
- }
73
- controller.close();
74
- } catch (error) {
75
- controller.error(error);
76
- }
77
- } });
78
- }
79
115
  async function renderReactPage({ pageObj, assetTags, components, layoutTree, preambleScript = "" }) {
80
116
  const PageComponent = (await components[pageObj.component]()).default;
81
117
  const layoutIds = layoutTree[pageObj.component] || [];
@@ -86,7 +122,7 @@ async function renderReactPage({ pageObj, assetTags, components, layoutTree, pre
86
122
  element = React.createElement(RouterContext.Provider, { value: createSsrRouter(pageObj.url || "/", pageObj.params ?? {}) }, React.createElement(NavigationContext.Provider, { value: idleNavigationState() }, React.createElement(React.Suspense, { fallback: null }, React.createElement(SharedContext.Provider, { value: pageObj.shared || {} }, React.createElement(ParamsContext.Provider, { value: pageObj.params ?? {} }, React.createElement(ErrorsContext.Provider, { value: pageObj.errors || {} }, element))))));
87
123
  const renderElement = pageObj.deferredKeys ? React.createElement(React.Fragment, null, element, React.createElement("template", { id: DEFERRED_SHELL_END_ID })) : element;
88
124
  const appStream = await renderToReadableStream(renderElement, { onError(error) {
89
- if (error === DEFERRED_SHELL_CANCEL_REASON) return;
125
+ if (error === "Void deferred shell complete") return;
90
126
  throw error;
91
127
  } });
92
128
  const renderedAppStream = pageObj.deferredKeys ? readDeferredShell(appStream) : appStream;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@void/react",
3
- "version": "0.21.1",
3
+ "version": "0.21.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/voidzero-dev/void.git",
@@ -43,13 +43,13 @@
43
43
  "react-dom": "^19.3.0",
44
44
  "vite": "npm:@voidzero-dev/vite-plus-core@0.3.1",
45
45
  "vite-plus": "0.3.1",
46
- "void": "0.21.1"
46
+ "void": "0.21.3"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "react": "^19.0.0",
50
50
  "react-dom": "^19.0.0",
51
51
  "vite": "^8.0.0",
52
- "void": "0.21.1"
52
+ "void": "0.21.3"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "vp pack",