@askrjs/vite 0.0.5 → 0.0.7

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
@@ -17,12 +17,35 @@ npm install @askrjs/askr
17
17
  ```ts
18
18
  import { defineConfig } from "vite";
19
19
  import { askr } from "@askrjs/vite";
20
+ import { askrServer } from "@askrjs/vite/server";
20
21
 
21
22
  export default defineConfig({
22
- plugins: [askr()],
23
+ plugins: [askr(), askrServer({ entry: "./src/server/entry-server.ts" })],
23
24
  });
24
25
  ```
25
26
 
27
+ ## Document ownership
28
+
29
+ Vite is the sole owner of the HTML document. A server-rendered template must
30
+ contain exactly one head marker and one app marker:
31
+
32
+ ```html
33
+ <head>
34
+ <meta charset="UTF-8" />
35
+ <!--askr-head-->
36
+ </head>
37
+ <body>
38
+ <div id="app"><!--askr-app--></div>
39
+ </body>
40
+ ```
41
+
42
+ The server plugin validates both markers. It preserves application-authored
43
+ head content, injects only normalized Askr-owned title, meta, link, and JSON-LD
44
+ nodes at the head marker, patches the existing `html` language and direction,
45
+ and composes the app response between the template prefix and suffix without
46
+ buffering the full Web stream. Internal coordination headers are not sent to
47
+ the browser.
48
+
26
49
  ## When To Use It
27
50
 
28
51
  - In `vite.config.ts` for any app that uses `@askrjs/askr`
package/dist/index.d.ts CHANGED
@@ -1,13 +1,20 @@
1
- import { Plugin } from "vite";
2
1
 
3
- //#region src/index.d.ts
2
+ //#region src/jsx-plugin.d.ts
4
3
  interface AskrVitePluginOptions {
5
4
  transformJsx?: boolean;
6
5
  optimizeTemplates?: boolean;
7
6
  ssrPrecompile?: boolean;
8
7
  }
9
- declare function askrVitePlugin(opts?: AskrVitePluginOptions): Plugin;
10
- declare const askr: typeof askrVitePlugin;
8
+ /**
9
+ * Portable public plugin identity.
10
+ *
11
+ * Vite-compatible runners only require the plugin name. Keeping versioned Vite
12
+ * hook types out of the installed declaration prevents consumers such as
13
+ * vite-plus from comparing two distinct copies of Vite's recursive Plugin type.
14
+ */
15
+ interface AskrVitePlugin {
16
+ readonly name: "askr:vite";
17
+ }
18
+ declare function askrVitePlugin(options?: AskrVitePluginOptions): AskrVitePlugin;
11
19
  //#endregion
12
- export { AskrVitePluginOptions, askr, askrVitePlugin, askrVitePlugin as default };
13
- //# sourceMappingURL=index.d.ts.map
20
+ export { type AskrVitePlugin, type AskrVitePluginOptions, askrVitePlugin as askr, askrVitePlugin, askrVitePlugin as default };
package/dist/index.js CHANGED
@@ -1,14 +1,33 @@
1
+
1
2
  import { createRequire } from "node:module";
2
3
  import { transformWithOxc } from "vite";
3
- //#region src/index.ts
4
+ //#region src/template-optimizer.ts
5
+ function optimizeTemplateOutput(code) {
6
+ const hoists = /* @__PURE__ */ new Map();
7
+ let hoistIndex = 0;
8
+ const optimized = code.replace(/\b(class|className|style):\s*("([^"\\]|\\.)*")/g, (fullMatch, key, literal) => {
9
+ const cacheKey = `${key}:${literal}`;
10
+ let identifier = hoists.get(cacheKey);
11
+ if (!identifier) {
12
+ if (code.split(fullMatch).length - 1 < 2) return fullMatch;
13
+ identifier = `__askrStaticLiteral${hoistIndex++}`;
14
+ hoists.set(cacheKey, identifier);
15
+ }
16
+ return `${key}: ${identifier}`;
17
+ });
18
+ if (hoists.size === 0) return code;
19
+ return `${Array.from(hoists.entries()).map(([cacheKey, name]) => {
20
+ return `const ${name} = ${cacheKey.slice(cacheKey.indexOf(":") + 1)};`;
21
+ }).join("\n")}\n${optimized}`;
22
+ }
23
+ //#endregion
24
+ //#region src/jsx-plugin.ts
4
25
  const require = createRequire(import.meta.url);
5
- function askrVitePlugin(opts = {}) {
6
- const pluginName = "askr:vite";
7
- const shouldTransform = opts.transformJsx ?? true;
8
- const shouldOptimizeTemplates = opts.optimizeTemplates ?? false;
9
- const dedupePackages = ["@askrjs/askr"];
26
+ function askrVitePlugin(options = {}) {
27
+ const shouldTransform = options.transformJsx ?? true;
28
+ const shouldOptimizeTemplates = options.optimizeTemplates ?? false;
10
29
  return {
11
- name: pluginName,
30
+ name: "askr:vite",
12
31
  enforce: "pre",
13
32
  config() {
14
33
  let askrEntry = null;
@@ -24,7 +43,7 @@ function askrVitePlugin(opts = {}) {
24
43
  find: "@askrjs/askr",
25
44
  replacement: askrEntry
26
45
  }] : [],
27
- dedupe: dedupePackages
46
+ dedupe: ["@askrjs/askr"]
28
47
  },
29
48
  optimizeDeps: { include: [
30
49
  "@askrjs/askr",
@@ -53,9 +72,7 @@ function askrVitePlugin(opts = {}) {
53
72
  };
54
73
  },
55
74
  async transform(code, id) {
56
- if (!shouldTransform) return null;
57
- if (!/\.(jsx|tsx)$/.test(id)) return null;
58
- if (id.includes("node_modules")) return null;
75
+ if (!shouldTransform || !/\.(jsx|tsx)$/.test(id) || id.includes("node_modules")) return null;
59
76
  try {
60
77
  const result = await transformWithOxc(code, id, {
61
78
  lang: id.endsWith(".tsx") ? "tsx" : "jsx",
@@ -65,7 +82,7 @@ function askrVitePlugin(opts = {}) {
65
82
  },
66
83
  sourcemap: true
67
84
  });
68
- if (!result || !result.code) return null;
85
+ if (!result?.code) return null;
69
86
  return {
70
87
  code: shouldOptimizeTemplates ? optimizeTemplateOutput(result.code) : result.code,
71
88
  map: result.map
@@ -76,26 +93,5 @@ function askrVitePlugin(opts = {}) {
76
93
  }
77
94
  };
78
95
  }
79
- function optimizeTemplateOutput(code) {
80
- const hoists = /* @__PURE__ */ new Map();
81
- let hoistIndex = 0;
82
- const optimized = code.replace(/\b(class|className|style):\s*("([^"\\]|\\.)*")/g, (fullMatch, key, literal) => {
83
- const cacheKey = `${key}:${literal}`;
84
- let identifier = hoists.get(cacheKey);
85
- if (!identifier) {
86
- if (code.split(fullMatch).length - 1 < 2) return fullMatch;
87
- identifier = `__askrStaticLiteral${hoistIndex++}`;
88
- hoists.set(cacheKey, identifier);
89
- }
90
- return `${key}: ${identifier}`;
91
- });
92
- if (hoists.size === 0) return code;
93
- return `${Array.from(hoists.entries()).map(([cacheKey, name]) => {
94
- return `const ${name} = ${cacheKey.slice(cacheKey.indexOf(":") + 1)};`;
95
- }).join("\n")}\n${optimized}`;
96
- }
97
- const askr = askrVitePlugin;
98
96
  //#endregion
99
- export { askr, askrVitePlugin, askrVitePlugin as default };
100
-
101
- //# sourceMappingURL=index.js.map
97
+ export { askrVitePlugin as askr, askrVitePlugin, askrVitePlugin as default };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { transformWithOxc, type Plugin } from \"vite\";\n\nconst require = createRequire(import.meta.url);\n\nexport interface AskrVitePluginOptions {\n transformJsx?: boolean;\n optimizeTemplates?: boolean;\n ssrPrecompile?: boolean;\n}\n\nexport function askrVitePlugin(opts: AskrVitePluginOptions = {}): Plugin {\n const pluginName = \"askr:vite\";\n const shouldTransform = opts.transformJsx ?? true;\n const shouldOptimizeTemplates = opts.optimizeTemplates ?? false;\n const dedupePackages = [\"@askrjs/askr\"];\n\n return {\n name: pluginName,\n enforce: \"pre\",\n config() {\n let askrEntry: string | null = null;\n try {\n askrEntry = require.resolve(\"@askrjs/askr\");\n } catch {\n askrEntry = null;\n }\n\n return {\n define: {\n __ASKR_OPTIMIZE_TEMPLATES__: JSON.stringify(shouldOptimizeTemplates),\n },\n resolve: {\n alias: askrEntry\n ? [\n {\n find: \"@askrjs/askr\",\n replacement: askrEntry,\n },\n ]\n : [],\n dedupe: dedupePackages,\n },\n optimizeDeps: {\n include: [\n \"@askrjs/askr\",\n \"@askrjs/askr/router\",\n \"@askrjs/askr/boot\",\n \"@askrjs/askr/control\",\n \"@askrjs/askr/fx\",\n \"@askrjs/askr/resources\",\n \"@askrjs/askr/data\",\n \"@askrjs/askr/foundations\",\n \"@askrjs/askr/foundations/icon\",\n \"@askrjs/askr/foundations/interactions\",\n \"@askrjs/askr/foundations/state\",\n \"@askrjs/askr/foundations/structures\",\n \"@askrjs/askr/foundations/utilities\",\n \"@askrjs/askr/jsx-runtime\",\n \"@askrjs/askr/jsx-dev-runtime\",\n ],\n },\n oxc: {\n jsx: {\n runtime: \"automatic\",\n importSource: \"@askrjs/askr\",\n },\n jsxInject: \"import { jsx, jsxs, Fragment } from '@askrjs/askr/jsx-runtime';\",\n },\n };\n },\n\n async transform(code: string, id: string) {\n if (!shouldTransform) return null;\n if (!/\\.(jsx|tsx)$/.test(id)) return null;\n if (id.includes(\"node_modules\")) return null;\n\n try {\n const lang = id.endsWith(\".tsx\") ? \"tsx\" : \"jsx\";\n const result = await transformWithOxc(code, id, {\n lang,\n jsx: {\n runtime: \"automatic\",\n importSource: \"@askrjs/askr\",\n },\n sourcemap: true,\n });\n\n if (!result || !result.code) return null;\n\n const codeOut = shouldOptimizeTemplates ? optimizeTemplateOutput(result.code) : result.code;\n\n return {\n code: codeOut,\n map: result.map,\n };\n } catch {\n return null;\n }\n },\n };\n}\n\nfunction optimizeTemplateOutput(code: string): string {\n const hoists = new Map<string, string>();\n let hoistIndex = 0;\n\n const optimized = code.replace(\n /\\b(class|className|style):\\s*(\"([^\"\\\\]|\\\\.)*\")/g,\n (fullMatch: string, key: string, literal: string) => {\n const cacheKey = `${key}:${literal}`;\n let identifier = hoists.get(cacheKey);\n if (!identifier) {\n const occurrenceCount = code.split(fullMatch).length - 1;\n if (occurrenceCount < 2) {\n return fullMatch;\n }\n identifier = `__askrStaticLiteral${hoistIndex++}`;\n hoists.set(cacheKey, identifier);\n }\n return `${key}: ${identifier}`;\n },\n );\n\n if (hoists.size === 0) {\n return code;\n }\n\n const declarations = Array.from(hoists.entries()).map(([cacheKey, name]) => {\n const literal = cacheKey.slice(cacheKey.indexOf(\":\") + 1);\n return `const ${name} = ${literal};`;\n });\n\n return `${declarations.join(\"\\n\")}\\n${optimized}`;\n}\n\nexport const askr = askrVitePlugin;\n\nexport default askrVitePlugin;\n"],"mappings":";;;AAGA,MAAM,UAAU,cAAc,OAAO,KAAK,GAAG;AAQ7C,SAAgB,eAAe,OAA8B,CAAC,GAAW;CACvE,MAAM,aAAa;CACnB,MAAM,kBAAkB,KAAK,gBAAgB;CAC7C,MAAM,0BAA0B,KAAK,qBAAqB;CAC1D,MAAM,iBAAiB,CAAC,cAAc;CAEtC,OAAO;EACL,MAAM;EACN,SAAS;EACT,SAAS;GACP,IAAI,YAA2B;GAC/B,IAAI;IACF,YAAY,QAAQ,QAAQ,cAAc;GAC5C,QAAQ;IACN,YAAY;GACd;GAEA,OAAO;IACL,QAAQ,EACN,6BAA6B,KAAK,UAAU,uBAAuB,EACrE;IACA,SAAS;KACP,OAAO,YACH,CACE;MACE,MAAM;MACN,aAAa;KACf,CACF,IACA,CAAC;KACL,QAAQ;IACV;IACA,cAAc,EACZ,SAAS;KACP;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;IACF,EACF;IACA,KAAK;KACH,KAAK;MACH,SAAS;MACT,cAAc;KAChB;KACA,WAAW;IACb;GACF;EACF;EAEA,MAAM,UAAU,MAAc,IAAY;GACxC,IAAI,CAAC,iBAAiB,OAAO;GAC7B,IAAI,CAAC,eAAe,KAAK,EAAE,GAAG,OAAO;GACrC,IAAI,GAAG,SAAS,cAAc,GAAG,OAAO;GAExC,IAAI;IAEF,MAAM,SAAS,MAAM,iBAAiB,MAAM,IAAI;KAC9C,MAFW,GAAG,SAAS,MAAM,IAAI,QAAQ;KAGzC,KAAK;MACH,SAAS;MACT,cAAc;KAChB;KACA,WAAW;IACb,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,OAAO,MAAM,OAAO;IAIpC,OAAO;KACL,MAHc,0BAA0B,uBAAuB,OAAO,IAAI,IAAI,OAAO;KAIrF,KAAK,OAAO;IACd;GACF,QAAQ;IACN,OAAO;GACT;EACF;CACF;AACF;AAEA,SAAS,uBAAuB,MAAsB;CACpD,MAAM,yBAAS,IAAI,IAAoB;CACvC,IAAI,aAAa;CAEjB,MAAM,YAAY,KAAK,QACrB,oDACC,WAAmB,KAAa,YAAoB;EACnD,MAAM,WAAW,GAAG,IAAI,GAAG;EAC3B,IAAI,aAAa,OAAO,IAAI,QAAQ;EACpC,IAAI,CAAC,YAAY;GAEf,IADwB,KAAK,MAAM,SAAS,EAAE,SAAS,IACjC,GACpB,OAAO;GAET,aAAa,sBAAsB;GACnC,OAAO,IAAI,UAAU,UAAU;EACjC;EACA,OAAO,GAAG,IAAI,IAAI;CACpB,CACF;CAEA,IAAI,OAAO,SAAS,GAClB,OAAO;CAQT,OAAO,GALc,MAAM,KAAK,OAAO,QAAQ,CAAC,EAAE,KAAK,CAAC,UAAU,UAAU;EAE1E,OAAO,SAAS,KAAK,KADL,SAAS,MAAM,SAAS,QAAQ,GAAG,IAAI,CACvB,EAAE;CACpC,CAEqB,EAAE,KAAK,IAAI,EAAE,IAAI;AACxC;AAEA,MAAa,OAAO"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/template-optimizer.ts","../src/jsx-plugin.ts"],"sourcesContent":["export function optimizeTemplateOutput(code: string): string {\n const hoists = new Map<string, string>();\n let hoistIndex = 0;\n const optimized = code.replace(\n /\\b(class|className|style):\\s*(\"([^\"\\\\]|\\\\.)*\")/g,\n (fullMatch: string, key: string, literal: string) => {\n const cacheKey = `${key}:${literal}`;\n let identifier = hoists.get(cacheKey);\n if (!identifier) {\n if (code.split(fullMatch).length - 1 < 2) return fullMatch;\n identifier = `__askrStaticLiteral${hoistIndex++}`;\n hoists.set(cacheKey, identifier);\n }\n return `${key}: ${identifier}`;\n },\n );\n if (hoists.size === 0) return code;\n const declarations = Array.from(hoists.entries()).map(([cacheKey, name]) => {\n const literal = cacheKey.slice(cacheKey.indexOf(\":\") + 1);\n return `const ${name} = ${literal};`;\n });\n return `${declarations.join(\"\\n\")}\\n${optimized}`;\n}\n","import { createRequire } from \"node:module\";\nimport { transformWithOxc, type Plugin } from \"vite\";\nimport { optimizeTemplateOutput } from \"./template-optimizer\";\n\nconst require = createRequire(import.meta.url);\n\nexport interface AskrVitePluginOptions {\n transformJsx?: boolean;\n optimizeTemplates?: boolean;\n ssrPrecompile?: boolean;\n}\n\n/**\n * Portable public plugin identity.\n *\n * Vite-compatible runners only require the plugin name. Keeping versioned Vite\n * hook types out of the installed declaration prevents consumers such as\n * vite-plus from comparing two distinct copies of Vite's recursive Plugin type.\n */\nexport interface AskrVitePlugin {\n readonly name: \"askr:vite\";\n}\n\nexport function askrVitePlugin(options: AskrVitePluginOptions = {}): AskrVitePlugin {\n const shouldTransform = options.transformJsx ?? true;\n const shouldOptimizeTemplates = options.optimizeTemplates ?? false;\n const plugin = {\n name: \"askr:vite\" as const,\n enforce: \"pre\",\n config() {\n let askrEntry: string | null = null;\n try {\n askrEntry = require.resolve(\"@askrjs/askr\");\n } catch {\n askrEntry = null;\n }\n return {\n define: {\n __ASKR_OPTIMIZE_TEMPLATES__: JSON.stringify(shouldOptimizeTemplates),\n },\n resolve: {\n alias: askrEntry ? [{ find: \"@askrjs/askr\", replacement: askrEntry }] : [],\n dedupe: [\"@askrjs/askr\"],\n },\n optimizeDeps: {\n include: [\n \"@askrjs/askr\",\n \"@askrjs/askr/router\",\n \"@askrjs/askr/boot\",\n \"@askrjs/askr/control\",\n \"@askrjs/askr/fx\",\n \"@askrjs/askr/resources\",\n \"@askrjs/askr/data\",\n \"@askrjs/askr/foundations\",\n \"@askrjs/askr/foundations/icon\",\n \"@askrjs/askr/foundations/interactions\",\n \"@askrjs/askr/foundations/state\",\n \"@askrjs/askr/foundations/structures\",\n \"@askrjs/askr/foundations/utilities\",\n \"@askrjs/askr/jsx-runtime\",\n \"@askrjs/askr/jsx-dev-runtime\",\n ],\n },\n oxc: {\n jsx: { runtime: \"automatic\", importSource: \"@askrjs/askr\" },\n jsxInject: \"import { jsx, jsxs, Fragment } from '@askrjs/askr/jsx-runtime';\",\n },\n };\n },\n async transform(code, id) {\n if (!shouldTransform || !/\\.(jsx|tsx)$/.test(id) || id.includes(\"node_modules\")) return null;\n try {\n const result = await transformWithOxc(code, id, {\n lang: id.endsWith(\".tsx\") ? \"tsx\" : \"jsx\",\n jsx: { runtime: \"automatic\", importSource: \"@askrjs/askr\" },\n sourcemap: true,\n });\n if (!result?.code) return null;\n return {\n code: shouldOptimizeTemplates ? optimizeTemplateOutput(result.code) : result.code,\n map: result.map,\n };\n } catch {\n return null;\n }\n },\n } satisfies Plugin;\n return plugin;\n}\n"],"mappings":";;;;AAAA,SAAgB,uBAAuB,MAAsB;CAC3D,MAAM,yBAAS,IAAI,IAAoB;CACvC,IAAI,aAAa;CACjB,MAAM,YAAY,KAAK,QACrB,oDACC,WAAmB,KAAa,YAAoB;EACnD,MAAM,WAAW,GAAG,IAAI,GAAG;EAC3B,IAAI,aAAa,OAAO,IAAI,QAAQ;EACpC,IAAI,CAAC,YAAY;GACf,IAAI,KAAK,MAAM,SAAS,CAAC,CAAC,SAAS,IAAI,GAAG,OAAO;GACjD,aAAa,sBAAsB;GACnC,OAAO,IAAI,UAAU,UAAU;EACjC;EACA,OAAO,GAAG,IAAI,IAAI;CACpB,CACF;CACA,IAAI,OAAO,SAAS,GAAG,OAAO;CAK9B,OAAO,GAJc,MAAM,KAAK,OAAO,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,UAAU;EAE1E,OAAO,SAAS,KAAK,KADL,SAAS,MAAM,SAAS,QAAQ,GAAG,IAAI,CACvB,EAAE;CACpC,CACqB,CAAC,CAAC,KAAK,IAAI,EAAE,IAAI;AACxC;;;AClBA,MAAM,UAAU,cAAc,OAAO,KAAK,GAAG;AAmB7C,SAAgB,eAAe,UAAiC,CAAC,GAAmB;CAClF,MAAM,kBAAkB,QAAQ,gBAAgB;CAChD,MAAM,0BAA0B,QAAQ,qBAAqB;CA8D7D,OAAO;EA5DL,MAAM;EACN,SAAS;EACT,SAAS;GACP,IAAI,YAA2B;GAC/B,IAAI;IACF,YAAY,QAAQ,QAAQ,cAAc;GAC5C,QAAQ;IACN,YAAY;GACd;GACA,OAAO;IACL,QAAQ,EACN,6BAA6B,KAAK,UAAU,uBAAuB,EACrE;IACA,SAAS;KACP,OAAO,YAAY,CAAC;MAAE,MAAM;MAAgB,aAAa;KAAU,CAAC,IAAI,CAAC;KACzE,QAAQ,CAAC,cAAc;IACzB;IACA,cAAc,EACZ,SAAS;KACP;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;IACF,EACF;IACA,KAAK;KACH,KAAK;MAAE,SAAS;MAAa,cAAc;KAAe;KAC1D,WAAW;IACb;GACF;EACF;EACA,MAAM,UAAU,MAAM,IAAI;GACxB,IAAI,CAAC,mBAAmB,CAAC,eAAe,KAAK,EAAE,KAAK,GAAG,SAAS,cAAc,GAAG,OAAO;GACxF,IAAI;IACF,MAAM,SAAS,MAAM,iBAAiB,MAAM,IAAI;KAC9C,MAAM,GAAG,SAAS,MAAM,IAAI,QAAQ;KACpC,KAAK;MAAE,SAAS;MAAa,cAAc;KAAe;KAC1D,WAAW;IACb,CAAC;IACD,IAAI,CAAC,QAAQ,MAAM,OAAO;IAC1B,OAAO;KACL,MAAM,0BAA0B,uBAAuB,OAAO,IAAI,IAAI,OAAO;KAC7E,KAAK,OAAO;IACd;GACF,QAAQ;IACN,OAAO;GACT;EACF;CAEU;AACd"}
@@ -0,0 +1,36 @@
1
+ /// <reference path="./virtual-askr-server.d.ts" />
2
+ import { ServerApp } from "@askrjs/server";
3
+ //#region src/server/document.d.ts
4
+ interface ViteTelemetryFields {
5
+ status?: number;
6
+ }
7
+ interface ViteTelemetry {
8
+ viteDocument?<T>(fields: ViteTelemetryFields, work: () => T): T;
9
+ }
10
+ interface AskrDocumentOptions {
11
+ telemetry?: ViteTelemetry;
12
+ }
13
+ declare const ASKR_APP_MARKER = "<!--askr-app-->";
14
+ declare const ASKR_HEAD_MARKER = "<!--askr-head-->";
15
+ declare function insertAskrFragment(document: string, fragment: string): string;
16
+ declare function composeAskrHead(document: string, head: string, lang?: string, dir?: string): string;
17
+ declare function isAskrFragment(response: Response): boolean;
18
+ declare function composeAskrDocumentResponse(response: Response, transformedDocument: string, options?: AskrDocumentOptions): Promise<Response>;
19
+ declare function createDocumentApp(app: ServerApp, transformedDocument: string, options?: AskrDocumentOptions): ServerApp;
20
+ //#endregion
21
+ //#region src/server/types.d.ts
22
+ interface AskrServerOptions {
23
+ entry: string;
24
+ exportName?: string;
25
+ indexHtml?: string;
26
+ }
27
+ //#endregion
28
+ //#region src/server/plugin.d.ts
29
+ declare const ASKR_SERVER_MODULE_ID = "virtual:askr-server";
30
+ /** Portable public identity for the server integration plugin. */
31
+ interface AskrServerPlugin {
32
+ readonly name: "askr:server";
33
+ }
34
+ declare function askrServer(options: AskrServerOptions): AskrServerPlugin;
35
+ //#endregion
36
+ export { ASKR_APP_MARKER, ASKR_HEAD_MARKER, ASKR_SERVER_MODULE_ID, AskrDocumentOptions, type AskrServerOptions, AskrServerPlugin, askrServer, composeAskrDocumentResponse, composeAskrHead, createDocumentApp, insertAskrFragment, isAskrFragment };
package/dist/server.js ADDED
@@ -0,0 +1,176 @@
1
+
2
+ import { createNodeHandler } from "@askrjs/node";
3
+ import { readFile } from "node:fs/promises";
4
+ import { existsSync } from "node:fs";
5
+ import { resolve } from "node:path";
6
+ //#region src/server/document.ts
7
+ const ASKR_APP_MARKER = "<!--askr-app-->";
8
+ const ASKR_HEAD_MARKER = "<!--askr-head-->";
9
+ function markerCount(document) {
10
+ return document.split(ASKR_APP_MARKER).length - 1;
11
+ }
12
+ function headMarkerCount(document) {
13
+ return document.split(ASKR_HEAD_MARKER).length - 1;
14
+ }
15
+ function escapeAttribute(value) {
16
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
17
+ }
18
+ function patchHtmlAttribute(document, name, value) {
19
+ return document.replace(/<html\b([^>]*)>/i, (_match, attributes) => {
20
+ const matcher = new RegExp(`\\s+${name}(?:\\s*=\\s*(?:"[^"]*"|'[^']*'|[^\\s"'=<>\u0060]+))?`, "gi");
21
+ return `<html${attributes.replace(matcher, "")} ${name}="${escapeAttribute(value)}">`;
22
+ });
23
+ }
24
+ function insertAskrFragment(document, fragment) {
25
+ const count = markerCount(document);
26
+ if (count !== 1) throw new Error(`index.html must contain exactly one ${ASKR_APP_MARKER} marker; found ${count}.`);
27
+ return document.replace(ASKR_APP_MARKER, fragment);
28
+ }
29
+ function composeAskrHead(document, head, lang, dir) {
30
+ let output = document.replace(ASKR_HEAD_MARKER, head);
31
+ if (lang) output = patchHtmlAttribute(output, "lang", lang);
32
+ if (dir) output = patchHtmlAttribute(output, "dir", dir);
33
+ return output;
34
+ }
35
+ function isAskrFragment(response) {
36
+ return response.headers.get("content-type")?.split(";").some((part) => part.trim().toLowerCase() === "askr-fragment=1") ?? false;
37
+ }
38
+ function documentHeaders(response) {
39
+ const headers = new Headers(response.headers);
40
+ const type = response.headers.get("content-type")?.split(";").map((part) => part.trim()).filter((part) => part.toLowerCase() !== "askr-fragment=1").join("; ");
41
+ if (type) headers.set("content-type", type);
42
+ const internalHeaders = [];
43
+ headers.forEach((_value, key) => {
44
+ if (key.toLowerCase().startsWith("x-askr-")) internalHeaders.push(key);
45
+ });
46
+ for (const key of internalHeaders) headers.delete(key);
47
+ return headers;
48
+ }
49
+ function composeDocumentBody(fragment, prefix, suffix) {
50
+ if (!fragment) return `${prefix}${suffix}`;
51
+ const encoder = new TextEncoder();
52
+ const reader = fragment.getReader();
53
+ let prefixPending = true;
54
+ let suffixPending = true;
55
+ let reading = false;
56
+ let cancelled = false;
57
+ return new ReadableStream({
58
+ pull(controller) {
59
+ if (prefixPending) {
60
+ prefixPending = false;
61
+ if (prefix) controller.enqueue(encoder.encode(prefix));
62
+ return;
63
+ }
64
+ if (reading) return;
65
+ reading = true;
66
+ reader.read().then((part) => {
67
+ reading = false;
68
+ if (cancelled) return;
69
+ if (!part.done) {
70
+ controller.enqueue(part.value);
71
+ return;
72
+ }
73
+ if (suffixPending && suffix) controller.enqueue(encoder.encode(suffix));
74
+ suffixPending = false;
75
+ controller.close();
76
+ }, (error) => {
77
+ reading = false;
78
+ if (cancelled) return;
79
+ controller.error(error);
80
+ reader.cancel(error).catch(() => void 0);
81
+ });
82
+ },
83
+ async cancel(reason) {
84
+ cancelled = true;
85
+ await reader.cancel(reason).catch(() => void 0);
86
+ }
87
+ });
88
+ }
89
+ async function composeAskrDocumentResponse(response, transformedDocument, options = {}) {
90
+ if (!isAskrFragment(response)) return response;
91
+ const compose = () => {
92
+ if (headMarkerCount(transformedDocument) !== 1) throw new Error(`index.html must contain exactly one ${ASKR_HEAD_MARKER} marker; found ${headMarkerCount(transformedDocument)}.`);
93
+ 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);
94
+ const appCount = markerCount(withHead);
95
+ if (appCount !== 1) throw new Error(`index.html must contain exactly one ${ASKR_APP_MARKER} marker; found ${appCount}.`);
96
+ const markerIndex = withHead.indexOf(ASKR_APP_MARKER);
97
+ const prefix = withHead.slice(0, markerIndex);
98
+ const suffix = withHead.slice(markerIndex + 15);
99
+ return new Response(composeDocumentBody(response.body, prefix, suffix), {
100
+ status: response.status,
101
+ statusText: response.statusText,
102
+ headers: documentHeaders(response)
103
+ });
104
+ };
105
+ return options.telemetry?.viteDocument ? options.telemetry.viteDocument({ status: response.status }, compose) : compose();
106
+ }
107
+ function createDocumentApp(app, transformedDocument, options = {}) {
108
+ return { async fetch(request) {
109
+ return composeAskrDocumentResponse(await app.fetch(request), transformedDocument, options);
110
+ } };
111
+ }
112
+ //#endregion
113
+ //#region src/server/development.ts
114
+ function serverApp(module, options) {
115
+ const value = module[options.exportName ?? "app"] ?? module.default;
116
+ if (!value || typeof value.fetch !== "function") throw new Error(`@askrjs/vite: ${options.entry} must export a ServerApp as ${options.exportName ?? "app"} or default`);
117
+ return value;
118
+ }
119
+ function moduleTelemetry(module) {
120
+ const telemetry = module["telemetry"];
121
+ return telemetry && typeof telemetry === "object" ? { telemetry } : void 0;
122
+ }
123
+ function createDevelopmentApp(server, options) {
124
+ return { async fetch(request) {
125
+ const module = await server.ssrLoadModule(options.entry);
126
+ const response = await serverApp(module, options).fetch(request);
127
+ if (!isAskrFragment(response)) return response;
128
+ const document = await readFile(resolve(server.config.root, options.indexHtml ?? "index.html"), "utf8");
129
+ return composeAskrDocumentResponse(response, await server.transformIndexHtml(new URL(request.url).pathname, document), moduleTelemetry(module));
130
+ } };
131
+ }
132
+ //#endregion
133
+ //#region src/server/plugin.ts
134
+ const ASKR_SERVER_MODULE_ID = "virtual:askr-server";
135
+ const RESOLVED_SERVER_MODULE_ID = "\0askr:server";
136
+ function askrServer(options) {
137
+ let config;
138
+ return {
139
+ name: "askr:server",
140
+ config() {
141
+ return {
142
+ appType: "custom",
143
+ resolve: { noExternal: ["@askrjs/*"] }
144
+ };
145
+ },
146
+ configResolved(resolved) {
147
+ config = resolved;
148
+ },
149
+ resolveId(id) {
150
+ return id === "virtual:askr-server" ? RESOLVED_SERVER_MODULE_ID : null;
151
+ },
152
+ async load(id) {
153
+ if (id !== RESOLVED_SERVER_MODULE_ID) return null;
154
+ if (!config) throw new Error("@askrjs/vite: server plugin was not configured.");
155
+ const sourceDocument = resolve(config.root, options.indexHtml ?? "index.html");
156
+ const document = await readFile([resolve(config.root, config.build.outDir, "index.html"), resolve(config.root, config.build.outDir, "..", "index.html")].find(existsSync) ?? sourceDocument, "utf8");
157
+ const entry = resolve(config.root, options.entry);
158
+ const exportName = options.exportName ?? "app";
159
+ return [
160
+ `import * as source from ${JSON.stringify(entry)};`,
161
+ `import { createDocumentApp } from '@askrjs/vite/server';`,
162
+ `const sourceApp = source[${JSON.stringify(exportName)}] ?? source.default;`,
163
+ `const sourceTelemetry = Reflect.get(source, 'telemetry');`,
164
+ `if (!sourceApp?.fetch) throw new Error(${JSON.stringify(`@askrjs/vite: ${options.entry} must export a ServerApp`)});`,
165
+ `export const app = createDocumentApp(sourceApp, ${JSON.stringify(document)}, sourceTelemetry ? { telemetry: sourceTelemetry } : undefined);`,
166
+ `export default app;`
167
+ ].join("\n");
168
+ },
169
+ configureServer(server) {
170
+ const handler = createNodeHandler(createDevelopmentApp(server, options));
171
+ return () => server.middlewares.use(handler);
172
+ }
173
+ };
174
+ }
175
+ //#endregion
176
+ export { ASKR_APP_MARKER, ASKR_HEAD_MARKER, ASKR_SERVER_MODULE_ID, askrServer, composeAskrDocumentResponse, composeAskrHead, createDocumentApp, insertAskrFragment, isAskrFragment };
@@ -0,0 +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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"}
@@ -0,0 +1,6 @@
1
+ declare module "virtual:askr-server" {
2
+ import type { ServerApp } from "@askrjs/server";
3
+
4
+ export const app: ServerApp;
5
+ export default app;
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askrjs/vite",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Vite plugin for Askr JSX and template transforms",
5
5
  "homepage": "https://github.com/askrjs/askr-vite#readme",
6
6
  "bugs": {
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "repository": {
15
15
  "type": "git",
16
- "url": "https://github.com/askrjs/askr-vite.git"
16
+ "url": "git+https://github.com/askrjs/askr-vite.git"
17
17
  },
18
18
  "files": [
19
19
  "dist",
@@ -24,9 +24,14 @@
24
24
  "types": "./dist/index.d.ts",
25
25
  "exports": {
26
26
  ".": {
27
- "import": "./dist/index.js",
28
- "types": "./dist/index.d.ts"
29
- }
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js"
29
+ },
30
+ "./server": {
31
+ "types": "./dist/server.d.ts",
32
+ "import": "./dist/server.js"
33
+ },
34
+ "./package.json": "./package.json"
30
35
  },
31
36
  "publishConfig": {
32
37
  "access": "public"
@@ -38,17 +43,29 @@
38
43
  "test:unit": "vp test run -c vitest.config.ts",
39
44
  "test:jsdom": "npm run test:unit",
40
45
  "test:browser": "npm run test:unit",
46
+ "test:publint": "publint",
41
47
  "fmt": "vp fmt .",
42
48
  "lint": "vp lint .",
43
- "prepublishOnly": "npm run build"
49
+ "typecheck": "tsc -p tsconfig.json --noEmit",
50
+ "pack:check": "node tests/package-artifacts.js",
51
+ "check": "npm run lint && npm run typecheck && npm test && npm run build && npm run test:publint && npm run pack:check",
52
+ "prepack": "npm run build",
53
+ "prepublishOnly": "npm run check"
54
+ },
55
+ "dependencies": {
56
+ "@askrjs/node": ">=0.0.1 <0.1.0"
44
57
  },
45
58
  "devDependencies": {
59
+ "@askrjs/askr": ">=0.0.52 <0.1.0",
60
+ "@askrjs/server": ">=0.0.1 <0.1.0",
61
+ "@types/node": "^26.0.0",
62
+ "publint": "^0.3.21",
46
63
  "typescript": "^6.0.3",
47
64
  "vite": "^8.0.16",
48
- "vite-plus": "^0.1.24"
65
+ "vite-plus": "^0.2.4"
49
66
  },
50
67
  "peerDependencies": {
51
- "@askrjs/askr": ">=0.0.30 <0.1.0",
68
+ "@askrjs/askr": ">=0.0.52 <0.1.0",
52
69
  "vite": "^8.0.1"
53
70
  },
54
71
  "engines": {