@askrjs/vite 0.0.7 → 0.0.9

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/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # @askrjs/vite
2
2
 
3
+ [![CI](https://github.com/askrjs/askr-vite/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/askrjs/askr-vite/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/%40askrjs%2Fvite.svg)](https://www.npmjs.com/package/@askrjs/vite)
5
+
3
6
  Vite plugin for Askr JSX and template transforms.
4
7
 
5
8
  `@askrjs/vite` is the build-time glue that lets Askr projects use the
package/dist/server.js CHANGED
@@ -1,8 +1,103 @@
1
1
 
2
+ import { parseFragment, serialize } from "parse5";
2
3
  import { createNodeHandler } from "@askrjs/node";
3
4
  import { readFile } from "node:fs/promises";
4
5
  import { existsSync } from "node:fs";
5
6
  import { resolve } from "node:path";
7
+ //#region src/server/head.ts
8
+ const htmlNamespace = "http://www.w3.org/1999/xhtml";
9
+ const linkAttributes = /* @__PURE__ */ new Set([
10
+ "as",
11
+ "crossorigin",
12
+ "data-askr-head",
13
+ "fetchpriority",
14
+ "href",
15
+ "hreflang",
16
+ "imagesizes",
17
+ "imagesrcset",
18
+ "media",
19
+ "referrerpolicy",
20
+ "rel",
21
+ "sizes",
22
+ "type"
23
+ ]);
24
+ function headMarkupError(message) {
25
+ throw new TypeError(`Invalid x-askr-head metadata: ${message}`);
26
+ }
27
+ function attributesOf(node) {
28
+ const attributes = /* @__PURE__ */ new Map();
29
+ for (const attribute of node.attrs) {
30
+ if (attribute.namespace || attribute.prefix) headMarkupError("namespaced attributes are not allowed.");
31
+ attributes.set(attribute.name.toLowerCase(), attribute.value);
32
+ }
33
+ return attributes;
34
+ }
35
+ function requireAllowedAttributes(attributes, allowed) {
36
+ for (const name of attributes.keys()) if (!allowed.has(name)) headMarkupError(`attribute ${name} is not allowed.`);
37
+ if (attributes.get("data-askr-head") !== "") headMarkupError("every metadata node must be Askr-owned.");
38
+ }
39
+ function isTextNode(node) {
40
+ return "value" in node;
41
+ }
42
+ function textContent(node) {
43
+ let value = "";
44
+ for (const child of node.childNodes) {
45
+ if (!isTextNode(child)) headMarkupError(`${node.tagName} must contain only text.`);
46
+ value += child.value;
47
+ }
48
+ return value;
49
+ }
50
+ function validateHeadElement(node) {
51
+ if (node.namespaceURI !== htmlNamespace) headMarkupError("foreign elements are not allowed.");
52
+ const attributes = attributesOf(node);
53
+ if (node.tagName === "title") {
54
+ requireAllowedAttributes(attributes, /* @__PURE__ */ new Set(["data-askr-head"]));
55
+ textContent(node);
56
+ return;
57
+ }
58
+ if (node.tagName === "meta") {
59
+ requireAllowedAttributes(attributes, /* @__PURE__ */ new Set([
60
+ "content",
61
+ "data-askr-head",
62
+ "name",
63
+ "property"
64
+ ]));
65
+ if (attributes.has("name") === attributes.has("property") || !attributes.has("content")) headMarkupError("meta requires content and exactly one of name or property.");
66
+ if (node.childNodes.length) headMarkupError("meta cannot contain child nodes.");
67
+ return;
68
+ }
69
+ if (node.tagName === "link") {
70
+ requireAllowedAttributes(attributes, linkAttributes);
71
+ if (!attributes.has("rel") || !attributes.has("href")) headMarkupError("link requires rel and href.");
72
+ if (node.childNodes.length) headMarkupError("link cannot contain child nodes.");
73
+ return;
74
+ }
75
+ if (node.tagName === "script") {
76
+ requireAllowedAttributes(attributes, /* @__PURE__ */ new Set(["data-askr-head", "type"]));
77
+ if (attributes.get("type") !== "application/ld+json") headMarkupError("only application/ld+json script nodes are allowed.");
78
+ const json = textContent(node);
79
+ try {
80
+ JSON.parse(json);
81
+ } catch {
82
+ headMarkupError("JSON-LD content must be valid JSON.");
83
+ }
84
+ return;
85
+ }
86
+ headMarkupError(`element ${node.tagName} is not allowed.`);
87
+ }
88
+ function normalizeAskrHead(head) {
89
+ const fragment = parseFragment(head);
90
+ for (const node of fragment.childNodes) {
91
+ if (isTextNode(node)) {
92
+ if (node.value.trim()) headMarkupError("top-level text is not allowed.");
93
+ continue;
94
+ }
95
+ if (!("tagName" in node)) headMarkupError("non-element nodes are not allowed.");
96
+ validateHeadElement(node);
97
+ }
98
+ return serialize(fragment);
99
+ }
100
+ //#endregion
6
101
  //#region src/server/document.ts
7
102
  const ASKR_APP_MARKER = "<!--askr-app-->";
8
103
  const ASKR_HEAD_MARKER = "<!--askr-head-->";
@@ -27,7 +122,7 @@ function insertAskrFragment(document, fragment) {
27
122
  return document.replace(ASKR_APP_MARKER, fragment);
28
123
  }
29
124
  function composeAskrHead(document, head, lang, dir) {
30
- let output = document.replace(ASKR_HEAD_MARKER, head);
125
+ let output = document.replace(ASKR_HEAD_MARKER, normalizeAskrHead(head));
31
126
  if (lang) output = patchHtmlAttribute(output, "lang", lang);
32
127
  if (dir) output = patchHtmlAttribute(output, "dir", dir);
33
128
  return output;
@@ -39,6 +134,19 @@ function documentHeaders(response) {
39
134
  const headers = new Headers(response.headers);
40
135
  const type = response.headers.get("content-type")?.split(";").map((part) => part.trim()).filter((part) => part.toLowerCase() !== "askr-fragment=1").join("; ");
41
136
  if (type) headers.set("content-type", type);
137
+ for (const name of [
138
+ "accept-ranges",
139
+ "content-digest",
140
+ "content-encoding",
141
+ "content-length",
142
+ "content-md5",
143
+ "content-range",
144
+ "digest",
145
+ "etag",
146
+ "last-modified",
147
+ "repr-digest",
148
+ "trailer"
149
+ ]) headers.delete(name);
42
150
  const internalHeaders = [];
43
151
  headers.forEach((_value, key) => {
44
152
  if (key.toLowerCase().startsWith("x-askr-")) internalHeaders.push(key);
@@ -133,6 +241,23 @@ function createDevelopmentApp(server, options) {
133
241
  //#region src/server/plugin.ts
134
242
  const ASKR_SERVER_MODULE_ID = "virtual:askr-server";
135
243
  const RESOLVED_SERVER_MODULE_ID = "\0askr:server";
244
+ function createNodeHandlerOptions(server) {
245
+ const serverConfig = server.config?.server;
246
+ const baseUrl = serverConfig?.origin;
247
+ if (serverConfig?.allowedHosts && serverConfig.allowedHosts !== true && serverConfig.allowedHosts.length > 0) return {
248
+ baseUrl,
249
+ allowedHosts: serverConfig.allowedHosts
250
+ };
251
+ if (baseUrl) return { baseUrl };
252
+ const host = typeof serverConfig?.host === "string" ? serverConfig.host : "localhost";
253
+ const allowedHosts = /* @__PURE__ */ new Set([
254
+ host,
255
+ "localhost",
256
+ "127.0.0.1",
257
+ "[::1]"
258
+ ]);
259
+ return { allowedHosts: Array.from(allowedHosts) };
260
+ }
136
261
  function askrServer(options) {
137
262
  let config;
138
263
  return {
@@ -167,7 +292,7 @@ function askrServer(options) {
167
292
  ].join("\n");
168
293
  },
169
294
  configureServer(server) {
170
- const handler = createNodeHandler(createDevelopmentApp(server, options));
295
+ const handler = createNodeHandler(createDevelopmentApp(server, options), createNodeHandlerOptions(server));
171
296
  return () => server.middlewares.use(handler);
172
297
  }
173
298
  };
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","names":[],"sources":["../src/server/document.ts","../src/server/development.ts","../src/server/plugin.ts"],"sourcesContent":["import type { ServerApp } from \"@askrjs/server\";\n\ninterface ViteTelemetryFields {\n status?: number;\n}\n\ninterface ViteTelemetry {\n viteDocument?<T>(fields: ViteTelemetryFields, work: () => T): T;\n}\n\nexport interface AskrDocumentOptions {\n telemetry?: ViteTelemetry;\n}\n\nexport const ASKR_APP_MARKER = \"<!--askr-app-->\";\nexport const ASKR_HEAD_MARKER = \"<!--askr-head-->\";\n\nfunction markerCount(document: string): number {\n return document.split(ASKR_APP_MARKER).length - 1;\n}\n\nfunction headMarkerCount(document: string): number {\n return document.split(ASKR_HEAD_MARKER).length - 1;\n}\n\nfunction escapeAttribute(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nfunction patchHtmlAttribute(document: string, name: \"lang\" | \"dir\", value: string): string {\n return document.replace(/<html\\b([^>]*)>/i, (_match, attributes: string) => {\n const matcher = new RegExp(\n `\\\\s+${name}(?:\\\\s*=\\\\s*(?:\"[^\"]*\"|'[^']*'|[^\\\\s\"'=<>\\u0060]+))?`,\n \"gi\",\n );\n const withoutExisting = attributes.replace(matcher, \"\");\n return `<html${withoutExisting} ${name}=\"${escapeAttribute(value)}\">`;\n });\n}\n\nexport function insertAskrFragment(document: string, fragment: string): string {\n const count = markerCount(document);\n if (count !== 1) {\n throw new Error(\n `index.html must contain exactly one ${ASKR_APP_MARKER} marker; found ${count}.`,\n );\n }\n return document.replace(ASKR_APP_MARKER, fragment);\n}\n\nexport function composeAskrHead(\n document: string,\n head: string,\n lang?: string,\n dir?: string,\n): string {\n let output = document.replace(ASKR_HEAD_MARKER, head);\n if (lang) output = patchHtmlAttribute(output, \"lang\", lang);\n if (dir) output = patchHtmlAttribute(output, \"dir\", dir);\n return output;\n}\n\nexport function isAskrFragment(response: Response): boolean {\n return (\n response.headers\n .get(\"content-type\")\n ?.split(\";\")\n .some((part) => part.trim().toLowerCase() === \"askr-fragment=1\") ?? false\n );\n}\n\nfunction documentHeaders(response: Response): Headers {\n const headers = new Headers(response.headers);\n const type = response.headers\n .get(\"content-type\")\n ?.split(\";\")\n .map((part) => part.trim())\n .filter((part) => part.toLowerCase() !== \"askr-fragment=1\")\n .join(\"; \");\n if (type) headers.set(\"content-type\", type);\n const internalHeaders: string[] = [];\n headers.forEach((_value, key) => {\n if (key.toLowerCase().startsWith(\"x-askr-\")) internalHeaders.push(key);\n });\n for (const key of internalHeaders) headers.delete(key);\n return headers;\n}\n\nfunction composeDocumentBody(\n fragment: ReadableStream<Uint8Array> | null,\n prefix: string,\n suffix: string,\n): BodyInit {\n if (!fragment) return `${prefix}${suffix}`;\n const encoder = new TextEncoder();\n const reader = fragment.getReader();\n let prefixPending = true;\n let suffixPending = true;\n let reading = false;\n let cancelled = false;\n\n return new ReadableStream<Uint8Array>({\n pull(controller) {\n if (prefixPending) {\n prefixPending = false;\n if (prefix) controller.enqueue(encoder.encode(prefix));\n return;\n }\n if (reading) return;\n reading = true;\n void reader.read().then(\n (part) => {\n reading = false;\n if (cancelled) return;\n if (!part.done) {\n controller.enqueue(part.value);\n return;\n }\n if (suffixPending && suffix) controller.enqueue(encoder.encode(suffix));\n suffixPending = false;\n controller.close();\n },\n (error) => {\n reading = false;\n if (cancelled) return;\n controller.error(error);\n void reader.cancel(error).catch(() => undefined);\n },\n );\n },\n async cancel(reason) {\n cancelled = true;\n await reader.cancel(reason).catch(() => undefined);\n },\n });\n}\n\nexport async function composeAskrDocumentResponse(\n response: Response,\n transformedDocument: string,\n options: AskrDocumentOptions = {},\n): Promise<Response> {\n if (!isAskrFragment(response)) return response;\n const compose = () => {\n if (headMarkerCount(transformedDocument) !== 1) {\n throw new Error(\n `index.html must contain exactly one ${ASKR_HEAD_MARKER} marker; found ${headMarkerCount(transformedDocument)}.`,\n );\n }\n const withHead = composeAskrHead(\n transformedDocument,\n response.headers.get(\"x-askr-head\") ?? \"\",\n response.headers.get(\"x-askr-html-lang\") ?? undefined,\n response.headers.get(\"x-askr-html-dir\") ?? undefined,\n );\n const appCount = markerCount(withHead);\n if (appCount !== 1) {\n throw new Error(\n `index.html must contain exactly one ${ASKR_APP_MARKER} marker; found ${appCount}.`,\n );\n }\n const markerIndex = withHead.indexOf(ASKR_APP_MARKER);\n const prefix = withHead.slice(0, markerIndex);\n const suffix = withHead.slice(markerIndex + ASKR_APP_MARKER.length);\n return new Response(composeDocumentBody(response.body, prefix, suffix), {\n status: response.status,\n statusText: response.statusText,\n headers: documentHeaders(response),\n });\n };\n return options.telemetry?.viteDocument\n ? options.telemetry.viteDocument({ status: response.status }, compose)\n : compose();\n}\n\nexport function createDocumentApp(\n app: ServerApp,\n transformedDocument: string,\n options: AskrDocumentOptions = {},\n): ServerApp {\n return {\n async fetch(request) {\n return composeAskrDocumentResponse(await app.fetch(request), transformedDocument, options);\n },\n };\n}\n","import { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { ServerApp } from \"@askrjs/server\";\nimport type { ViteDevServer } from \"vite\";\nimport { composeAskrDocumentResponse, isAskrFragment } from \"./document\";\nimport type { AskrServerOptions } from \"./types\";\n\nfunction serverApp(module: Record<string, unknown>, options: AskrServerOptions): ServerApp {\n const value = module[options.exportName ?? \"app\"] ?? module.default;\n if (!value || typeof (value as ServerApp).fetch !== \"function\") {\n throw new Error(\n `@askrjs/vite: ${options.entry} must export a ServerApp as ${options.exportName ?? \"app\"} or default`,\n );\n }\n return value as ServerApp;\n}\n\nfunction moduleTelemetry(module: Record<string, unknown>) {\n const telemetry = module[\"telemetry\"];\n return telemetry && typeof telemetry === \"object\"\n ? {\n telemetry: telemetry as NonNullable<\n Parameters<typeof composeAskrDocumentResponse>[2]\n >[\"telemetry\"],\n }\n : undefined;\n}\n\nexport function createDevelopmentApp(server: ViteDevServer, options: AskrServerOptions): ServerApp {\n return {\n async fetch(request) {\n const module = await server.ssrLoadModule(options.entry);\n const response = await serverApp(module, options).fetch(request);\n if (!isAskrFragment(response)) return response;\n const file = resolve(server.config.root, options.indexHtml ?? \"index.html\");\n const document = await readFile(file, \"utf8\");\n const transformed = await server.transformIndexHtml(new URL(request.url).pathname, document);\n return composeAskrDocumentResponse(response, transformed, moduleTelemetry(module));\n },\n };\n}\n","import { createNodeHandler } from \"@askrjs/node\";\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { Plugin, ResolvedConfig } from \"vite\";\nimport { createDevelopmentApp } from \"./development\";\nimport type { AskrServerOptions } from \"./types\";\n\nexport const ASKR_SERVER_MODULE_ID = \"virtual:askr-server\";\nconst RESOLVED_SERVER_MODULE_ID = \"\\0askr:server\";\n\n/** Portable public identity for the server integration plugin. */\nexport interface AskrServerPlugin {\n readonly name: \"askr:server\";\n}\n\nexport function askrServer(options: AskrServerOptions): AskrServerPlugin {\n let config: ResolvedConfig | undefined;\n const plugin = {\n name: \"askr:server\" as const,\n config() {\n // Askr owns document routing. Vite should serve modules and assets, but\n // must not rewrite page URLs to /index.html before the Askr fallback.\n // Keep the Askr package family inside Vite's module graph so file-linked\n // peers cannot load a second runtime with a different component context.\n // Vitest derives its dependency inlining from resolve.noExternal, which\n // is also Vite 8's replacement for the deprecated ssr.noExternal option.\n return {\n appType: \"custom\",\n resolve: { noExternal: [\"@askrjs/*\"] },\n };\n },\n configResolved(resolved) {\n config = resolved;\n },\n resolveId(id) {\n return id === ASKR_SERVER_MODULE_ID ? RESOLVED_SERVER_MODULE_ID : null;\n },\n async load(id) {\n if (id !== RESOLVED_SERVER_MODULE_ID) return null;\n if (!config) throw new Error(\"@askrjs/vite: server plugin was not configured.\");\n const sourceDocument = resolve(config.root, options.indexHtml ?? \"index.html\");\n const serverOutDocument = resolve(config.root, config.build.outDir, \"index.html\");\n const clientOutDocument = resolve(config.root, config.build.outDir, \"..\", \"index.html\");\n const builtDocument = [serverOutDocument, clientOutDocument].find(existsSync);\n const document = await readFile(builtDocument ?? sourceDocument, \"utf8\");\n const entry = resolve(config.root, options.entry);\n const exportName = options.exportName ?? \"app\";\n return [\n `import * as source from ${JSON.stringify(entry)};`,\n `import { createDocumentApp } from '@askrjs/vite/server';`,\n `const sourceApp = source[${JSON.stringify(exportName)}] ?? source.default;`,\n `const sourceTelemetry = Reflect.get(source, 'telemetry');`,\n `if (!sourceApp?.fetch) throw new Error(${JSON.stringify(`@askrjs/vite: ${options.entry} must export a ServerApp`)});`,\n `export const app = createDocumentApp(sourceApp, ${JSON.stringify(document)}, sourceTelemetry ? { telemetry: sourceTelemetry } : undefined);`,\n `export default app;`,\n ].join(\"\\n\");\n },\n configureServer(server) {\n const handler = createNodeHandler(createDevelopmentApp(server, options));\n // Page rendering is the development fallback. Register it after Vite's\n // module, asset, and HTML middleware so client entries and imported CSS\n // are served by Vite instead of being mistaken for application routes.\n return () => server.middlewares.use(handler);\n },\n } satisfies Plugin;\n return plugin;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.js","names":[],"sources":["../src/server/head.ts","../src/server/document.ts","../src/server/development.ts","../src/server/plugin.ts"],"sourcesContent":["import { parseFragment, serialize, type DefaultTreeAdapterMap } from \"parse5\";\n\nconst htmlNamespace = \"http://www.w3.org/1999/xhtml\";\nconst linkAttributes = new Set([\n \"as\",\n \"crossorigin\",\n \"data-askr-head\",\n \"fetchpriority\",\n \"href\",\n \"hreflang\",\n \"imagesizes\",\n \"imagesrcset\",\n \"media\",\n \"referrerpolicy\",\n \"rel\",\n \"sizes\",\n \"type\",\n]);\n\nfunction headMarkupError(message: string): never {\n throw new TypeError(`Invalid x-askr-head metadata: ${message}`);\n}\n\nfunction attributesOf(node: DefaultTreeAdapterMap[\"element\"]): Map<string, string> {\n const attributes = new Map<string, string>();\n for (const attribute of node.attrs) {\n if (attribute.namespace || attribute.prefix)\n headMarkupError(\"namespaced attributes are not allowed.\");\n attributes.set(attribute.name.toLowerCase(), attribute.value);\n }\n return attributes;\n}\n\nfunction requireAllowedAttributes(\n attributes: Map<string, string>,\n allowed: ReadonlySet<string>,\n): void {\n for (const name of attributes.keys()) {\n if (!allowed.has(name)) headMarkupError(`attribute ${name} is not allowed.`);\n }\n if (attributes.get(\"data-askr-head\") !== \"\")\n headMarkupError(\"every metadata node must be Askr-owned.\");\n}\n\nfunction isTextNode(\n node: DefaultTreeAdapterMap[\"childNode\"],\n): node is DefaultTreeAdapterMap[\"textNode\"] {\n return \"value\" in node;\n}\n\nfunction textContent(node: DefaultTreeAdapterMap[\"element\"]): string {\n let value = \"\";\n for (const child of node.childNodes) {\n if (!isTextNode(child)) headMarkupError(`${node.tagName} must contain only text.`);\n value += child.value;\n }\n return value;\n}\n\nfunction validateHeadElement(node: DefaultTreeAdapterMap[\"element\"]): void {\n if (node.namespaceURI !== htmlNamespace) headMarkupError(\"foreign elements are not allowed.\");\n const attributes = attributesOf(node);\n if (node.tagName === \"title\") {\n requireAllowedAttributes(attributes, new Set([\"data-askr-head\"]));\n textContent(node);\n return;\n }\n if (node.tagName === \"meta\") {\n requireAllowedAttributes(\n attributes,\n new Set([\"content\", \"data-askr-head\", \"name\", \"property\"]),\n );\n const hasName = attributes.has(\"name\");\n const hasProperty = attributes.has(\"property\");\n if (hasName === hasProperty || !attributes.has(\"content\"))\n headMarkupError(\"meta requires content and exactly one of name or property.\");\n if (node.childNodes.length) headMarkupError(\"meta cannot contain child nodes.\");\n return;\n }\n if (node.tagName === \"link\") {\n requireAllowedAttributes(attributes, linkAttributes);\n if (!attributes.has(\"rel\") || !attributes.has(\"href\"))\n headMarkupError(\"link requires rel and href.\");\n if (node.childNodes.length) headMarkupError(\"link cannot contain child nodes.\");\n return;\n }\n if (node.tagName === \"script\") {\n requireAllowedAttributes(attributes, new Set([\"data-askr-head\", \"type\"]));\n if (attributes.get(\"type\") !== \"application/ld+json\")\n headMarkupError(\"only application/ld+json script nodes are allowed.\");\n const json = textContent(node);\n try {\n JSON.parse(json);\n } catch {\n headMarkupError(\"JSON-LD content must be valid JSON.\");\n }\n return;\n }\n headMarkupError(`element ${node.tagName} is not allowed.`);\n}\n\nexport function normalizeAskrHead(head: string): string {\n const fragment = parseFragment(head);\n for (const node of fragment.childNodes) {\n if (isTextNode(node)) {\n if (node.value.trim()) headMarkupError(\"top-level text is not allowed.\");\n continue;\n }\n if (!(\"tagName\" in node)) headMarkupError(\"non-element nodes are not allowed.\");\n validateHeadElement(node);\n }\n return serialize(fragment);\n}\n","import type { ServerApp } from \"@askrjs/server\";\nimport { normalizeAskrHead } from \"./head\";\n\ninterface ViteTelemetryFields {\n status?: number;\n}\n\ninterface ViteTelemetry {\n viteDocument?<T>(fields: ViteTelemetryFields, work: () => T): T;\n}\n\nexport interface AskrDocumentOptions {\n telemetry?: ViteTelemetry;\n}\n\nexport const ASKR_APP_MARKER = \"<!--askr-app-->\";\nexport const ASKR_HEAD_MARKER = \"<!--askr-head-->\";\n\nfunction markerCount(document: string): number {\n return document.split(ASKR_APP_MARKER).length - 1;\n}\n\nfunction headMarkerCount(document: string): number {\n return document.split(ASKR_HEAD_MARKER).length - 1;\n}\n\nfunction escapeAttribute(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nfunction patchHtmlAttribute(document: string, name: \"lang\" | \"dir\", value: string): string {\n return document.replace(/<html\\b([^>]*)>/i, (_match, attributes: string) => {\n const matcher = new RegExp(\n `\\\\s+${name}(?:\\\\s*=\\\\s*(?:\"[^\"]*\"|'[^']*'|[^\\\\s\"'=<>\\u0060]+))?`,\n \"gi\",\n );\n const withoutExisting = attributes.replace(matcher, \"\");\n return `<html${withoutExisting} ${name}=\"${escapeAttribute(value)}\">`;\n });\n}\n\nexport function insertAskrFragment(document: string, fragment: string): string {\n const count = markerCount(document);\n if (count !== 1) {\n throw new Error(\n `index.html must contain exactly one ${ASKR_APP_MARKER} marker; found ${count}.`,\n );\n }\n return document.replace(ASKR_APP_MARKER, fragment);\n}\n\nexport function composeAskrHead(\n document: string,\n head: string,\n lang?: string,\n dir?: string,\n): string {\n let output = document.replace(ASKR_HEAD_MARKER, normalizeAskrHead(head));\n if (lang) output = patchHtmlAttribute(output, \"lang\", lang);\n if (dir) output = patchHtmlAttribute(output, \"dir\", dir);\n return output;\n}\n\nexport function isAskrFragment(response: Response): boolean {\n return (\n response.headers\n .get(\"content-type\")\n ?.split(\";\")\n .some((part) => part.trim().toLowerCase() === \"askr-fragment=1\") ?? false\n );\n}\n\nfunction documentHeaders(response: Response): Headers {\n const headers = new Headers(response.headers);\n const type = response.headers\n .get(\"content-type\")\n ?.split(\";\")\n .map((part) => part.trim())\n .filter((part) => part.toLowerCase() !== \"askr-fragment=1\")\n .join(\"; \");\n if (type) headers.set(\"content-type\", type);\n for (const name of [\n \"accept-ranges\",\n \"content-digest\",\n \"content-encoding\",\n \"content-length\",\n \"content-md5\",\n \"content-range\",\n \"digest\",\n \"etag\",\n \"last-modified\",\n \"repr-digest\",\n \"trailer\",\n ]) {\n headers.delete(name);\n }\n const internalHeaders: string[] = [];\n headers.forEach((_value, key) => {\n if (key.toLowerCase().startsWith(\"x-askr-\")) internalHeaders.push(key);\n });\n for (const key of internalHeaders) headers.delete(key);\n return headers;\n}\n\nfunction composeDocumentBody(\n fragment: ReadableStream<Uint8Array> | null,\n prefix: string,\n suffix: string,\n): BodyInit {\n if (!fragment) return `${prefix}${suffix}`;\n const encoder = new TextEncoder();\n const reader = fragment.getReader();\n let prefixPending = true;\n let suffixPending = true;\n let reading = false;\n let cancelled = false;\n\n return new ReadableStream<Uint8Array>({\n pull(controller) {\n if (prefixPending) {\n prefixPending = false;\n if (prefix) controller.enqueue(encoder.encode(prefix));\n return;\n }\n if (reading) return;\n reading = true;\n void reader.read().then(\n (part) => {\n reading = false;\n if (cancelled) return;\n if (!part.done) {\n controller.enqueue(part.value);\n return;\n }\n if (suffixPending && suffix) controller.enqueue(encoder.encode(suffix));\n suffixPending = false;\n controller.close();\n },\n (error) => {\n reading = false;\n if (cancelled) return;\n controller.error(error);\n void reader.cancel(error).catch(() => undefined);\n },\n );\n },\n async cancel(reason) {\n cancelled = true;\n await reader.cancel(reason).catch(() => undefined);\n },\n });\n}\n\nexport async function composeAskrDocumentResponse(\n response: Response,\n transformedDocument: string,\n options: AskrDocumentOptions = {},\n): Promise<Response> {\n if (!isAskrFragment(response)) return response;\n const compose = () => {\n if (headMarkerCount(transformedDocument) !== 1) {\n throw new Error(\n `index.html must contain exactly one ${ASKR_HEAD_MARKER} marker; found ${headMarkerCount(transformedDocument)}.`,\n );\n }\n const withHead = composeAskrHead(\n transformedDocument,\n response.headers.get(\"x-askr-head\") ?? \"\",\n response.headers.get(\"x-askr-html-lang\") ?? undefined,\n response.headers.get(\"x-askr-html-dir\") ?? undefined,\n );\n const appCount = markerCount(withHead);\n if (appCount !== 1) {\n throw new Error(\n `index.html must contain exactly one ${ASKR_APP_MARKER} marker; found ${appCount}.`,\n );\n }\n const markerIndex = withHead.indexOf(ASKR_APP_MARKER);\n const prefix = withHead.slice(0, markerIndex);\n const suffix = withHead.slice(markerIndex + ASKR_APP_MARKER.length);\n return new Response(composeDocumentBody(response.body, prefix, suffix), {\n status: response.status,\n statusText: response.statusText,\n headers: documentHeaders(response),\n });\n };\n return options.telemetry?.viteDocument\n ? options.telemetry.viteDocument({ status: response.status }, compose)\n : compose();\n}\n\nexport function createDocumentApp(\n app: ServerApp,\n transformedDocument: string,\n options: AskrDocumentOptions = {},\n): ServerApp {\n return {\n async fetch(request) {\n return composeAskrDocumentResponse(await app.fetch(request), transformedDocument, options);\n },\n };\n}\n","import { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { ServerApp } from \"@askrjs/server\";\nimport type { ViteDevServer } from \"vite\";\nimport { composeAskrDocumentResponse, isAskrFragment } from \"./document\";\nimport type { AskrServerOptions } from \"./types\";\n\nfunction serverApp(module: Record<string, unknown>, options: AskrServerOptions): ServerApp {\n const value = module[options.exportName ?? \"app\"] ?? module.default;\n if (!value || typeof (value as ServerApp).fetch !== \"function\") {\n throw new Error(\n `@askrjs/vite: ${options.entry} must export a ServerApp as ${options.exportName ?? \"app\"} or default`,\n );\n }\n return value as ServerApp;\n}\n\nfunction moduleTelemetry(module: Record<string, unknown>) {\n const telemetry = module[\"telemetry\"];\n return telemetry && typeof telemetry === \"object\"\n ? {\n telemetry: telemetry as NonNullable<\n Parameters<typeof composeAskrDocumentResponse>[2]\n >[\"telemetry\"],\n }\n : undefined;\n}\n\nexport function createDevelopmentApp(server: ViteDevServer, options: AskrServerOptions): ServerApp {\n return {\n async fetch(request) {\n const module = await server.ssrLoadModule(options.entry);\n const response = await serverApp(module, options).fetch(request);\n if (!isAskrFragment(response)) return response;\n const file = resolve(server.config.root, options.indexHtml ?? \"index.html\");\n const document = await readFile(file, \"utf8\");\n const transformed = await server.transformIndexHtml(new URL(request.url).pathname, document);\n return composeAskrDocumentResponse(response, transformed, moduleTelemetry(module));\n },\n };\n}\n","import { createNodeHandler } from \"@askrjs/node\";\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { Plugin, ResolvedConfig, ViteDevServer } from \"vite\";\nimport { createDevelopmentApp } from \"./development\";\nimport type { AskrServerOptions } from \"./types\";\n\nexport const ASKR_SERVER_MODULE_ID = \"virtual:askr-server\";\nconst RESOLVED_SERVER_MODULE_ID = \"\\0askr:server\";\n\n/** Portable public identity for the server integration plugin. */\nexport interface AskrServerPlugin {\n readonly name: \"askr:server\";\n}\n\nfunction createNodeHandlerOptions(server: ViteDevServer): {\n baseUrl?: string;\n allowedHosts?: readonly string[];\n} {\n const serverConfig = server.config?.server;\n const baseUrl = serverConfig?.origin;\n\n if (\n serverConfig?.allowedHosts &&\n serverConfig.allowedHosts !== true &&\n serverConfig.allowedHosts.length > 0\n ) {\n return { baseUrl, allowedHosts: serverConfig.allowedHosts };\n }\n\n if (baseUrl) {\n return { baseUrl };\n }\n\n const host = typeof serverConfig?.host === \"string\" ? serverConfig.host : \"localhost\";\n const allowedHosts = new Set([host, \"localhost\", \"127.0.0.1\", \"[::1]\"]);\n return { allowedHosts: Array.from(allowedHosts) };\n}\n\nexport function askrServer(options: AskrServerOptions): AskrServerPlugin {\n let config: ResolvedConfig | undefined;\n const plugin = {\n name: \"askr:server\" as const,\n config() {\n // Askr owns document routing. Vite should serve modules and assets, but\n // must not rewrite page URLs to /index.html before the Askr fallback.\n // Keep the Askr package family inside Vite's module graph so file-linked\n // peers cannot load a second runtime with a different component context.\n // Vitest derives its dependency inlining from resolve.noExternal, which\n // is also Vite 8's replacement for the deprecated ssr.noExternal option.\n return {\n appType: \"custom\",\n resolve: { noExternal: [\"@askrjs/*\"] },\n };\n },\n configResolved(resolved) {\n config = resolved;\n },\n resolveId(id) {\n return id === ASKR_SERVER_MODULE_ID ? RESOLVED_SERVER_MODULE_ID : null;\n },\n async load(id) {\n if (id !== RESOLVED_SERVER_MODULE_ID) return null;\n if (!config) throw new Error(\"@askrjs/vite: server plugin was not configured.\");\n const sourceDocument = resolve(config.root, options.indexHtml ?? \"index.html\");\n const serverOutDocument = resolve(config.root, config.build.outDir, \"index.html\");\n const clientOutDocument = resolve(config.root, config.build.outDir, \"..\", \"index.html\");\n const builtDocument = [serverOutDocument, clientOutDocument].find(existsSync);\n const document = await readFile(builtDocument ?? sourceDocument, \"utf8\");\n const entry = resolve(config.root, options.entry);\n const exportName = options.exportName ?? \"app\";\n return [\n `import * as source from ${JSON.stringify(entry)};`,\n `import { createDocumentApp } from '@askrjs/vite/server';`,\n `const sourceApp = source[${JSON.stringify(exportName)}] ?? source.default;`,\n `const sourceTelemetry = Reflect.get(source, 'telemetry');`,\n `if (!sourceApp?.fetch) throw new Error(${JSON.stringify(`@askrjs/vite: ${options.entry} must export a ServerApp`)});`,\n `export const app = createDocumentApp(sourceApp, ${JSON.stringify(document)}, sourceTelemetry ? { telemetry: sourceTelemetry } : undefined);`,\n `export default app;`,\n ].join(\"\\n\");\n },\n configureServer(server) {\n const handler = createNodeHandler(\n createDevelopmentApp(server, options),\n createNodeHandlerOptions(server),\n );\n // Page rendering is the development fallback. Register it after Vite's\n // module, asset, and HTML middleware so client entries and imported CSS\n // are served by Vite instead of being mistaken for application routes.\n return () => server.middlewares.use(handler);\n },\n } satisfies Plugin;\n return plugin;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askrjs/vite",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Vite plugin for Askr JSX and template transforms",
5
5
  "homepage": "https://github.com/askrjs/askr-vite#readme",
6
6
  "bugs": {
@@ -39,10 +39,8 @@
39
39
  "scripts": {
40
40
  "build": "vp pack",
41
41
  "dev": "vp pack --watch",
42
- "test": "npm run test:unit && npm run test:jsdom && npm run test:browser",
42
+ "test": "npm run test:unit",
43
43
  "test:unit": "vp test run -c vitest.config.ts",
44
- "test:jsdom": "npm run test:unit",
45
- "test:browser": "npm run test:unit",
46
44
  "test:publint": "publint",
47
45
  "fmt": "vp fmt .",
48
46
  "lint": "vp lint .",
@@ -53,7 +51,8 @@
53
51
  "prepublishOnly": "npm run check"
54
52
  },
55
53
  "dependencies": {
56
- "@askrjs/node": ">=0.0.1 <0.1.0"
54
+ "@askrjs/node": ">=0.0.1 <0.1.0",
55
+ "parse5": "^7.3.0"
57
56
  },
58
57
  "devDependencies": {
59
58
  "@askrjs/askr": ">=0.0.52 <0.1.0",
@@ -62,7 +61,8 @@
62
61
  "publint": "^0.3.21",
63
62
  "typescript": "^6.0.3",
64
63
  "vite": "^8.0.16",
65
- "vite-plus": "^0.2.4"
64
+ "vite-plus": "^0.2.4",
65
+ "vitest": "^4.1.10"
66
66
  },
67
67
  "peerDependencies": {
68
68
  "@askrjs/askr": ">=0.0.52 <0.1.0",