@askrjs/vite 0.4.0 → 0.4.2

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
@@ -11,12 +11,30 @@ framework's JSX and template conventions inside a normal Vite app.
11
11
  ## Install
12
12
 
13
13
  ```bash
14
- npm install -D @askrjs/vite vite
14
+ npm install -D @askrjs/vite vite@8.2.2
15
15
  # or
16
- npm install -D @askrjs/vite vite-plus
16
+ npm install -D @askrjs/vite vite-plus@1.1.0 vite@npm:@voidzero-dev/vite-plus-core@1.1.0
17
17
  npm install @askrjs/askr
18
18
  ```
19
19
 
20
+ Use `@askrjs/vite@0.4.2` or newer for these peer ranges. The
21
+ [minimal Vite+ starter](examples/vite-plus/README.md) uses a packed artifact from
22
+ this checkout.
23
+
24
+ The installed-package browser suite qualifies this matrix:
25
+
26
+ | Toolchain | `vite` dependency | Configuration import |
27
+ | ----------- | ---------------------------------------- | -------------------- |
28
+ | Vite 8.2.2 | `vite@8.2.2` | `vite` |
29
+ | Vite+ 0.3.1 | `npm:@voidzero-dev/vite-plus-core@0.3.1` | `vite-plus` |
30
+ | Vite+ 1.1.0 | `npm:@voidzero-dev/vite-plus-core@1.1.0` | `vite-plus` |
31
+
32
+ Each consumer installs normally with matching peers and exercises JSX, browser
33
+ HMR without a document reload, server document composition, production client
34
+ and server builds, and responsive-image generation. Image declarations require
35
+ a production build; image processing during development is not supported.
36
+ The low versions in the `vite` peer range describe Vite+ core aliases.
37
+
20
38
  ## Use
21
39
 
22
40
  ```ts
@@ -29,6 +47,8 @@ export default defineConfig({
29
47
  });
30
48
  ```
31
49
 
50
+ For Vite+, import `defineConfig` from `vite-plus` and use `vp dev` and `vp build`.
51
+
32
52
  `askr({ optimizeTemplates: true })` optionally hoists repeated static
33
53
  `class`, `className`, and `style` literals from parsed JSX-runtime property
34
54
  nodes. The optimizer never scans or rewrites unrelated strings, template
package/dist/server.js CHANGED
@@ -6,6 +6,7 @@ import { parseFragment, serialize } from "parse5";
6
6
  import { createNodeHandler } from "@askrjs/node";
7
7
  //#region src/server/head.ts
8
8
  const htmlNamespace = "http://www.w3.org/1999/xhtml";
9
+ const metadataEncoding = "base64url-v1";
9
10
  const linkAttributes = /* @__PURE__ */ new Set([
10
11
  "as",
11
12
  "crossorigin",
@@ -97,6 +98,16 @@ function normalizeAskrHead(head) {
97
98
  }
98
99
  return serialize(fragment);
99
100
  }
101
+ function decodeAskrHeaderText(value, encoding) {
102
+ if (encoding === null) return value;
103
+ if (encoding !== metadataEncoding) throw new TypeError(`Unsupported Askr metadata encoding: ${encoding}`);
104
+ const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
105
+ const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
106
+ const binary = atob(padded);
107
+ const bytes = new Uint8Array(binary.length);
108
+ for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
109
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
110
+ }
100
111
  //#endregion
101
112
  //#region src/server/document.ts
102
113
  /** Placeholder marker in `index.html` replaced with the rendered app fragment. */
@@ -126,14 +137,15 @@ function patchHtmlAttribute(document, name, value) {
126
137
  function insertAskrFragment(document, fragment) {
127
138
  const count = markerCount(document);
128
139
  if (count !== 1) throw new Error(`index.html must contain exactly one ${ASKR_APP_MARKER} marker; found ${count}.`);
129
- return document.replace(ASKR_APP_MARKER, fragment);
140
+ return document.replace(ASKR_APP_MARKER, () => fragment);
130
141
  }
131
142
  /**
132
143
  * Replace the {@link ASKR_HEAD_MARKER} in `document` with normalized `head`
133
144
  * markup, and patch the `<html>` tag's `lang`/`dir` attributes when given.
134
145
  */
135
146
  function composeAskrHead(document, head, lang, dir) {
136
- let output = document.replace(ASKR_HEAD_MARKER, normalizeAskrHead(head));
147
+ const normalizedHead = normalizeAskrHead(head);
148
+ let output = document.replace(ASKR_HEAD_MARKER, () => normalizedHead);
137
149
  if (lang) output = patchHtmlAttribute(output, "lang", lang);
138
150
  if (dir) output = patchHtmlAttribute(output, "dir", dir);
139
151
  return output;
@@ -223,7 +235,7 @@ async function composeAskrDocumentResponse(response, transformedDocument, option
223
235
  if (!isAskrFragment(response)) return response;
224
236
  const compose = () => {
225
237
  if (headMarkerCount(transformedDocument) !== 1) throw new Error(`index.html must contain exactly one ${ASKR_HEAD_MARKER} marker; found ${headMarkerCount(transformedDocument)}.`);
226
- const withHead = composeAskrHead(transformedDocument, response.headers.get("x-askr-head") ?? "", response.headers.get("x-askr-html-lang") ?? void 0, response.headers.get("x-askr-html-dir") ?? void 0);
238
+ const withHead = composeAskrHead(transformedDocument, decodeAskrHeaderText(response.headers.get("x-askr-head") ?? "", response.headers.get("x-askr-encoding")), response.headers.has("x-askr-html-lang") ? decodeAskrHeaderText(response.headers.get("x-askr-html-lang"), response.headers.get("x-askr-encoding")) : void 0, response.headers.has("x-askr-html-dir") ? decodeAskrHeaderText(response.headers.get("x-askr-html-dir"), response.headers.get("x-askr-encoding")) : void 0);
227
239
  const appCount = markerCount(withHead);
228
240
  if (appCount !== 1) throw new Error(`index.html must contain exactly one ${ASKR_APP_MARKER} marker; found ${appCount}.`);
229
241
  const markerIndex = withHead.indexOf(ASKR_APP_MARKER);
@@ -1 +1 @@
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\n/** Options for composing a rendered Askr fragment into the HTML document. */\nexport interface AskrDocumentOptions {\n telemetry?: ViteTelemetry;\n}\n\n/** Placeholder marker in `index.html` replaced with the rendered app fragment. */\nexport const ASKR_APP_MARKER = \"<!--askr-app-->\";\n/** Placeholder marker in `index.html` replaced with rendered `<head>` content. */\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\n/**\n * Replace the single {@link ASKR_APP_MARKER} in `document` with `fragment`.\n *\n * @throws If `document` does not contain exactly one app marker.\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\n/**\n * Replace the {@link ASKR_HEAD_MARKER} in `document` with normalized `head`\n * markup, and patch the `<html>` tag's `lang`/`dir` attributes when given.\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\n/** Whether `response` carries the `askr-fragment=1` content-type marker. */\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 let finished = 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 || finished) 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 const finalSuffix = suffixPending ? suffix : \"\";\n finished = true;\n suffixPending = false;\n if (finalSuffix) controller.enqueue(encoder.encode(finalSuffix));\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\n/**\n * Compose an Askr fragment response into the full `transformedDocument` HTML,\n * streaming the fragment's body between the document prefix and suffix.\n * Non-fragment responses are returned unchanged.\n *\n * @param response The upstream response, expected to be an Askr fragment.\n * @param transformedDocument The `index.html` document with app/head markers.\n * @param options Optional telemetry hook wrapping the compose work.\n * @returns The composed full-document `Response`, or `response` unchanged.\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\n/**\n * Wrap `app` so every response is composed into `transformedDocument` via\n * {@link composeAskrDocumentResponse}.\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\n/** Virtual module id that resolves to the generated server document app. */\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\n/**\n * Create the Vite plugin that serves Askr's server-rendered document during\n * development and generates the {@link ASKR_SERVER_MODULE_ID} virtual module\n * that composes the built server app with the HTML document.\n *\n * @param options Server entry and document configuration.\n * @returns A Vite-compatible plugin object exposing the `askr:server` name.\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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"}
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 metadataEncoding = \"base64url-v1\";\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\nexport function decodeAskrHeaderText(value: string, encoding: string | null): string {\n if (encoding === null) return value;\n if (encoding !== metadataEncoding)\n throw new TypeError(`Unsupported Askr metadata encoding: ${encoding}`);\n\n const base64 = value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded = base64 + \"=\".repeat((4 - (base64.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n}\n","import type { ServerApp } from \"@askrjs/server\";\nimport { decodeAskrHeaderText, normalizeAskrHead } from \"./head\";\n\ninterface ViteTelemetryFields {\n status?: number;\n}\n\ninterface ViteTelemetry {\n viteDocument?<T>(fields: ViteTelemetryFields, work: () => T): T;\n}\n\n/** Options for composing a rendered Askr fragment into the HTML document. */\nexport interface AskrDocumentOptions {\n telemetry?: ViteTelemetry;\n}\n\n/** Placeholder marker in `index.html` replaced with the rendered app fragment. */\nexport const ASKR_APP_MARKER = \"<!--askr-app-->\";\n/** Placeholder marker in `index.html` replaced with rendered `<head>` content. */\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\n/**\n * Replace the single {@link ASKR_APP_MARKER} in `document` with `fragment`.\n *\n * @throws If `document` does not contain exactly one app marker.\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\n/**\n * Replace the {@link ASKR_HEAD_MARKER} in `document` with normalized `head`\n * markup, and patch the `<html>` tag's `lang`/`dir` attributes when given.\n */\nexport function composeAskrHead(\n document: string,\n head: string,\n lang?: string,\n dir?: string,\n): string {\n const normalizedHead = normalizeAskrHead(head);\n let output = document.replace(ASKR_HEAD_MARKER, () => normalizedHead);\n if (lang) output = patchHtmlAttribute(output, \"lang\", lang);\n if (dir) output = patchHtmlAttribute(output, \"dir\", dir);\n return output;\n}\n\n/** Whether `response` carries the `askr-fragment=1` content-type marker. */\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 let finished = 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 || finished) 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 const finalSuffix = suffixPending ? suffix : \"\";\n finished = true;\n suffixPending = false;\n if (finalSuffix) controller.enqueue(encoder.encode(finalSuffix));\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\n/**\n * Compose an Askr fragment response into the full `transformedDocument` HTML,\n * streaming the fragment's body between the document prefix and suffix.\n * Non-fragment responses are returned unchanged.\n *\n * @param response The upstream response, expected to be an Askr fragment.\n * @param transformedDocument The `index.html` document with app/head markers.\n * @param options Optional telemetry hook wrapping the compose work.\n * @returns The composed full-document `Response`, or `response` unchanged.\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 decodeAskrHeaderText(\n response.headers.get(\"x-askr-head\") ?? \"\",\n response.headers.get(\"x-askr-encoding\"),\n ),\n response.headers.has(\"x-askr-html-lang\")\n ? decodeAskrHeaderText(\n response.headers.get(\"x-askr-html-lang\")!,\n response.headers.get(\"x-askr-encoding\"),\n )\n : undefined,\n response.headers.has(\"x-askr-html-dir\")\n ? decodeAskrHeaderText(\n response.headers.get(\"x-askr-html-dir\")!,\n response.headers.get(\"x-askr-encoding\"),\n )\n : 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\n/**\n * Wrap `app` so every response is composed into `transformedDocument` via\n * {@link composeAskrDocumentResponse}.\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\n/** Virtual module id that resolves to the generated server document app. */\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\n/**\n * Create the Vite plugin that serves Askr's server-rendered document during\n * development and generates the {@link ASKR_SERVER_MODULE_ID} virtual module\n * that composes the built server app with the HTML document.\n *\n * @param options Server entry and document configuration.\n * @returns A Vite-compatible plugin object exposing the `askr:server` name.\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.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Vite plugin for Askr JSX and template transforms",
5
5
  "homepage": "https://github.com/askrjs/askr-vite#readme",
6
6
  "bugs": {
@@ -47,8 +47,9 @@
47
47
  "scripts": {
48
48
  "build": "vp pack",
49
49
  "dev": "vp pack --watch",
50
- "test": "npm run test:unit",
50
+ "test": "npm run test:unit && npm run test:browser",
51
51
  "test:unit": "vp test run -c vitest.config.ts",
52
+ "test:browser": "npm run build && playwright test",
52
53
  "test:publint": "publint",
53
54
  "fmt": "vp fmt .",
54
55
  "lint": "vp lint .",
@@ -66,6 +67,7 @@
66
67
  "devDependencies": {
67
68
  "@askrjs/askr": ">=0.4.0 <0.5.0",
68
69
  "@askrjs/server": ">=0.4.0 <0.5.0",
70
+ "@playwright/test": "1.63.0",
69
71
  "@types/node": "^26.3.0",
70
72
  "publint": "^0.3.24",
71
73
  "sharp": "^0.35.3",
@@ -77,8 +79,8 @@
77
79
  "peerDependencies": {
78
80
  "@askrjs/askr": ">=0.4.0 <0.5.0",
79
81
  "sharp": "^0.35.3",
80
- "vite": "^8.2.2",
81
- "vite-plus": "^0.3.1"
82
+ "vite": "^8.2.2 || ^0.3.1 || ^1.1.0",
83
+ "vite-plus": "^0.3.1 || ^1.1.0"
82
84
  },
83
85
  "peerDependenciesMeta": {
84
86
  "sharp": {