void 0.7.5 → 0.7.6

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,5 @@
1
1
  import { a as renderHtmlAttrs, i as renderHeadToString, n as HeadDescriptor, o as resolveHead, r as renderBodyAttrs, t as HeadConfig } from "../head-CZGAunBV.mjs";
2
- import { i as PageScanResult, n as NamedLayoutDefinition, r as PageDefinition, t as LayoutDefinition } from "../types-AdKzPp2C.mjs";
2
+ import { a as PageScanResult, i as PageDefinition, n as LayoutDefinition, r as NamedLayoutDefinition } from "../types-D6AuY5Bf.mjs";
3
3
  import { serializePageData } from "./serialize.mjs";
4
4
 
5
5
  //#region src/pages/layout-resolution.d.ts
@@ -1,4 +1,4 @@
1
- import { i as PageScanResult } from "../types-AdKzPp2C.mjs";
1
+ import { a as PageScanResult } from "../types-D6AuY5Bf.mjs";
2
2
  import { ResolvedConfig, ViteDevServer } from "vite";
3
3
 
4
4
  //#region src/pages/islands-plugin.d.ts
@@ -17,8 +17,6 @@ declare function resolveSpecifier(specifier: string, importerDir: string, extens
17
17
  * or any .md file has a `<script>` block.
18
18
  */
19
19
  declare function needsIslandClientBundle(dir: string, islandFilePattern: RegExp, checkStyleBlocks: boolean): boolean;
20
- /** Check if a file has CSS import statements (JS-style imports). */
21
- declare function fileHasCssImports(filePath: string): boolean;
22
20
  /**
23
21
  * Rewrite a matched CSS import line so relative specifiers resolve from the
24
22
  * virtual module. Absolute and bare specifiers are kept as-is.
@@ -26,16 +24,9 @@ declare function fileHasCssImports(filePath: string): boolean;
26
24
  declare function rewriteCssImportForVirtualModule(matchedLine: string, specifier: string, sourceFilePath: string): string;
27
25
  /** Extract CSS import statements from a file and return them as @import rules. */
28
26
  declare function extractCssImports(filePath: string): string;
29
- /** Rebuild islandCssMap from current file contents. */
30
- declare function rebuildIslandCssMap(pageScan: PageScanResult, pagesDir: string, islandCssMap: Map<string, Array<string>>, fileHasCssFn: (filePath: string) => boolean): void;
31
27
  /** Compute which island pages need client-side JavaScript. */
32
28
  declare function computePagesNeedingClientJS(pageScan: PageScanResult, pagesDir: string, resolvedConfig: ResolvedConfig | null): Set<string>;
33
29
  /**
34
- * Configure the Vite dev server for island CSS serving.
35
- * Sets up file watching and a middleware for serving virtual CSS modules.
36
- */
37
- declare function configureIslandCssServer(server: ViteDevServer, islandCssFiles: Set<string>, viteRoot: string, rebuildCssMap: () => void): void;
38
- /**
39
30
  * Watch the pages directory for structural changes (add/remove) and .md content
40
31
  * changes (frontmatter layout field) so that new layouts and named layouts are
41
32
  * picked up without a dev server restart.
@@ -47,4 +38,4 @@ declare function watchPagesForRescan(server: ViteDevServer, pagesDir: string, ro
47
38
  flush: () => Promise<void>;
48
39
  };
49
40
  //#endregion
50
- export { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, IslandEntry, computePagesNeedingClientJS, configureIslandCssServer, extractCssImports, fileHasCssImports, needsIslandClientBundle, rebuildIslandCssMap, resolveSpecifier, rewriteCssImportForVirtualModule, watchPagesForRescan };
41
+ export { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, IslandEntry, computePagesNeedingClientJS, extractCssImports, needsIslandClientBundle, resolveSpecifier, rewriteCssImportForVirtualModule, watchPagesForRescan };
@@ -43,15 +43,6 @@ function needsIslandClientBundle(dir, islandFilePattern, checkStyleBlocks) {
43
43
  } catch {}
44
44
  return false;
45
45
  }
46
- /** Check if a file has CSS import statements (JS-style imports). */
47
- function fileHasCssImports(filePath) {
48
- try {
49
- const code = readFileSync(filePath, "utf-8");
50
- return /import\b[^"']*["'][^"']+\.(?:css|scss|sass|less|styl|stylus|pcss|postcss)["']/.test(code);
51
- } catch {
52
- return false;
53
- }
54
- }
55
46
  /**
56
47
  * Rewrite a matched CSS import line so relative specifiers resolve from the
57
48
  * virtual module. Absolute and bare specifiers are kept as-is.
@@ -72,22 +63,6 @@ function extractCssImports(filePath) {
72
63
  while ((match = re.exec(code)) !== null) imports.push(`@import ${JSON.stringify(match[1])};`);
73
64
  return imports.join("\n");
74
65
  }
75
- /** Rebuild islandCssMap from current file contents. */
76
- function rebuildIslandCssMap(pageScan, pagesDir, islandCssMap, fileHasCssFn) {
77
- islandCssMap.clear();
78
- for (const page of pageScan.pages) {
79
- if (!page.island) continue;
80
- const cssPaths = [];
81
- const layoutChain = resolveEffectiveLayoutChain(page, pageScan.layouts, pageScan.namedLayouts);
82
- for (const layout of layoutChain) {
83
- const layoutPath = resolve(pagesDir, layout.filePath);
84
- if (fileHasCssFn(layoutPath)) cssPaths.push(layoutPath);
85
- }
86
- const pagePath = resolve(pagesDir, page.componentPath);
87
- if (fileHasCssFn(pagePath)) cssPaths.push(pagePath);
88
- if (cssPaths.length > 0) islandCssMap.set(page.componentId, cssPaths);
89
- }
90
- }
91
66
  /** Compute which island pages need client-side JavaScript. */
92
67
  function computePagesNeedingClientJS(pageScan, pagesDir, resolvedConfig) {
93
68
  const mdPlugin = resolvedConfig?.plugins?.find((plugin) => plugin.name === "void-md");
@@ -122,43 +97,6 @@ function computePagesNeedingClientJS(pageScan, pagesDir, resolvedConfig) {
122
97
  return result;
123
98
  }
124
99
  /**
125
- * Configure the Vite dev server for island CSS serving.
126
- * Sets up file watching and a middleware for serving virtual CSS modules.
127
- */
128
- function configureIslandCssServer(server, islandCssFiles, viteRoot, rebuildCssMap) {
129
- server.watcher.on("change", (path) => {
130
- const normalizedPath = normalizePath(path);
131
- if (!islandCssFiles.has(normalizedPath)) return;
132
- rebuildCssMap();
133
- for (const env of Object.values(server.environments)) {
134
- const mod = env.moduleGraph.getModuleById("\0" + ISLANDS_RENDERER_ID);
135
- if (mod) env.moduleGraph.invalidateModule(mod);
136
- }
137
- const cssMod = server.environments.client?.moduleGraph.getModuleById(normalizedPath + ".css");
138
- if (cssMod) server.environments.client.moduleGraph.invalidateModule(cssMod);
139
- });
140
- server.middlewares.use(async (req, res, next) => {
141
- if (!req.url) return next();
142
- const url = req.url.split("?")[0];
143
- if (!url.endsWith(".css")) return next();
144
- const srcPath = normalizePath(resolve(viteRoot, url.slice(1, -4)));
145
- if (!islandCssFiles.has(srcPath)) return next();
146
- try {
147
- const result = await server.environments.client.transformRequest(url);
148
- if (result) {
149
- const m = result.code.match(/\b__vite__css\s*=\s*("(?:[^"\\]|\\.)*")/) || result.code.match(/export\s+default\s+("(?:[^"\\]|\\.)*")/);
150
- if (m) {
151
- res.setHeader("Content-Type", "text/css");
152
- res.statusCode = 200;
153
- res.end(JSON.parse(m[1]));
154
- return;
155
- }
156
- }
157
- } catch {}
158
- next();
159
- });
160
- }
161
- /**
162
100
  * Watch the pages directory for structural changes (add/remove) and .md content
163
101
  * changes (frontmatter layout field) so that new layouts and named layouts are
164
102
  * picked up without a dev server restart.
@@ -192,4 +130,4 @@ function watchPagesForRescan(server, pagesDir, root, onRescan) {
192
130
  return { flush: () => pending ?? Promise.resolve() };
193
131
  }
194
132
  //#endregion
195
- export { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, configureIslandCssServer, extractCssImports, fileHasCssImports, needsIslandClientBundle, rebuildIslandCssMap, resolveSpecifier, rewriteCssImportForVirtualModule, watchPagesForRescan };
133
+ export { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, extractCssImports, needsIslandClientBundle, resolveSpecifier, rewriteCssImportForVirtualModule, watchPagesForRescan };
@@ -1,3 +1,3 @@
1
1
  import { S as ValidationError } from "../handler-dKQWyF-G.mjs";
2
- import { a as handlePageMutation, i as handlePageGet, n as PageRenderResult, o as safeJsonStringify, r as flattenValidationErrors, t as PageObject } from "../protocol-DxGzKEz2.mjs";
2
+ import { a as handlePageMutation, i as handlePageGet, n as PageRenderResult, o as safeJsonStringify, r as flattenValidationErrors, t as PageObject } from "../protocol-COz1NHxc.mjs";
3
3
  export { PageObject, PageRenderResult, ValidationError, flattenValidationErrors, handlePageGet, handlePageMutation, safeJsonStringify };
@@ -3,6 +3,7 @@ import { i as ValidationError, r as separateDeferred, t as DEFERRED } from "../d
3
3
  import { resolveHead } from "./head.mjs";
4
4
  //#region src/pages/protocol.ts
5
5
  const POWERED_BY = { "X-Powered-By": "Void" };
6
+ const RENDER_ERROR_ASSET_TAGS_KEY = "__voidPageAssetTags";
6
7
  /** Escape characters that are unsafe inside inline <script> tags to prevent XSS. */
7
8
  function safeJsonStringify(value) {
8
9
  return (JSON.stringify(value) ?? "null").replaceAll("<", "\\u003c").replaceAll(">", "\\u003e").replaceAll("&", "\\u0026").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
@@ -15,6 +16,85 @@ function pageResponseHeaders(c, headers) {
15
16
  for (const [key, value] of Object.entries(headers)) out.set(key, value);
16
17
  return out;
17
18
  }
19
+ function escapeHtmlAttr(value) {
20
+ return value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;");
21
+ }
22
+ function serializeStyleCss(css) {
23
+ return css.replace(/<\/style/gi, "<\\/style");
24
+ }
25
+ function serializeDevCssAssets(assets, nonce) {
26
+ return assets.map((asset) => `<style data-vite-dev-id="${escapeHtmlAttr(asset.id ?? asset.url)}" nonce="${escapeHtmlAttr(nonce)}">${serializeStyleCss(asset.css)}</style>`).join("\n");
27
+ }
28
+ function hasCompatibleStyleSource(value, nonceSource) {
29
+ return value === "'unsafe-inline'" || value === nonceSource;
30
+ }
31
+ function adjustCspValue(value, nonce) {
32
+ const nonceSource = `'nonce-${nonce}'`;
33
+ const parsed = value.split(";").map((part) => part.trim()).filter(Boolean).map((directive) => {
34
+ const [name = "", ...sources] = directive.split(/\s+/);
35
+ return {
36
+ name: name.toLowerCase(),
37
+ originalName: name,
38
+ sources
39
+ };
40
+ });
41
+ const styleElem = parsed.find((directive) => directive.name === "style-src-elem");
42
+ if (styleElem) {
43
+ if (!styleElem.sources.some((source) => hasCompatibleStyleSource(source, nonceSource))) styleElem.sources.push(nonceSource);
44
+ return parsed.map((directive) => [directive.originalName, ...directive.sources].join(" ")).join("; ");
45
+ }
46
+ const style = parsed.find((directive) => directive.name === "style-src");
47
+ if (style) {
48
+ if (!style.sources.some((source) => hasCompatibleStyleSource(source, nonceSource))) style.sources.push(nonceSource);
49
+ return parsed.map((directive) => [directive.originalName, ...directive.sources].join(" ")).join("; ");
50
+ }
51
+ const defaultSrc = parsed.find((directive) => directive.name === "default-src");
52
+ if (defaultSrc) {
53
+ parsed.push({
54
+ name: "style-src",
55
+ originalName: "style-src",
56
+ sources: [...defaultSrc.sources, nonceSource]
57
+ });
58
+ return parsed.map((directive) => [directive.originalName, ...directive.sources].join(" ")).join("; ");
59
+ }
60
+ parsed.push({
61
+ name: "style-src",
62
+ originalName: "style-src",
63
+ sources: [nonceSource]
64
+ });
65
+ return parsed.map((directive) => [directive.originalName, ...directive.sources].join(" ")).join("; ");
66
+ }
67
+ function adjustDevCssCsp(headers, nonce) {
68
+ if (!nonce) return headers;
69
+ const csp = headers.get("content-security-policy");
70
+ if (csp) headers.set("content-security-policy", adjustCspValue(csp, nonce));
71
+ const cspReportOnly = headers.get("content-security-policy-report-only");
72
+ if (cspReportOnly) headers.set("content-security-policy-report-only", adjustCspValue(cspReportOnly, nonce));
73
+ return headers;
74
+ }
75
+ function prepareAssetTags(assetTags) {
76
+ if (!assetTags.devCss || assetTags.devCss.length === 0) return assetTags;
77
+ const nonce = crypto.randomUUID().replaceAll("-", "");
78
+ return {
79
+ ...assetTags,
80
+ css: [assetTags.css, serializeDevCssAssets(assetTags.devCss, nonce)].filter(Boolean).join("\n"),
81
+ devCssNonce: nonce
82
+ };
83
+ }
84
+ function attachRenderErrorAssetTags(error, assetTags) {
85
+ if ((typeof error === "object" || typeof error === "function") && error !== null) Object.defineProperty(error, RENDER_ERROR_ASSET_TAGS_KEY, {
86
+ configurable: true,
87
+ value: assetTags
88
+ });
89
+ throw error;
90
+ }
91
+ async function renderWithAssetTags(renderFn, pageObj, assetTags) {
92
+ try {
93
+ return await renderFn(pageObj, assetTags);
94
+ } catch (error) {
95
+ attachRenderErrorAssetTags(error, assetTags);
96
+ }
97
+ }
18
98
  function pageJsonResponse(c, pageObj) {
19
99
  return Response.json(pageObj, { headers: pageResponseHeaders(c, {
20
100
  "X-VoidPages": "true",
@@ -65,14 +145,14 @@ async function writeRenderResult(writer, encoder, result) {
65
145
  await writer.write(value);
66
146
  }
67
147
  }
68
- function renderedResponse(c, result, contentType) {
69
- return new Response(result, { headers: pageResponseHeaders(c, {
148
+ function renderedResponse(c, result, contentType, assetTags) {
149
+ return new Response(result, { headers: adjustDevCssCsp(pageResponseHeaders(c, {
70
150
  "content-type": contentType,
71
151
  ...POWERED_BY
72
- }) });
152
+ }), assetTags?.devCssNonce) });
73
153
  }
74
154
  /** Build a streaming response that sends an initial chunk then streams deferred results. */
75
- function streamDeferredResponse(initialChunk, deferredMap, c, contentType, formatResolved, formatRejected) {
155
+ function streamDeferredResponse(initialChunk, deferredMap, c, contentType, formatResolved, formatRejected, assetTags) {
76
156
  const encoder = new TextEncoder();
77
157
  const { readable, writable } = new TransformStream();
78
158
  const writer = writable.getWriter();
@@ -84,10 +164,10 @@ function streamDeferredResponse(initialChunk, deferredMap, c, contentType, forma
84
164
  })().catch((error) => {
85
165
  writer.abort(error).catch(() => {});
86
166
  });
87
- return new Response(readable, { headers: pageResponseHeaders(c, {
167
+ return new Response(readable, { headers: adjustDevCssCsp(pageResponseHeaders(c, {
88
168
  "content-type": contentType,
89
169
  ...POWERED_BY
90
- }) });
170
+ }), assetTags?.devCssNonce) });
91
171
  }
92
172
  /** Handle GET page request — run handler, return page object or SSR HTML */
93
173
  async function handlePageGet(handler, headFn, c, componentId, renderFn, getAssetTags, configHead, island) {
@@ -150,9 +230,10 @@ async function handlePageGet(handler, headFn, c, componentId, renderFn, getAsset
150
230
  error
151
231
  }) + "\n");
152
232
  }
153
- const html = await renderFn(pageObj, await getAssetTags(c.req.raw, c.env, componentId));
154
- if (!hasDeferred) return renderedResponse(c, html, "text/html; charset=utf-8");
155
- return streamDeferredResponse(html, deferredMap, c, "text/html; charset=utf-8", (group, data) => `\n<script>window.__resolveDeferred?window.__resolveDeferred(${safeJsonStringify(group)},${safeJsonStringify(data)}):(window.__deferredQueue??=[]).push([${safeJsonStringify(group)},${safeJsonStringify(data)}])<\/script>`, (group, error) => `\n<script>window.__rejectDeferred?window.__rejectDeferred(${safeJsonStringify(group)},${safeJsonStringify(error)}):(window.__deferredQueue??=[]).push([${safeJsonStringify(group)},0,${safeJsonStringify(error)}])<\/script>`);
233
+ const assetTags = prepareAssetTags(await getAssetTags(c.req.raw, c.env, componentId));
234
+ const html = await renderWithAssetTags(renderFn, pageObj, assetTags);
235
+ if (!hasDeferred) return renderedResponse(c, html, "text/html; charset=utf-8", assetTags);
236
+ return streamDeferredResponse(html, deferredMap, c, "text/html; charset=utf-8", (group, data) => `\n<script>window.__resolveDeferred?window.__resolveDeferred(${safeJsonStringify(group)},${safeJsonStringify(data)}):(window.__deferredQueue??=[]).push([${safeJsonStringify(group)},${safeJsonStringify(data)}])<\/script>`, (group, error) => `\n<script>window.__rejectDeferred?window.__rejectDeferred(${safeJsonStringify(group)},${safeJsonStringify(error)}):(window.__deferredQueue??=[]).push([${safeJsonStringify(group)},0,${safeJsonStringify(error)}])<\/script>`, assetTags);
156
237
  }
157
238
  /** Handle POST/PUT/DELETE page request — run mutation, then re-run GET or redirect */
158
239
  async function handlePageMutation(mutationHandler, getHandler, headFn, c, componentId, renderFn, getAssetTags, configHead, island) {
@@ -178,7 +259,8 @@ async function handlePageMutation(mutationHandler, getHandler, headFn, c, compon
178
259
  if (island) return Response.json({ errors: e.errors }, { status: 422 });
179
260
  const pageObj = buildMutationPageObj(stripDeferred(getHandler ? await extractProps(await getHandler(c)) : {}), e.errors);
180
261
  if (isPagesRequest(c)) return pageJsonResponse(c, pageObj);
181
- return renderedResponse(c, await renderFn(pageObj, await getAssetTags(c.req.raw, c.env, componentId)), "text/html; charset=utf-8");
262
+ const assetTags = prepareAssetTags(await getAssetTags(c.req.raw, c.env, componentId));
263
+ return renderedResponse(c, await renderWithAssetTags(renderFn, pageObj, assetTags), "text/html; charset=utf-8", assetTags);
182
264
  }
183
265
  throw e;
184
266
  }
@@ -190,14 +272,16 @@ async function handlePageMutation(mutationHandler, getHandler, headFn, c, compon
190
272
  if (island) return Response.json({ errors }, { status: 422 });
191
273
  const pageObj = buildMutationPageObj(stripDeferred(getHandler ? await extractProps(await getHandler(c)) : {}), errors);
192
274
  if (isPagesRequest(c)) return pageJsonResponse(c, pageObj);
193
- return renderedResponse(c, await renderFn(pageObj, await getAssetTags(c.req.raw, c.env, componentId)), "text/html; charset=utf-8");
275
+ const assetTags = prepareAssetTags(await getAssetTags(c.req.raw, c.env, componentId));
276
+ return renderedResponse(c, await renderWithAssetTags(renderFn, pageObj, assetTags), "text/html; charset=utf-8", assetTags);
194
277
  }
195
278
  if (status !== 204) return result;
196
279
  }
197
280
  if (island) return Response.json({ success: true });
198
281
  const pageObj = buildMutationPageObj(stripDeferred(getHandler ? await extractProps(await getHandler(c)) : {}), null);
199
282
  if (isPagesRequest(c)) return pageJsonResponse(c, pageObj);
200
- return renderedResponse(c, await renderFn(pageObj, await getAssetTags(c.req.raw, c.env, componentId)), "text/html; charset=utf-8");
283
+ const assetTags = prepareAssetTags(await getAssetTags(c.req.raw, c.env, componentId));
284
+ return renderedResponse(c, await renderWithAssetTags(renderFn, pageObj, assetTags), "text/html; charset=utf-8", assetTags);
201
285
  }
202
286
  //#endregion
203
287
  export { ValidationError, flattenValidationErrors, handlePageGet, handlePageMutation, safeJsonStringify };