@pracht/vite-plugin 0.2.4 → 0.3.0

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/dist/index.d.mts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { Plugin } from "vite";
2
- import { RenderMode } from "@pracht/core";
2
+ import { RenderMode, RenderMode as RenderMode$1 } from "@pracht/core";
3
3
 
4
- //#region src/index.d.ts
4
+ //#region src/plugin-assets.d.ts
5
5
  declare const PRACHT_CLIENT_MODULE_ID = "virtual:pracht/client";
6
6
  declare const PRACHT_SERVER_MODULE_ID = "virtual:pracht/server";
7
+ //#endregion
8
+ //#region src/plugin-adapter.d.ts
7
9
  /**
8
10
  * An adapter object that bridges pracht's platform-agnostic core to a specific
9
11
  * deployment target. Built-in adapters are provided by `@pracht/adapter-node`,
@@ -36,7 +38,16 @@ interface PrachtAdapter {
36
38
  * (e.g. Cloudflare workerd via `@cloudflare/vite-plugin`).
37
39
  */
38
40
  ownsDevServer?: boolean;
41
+ /**
42
+ * If true, the adapter targets an edge runtime that cannot resolve
43
+ * dependencies from `node_modules` at runtime. The Vite plugin will set
44
+ * `ssr.noExternal = true` for SSR builds so all dependencies are bundled
45
+ * into the server output.
46
+ */
47
+ edge?: boolean;
39
48
  }
49
+ //#endregion
50
+ //#region src/plugin-options.d.ts
40
51
  interface PrachtPluginOptions {
41
52
  appFile?: string;
42
53
  routesDir?: string;
@@ -48,9 +59,10 @@ interface PrachtPluginOptions {
48
59
  /** Enable file-system pages routing by pointing to the pages directory (e.g. "/src/pages"). */
49
60
  pagesDir?: string;
50
61
  /** Default render mode for pages when RENDER_MODE is not exported. Defaults to "ssr". */
51
- pagesDefaultRender?: RenderMode;
62
+ pagesDefaultRender?: RenderMode$1;
52
63
  }
53
- declare function pracht(options?: PrachtPluginOptions): Promise<Plugin[]>;
64
+ //#endregion
65
+ //#region src/plugin-codegen.d.ts
54
66
  declare function createPrachtClientModuleSource(options?: PrachtPluginOptions, buildOptions?: {
55
67
  root?: string;
56
68
  }): string;
@@ -60,4 +72,7 @@ declare function createPrachtServerModuleSource(options?: PrachtPluginOptions, b
60
72
  }): string;
61
73
  declare function createPrachtRegistryModuleSource(options?: PrachtPluginOptions): string;
62
74
  //#endregion
63
- export { PRACHT_CLIENT_MODULE_ID, PRACHT_SERVER_MODULE_ID, PrachtAdapter, PrachtPluginOptions, type RenderMode, createPrachtClientModuleSource, createPrachtRegistryModuleSource, createPrachtServerModuleSource, pracht };
75
+ //#region src/index.d.ts
76
+ declare function pracht(options?: PrachtPluginOptions): Promise<Plugin[]>;
77
+ //#endregion
78
+ export { PRACHT_CLIENT_MODULE_ID, PRACHT_SERVER_MODULE_ID, type PrachtAdapter, type PrachtPluginOptions, type RenderMode, createPrachtClientModuleSource, createPrachtRegistryModuleSource, createPrachtServerModuleSource, pracht };
package/dist/index.mjs CHANGED
@@ -1,9 +1,34 @@
1
1
  import { generatePagesManifestSource, scanPagesDirectory } from "./pages-router.mjs";
2
2
  import preact from "@preact/preset-vite";
3
- import { existsSync, readFileSync } from "node:fs";
4
3
  import { resolve } from "node:path";
5
4
  import { parseAst } from "vite";
6
- //#region src/client-module-scope-analysis.ts
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ //#region src/client-module-query.ts
7
+ const CLIENT_MODULE_QUERY = "pracht-client";
8
+ const PRACHT_CLIENT_MODULE_QUERY = `?${CLIENT_MODULE_QUERY}`;
9
+ function isPrachtClientModuleId(id) {
10
+ const queryStart = id.indexOf("?");
11
+ if (queryStart === -1) return false;
12
+ return id.slice(queryStart + 1).split("&").includes(CLIENT_MODULE_QUERY);
13
+ }
14
+ function stripPrachtClientModuleQuery(id) {
15
+ const queryStart = id.indexOf("?");
16
+ if (queryStart === -1) return id;
17
+ const path = id.slice(0, queryStart);
18
+ const query = id.slice(queryStart + 1).split("&").filter((part) => part !== CLIENT_MODULE_QUERY);
19
+ return query.length > 0 ? `${path}?${query.join("&")}` : path;
20
+ }
21
+ function getRolldownLang(id) {
22
+ const path = stripPrachtClientModuleQuery(id).split("?")[0];
23
+ if (/\.(c|m)?tsx$/i.test(path)) return "tsx";
24
+ if (/\.(c|m)?ts$/i.test(path)) return "ts";
25
+ if (/\.(c|m)?jsx$/i.test(path)) return "jsx";
26
+ if (/\.mdx?$/i.test(path)) return "jsx";
27
+ if (/\.(c|m)?js$/i.test(path)) return "js";
28
+ return "tsx";
29
+ }
30
+ //#endregion
31
+ //#region src/scope-analysis-types.ts
7
32
  const JSX_COMPONENT_RE = /^[A-Z]/;
8
33
  const SKIPPED_KEYS = new Set([
9
34
  "attributes",
@@ -23,20 +48,187 @@ const SKIPPED_KEYS = new Set([
23
48
  "typeParameters",
24
49
  "value"
25
50
  ]);
26
- function analyzeRetainedStatements(statements, options = {}) {
27
- const programScope = createScope("program", null, null);
28
- for (const name of options.knownTopLevelNames ?? []) declareBinding(programScope, name, "placeholder", null);
29
- const scopesByNode = /* @__PURE__ */ new WeakMap();
30
- declareProgramScopes(statements, programScope, scopesByNode);
31
- const result = {
32
- programScope,
33
- referencedTopLevelNames: /* @__PURE__ */ new Set(),
34
- references: []
35
- };
36
- const excludedNames = new Set(options.excludedNames);
37
- for (const statement of statements) collectStatementReferences(statement.node, programScope, scopesByNode, result, excludedNames);
38
- return result;
51
+ //#endregion
52
+ //#region src/scope-analysis-helpers.ts
53
+ function getStatementDeclaration(statement) {
54
+ if (statement.type === "ExportNamedDeclaration") return statement.declaration ?? null;
55
+ if (statement.type === "ExportDefaultDeclaration" && (statement.declaration.type === "FunctionDeclaration" || statement.declaration.type === "ClassDeclaration")) return statement.declaration;
56
+ if (statement.type === "FunctionDeclaration" || statement.type === "ClassDeclaration" || statement.type === "VariableDeclaration") return statement;
57
+ return null;
58
+ }
59
+ function collectBindingNamesFromPattern(pattern) {
60
+ if (!pattern) return [];
61
+ switch (pattern.type) {
62
+ case "Identifier": return [pattern.name];
63
+ case "AssignmentPattern": return collectBindingNamesFromPattern(pattern.left);
64
+ case "RestElement": return collectBindingNamesFromPattern(pattern.argument);
65
+ case "ObjectPattern": return pattern.properties.flatMap((property) => {
66
+ if (property.type === "Property") return collectBindingNamesFromPattern(property.value);
67
+ return collectBindingNamesFromPattern(property.argument);
68
+ });
69
+ case "ArrayPattern": return pattern.elements.flatMap((element) => collectBindingNamesFromPattern(element));
70
+ default: return [];
71
+ }
72
+ }
73
+ function getIdentifierName(node) {
74
+ if (!node) return null;
75
+ if (node.type === "Identifier" || node.type === "JSXIdentifier") return node.name;
76
+ if (node.type === "Literal" && typeof node.value === "string") return node.value;
77
+ return null;
78
+ }
79
+ function isNode(value) {
80
+ return !!value && typeof value === "object" && "type" in value;
81
+ }
82
+ function getTsRuntimeChildren(node) {
83
+ switch (node.type) {
84
+ case "TSAsExpression":
85
+ case "TSInstantiationExpression":
86
+ case "TSNonNullExpression":
87
+ case "TSSatisfiesExpression":
88
+ case "TSTypeAssertion": return [node.expression];
89
+ default: return [];
90
+ }
91
+ }
92
+ function collectFunctionScopedVarBindings(node) {
93
+ const names = /* @__PURE__ */ new Set();
94
+ collectFunctionScopedVarBindingsInto(node, names);
95
+ return names;
96
+ }
97
+ function collectFunctionScopedVarBindingsInto(node, names) {
98
+ if (!node) return;
99
+ if (node.type.startsWith("TS")) {
100
+ for (const child of getTsRuntimeChildren(node)) collectFunctionScopedVarBindingsInto(child, names);
101
+ return;
102
+ }
103
+ switch (node.type) {
104
+ case "ArrowFunctionExpression":
105
+ case "FunctionDeclaration":
106
+ case "FunctionExpression":
107
+ case "ClassDeclaration":
108
+ case "ClassExpression": return;
109
+ case "VariableDeclaration":
110
+ if (node.kind === "var") for (const declarator of node.declarations) for (const name of collectBindingNamesFromPattern(declarator.id)) names.add(name);
111
+ return;
112
+ default: for (const [key, value] of Object.entries(node)) {
113
+ if (SKIPPED_KEYS.has(key)) continue;
114
+ if (key === "id" || key === "implements" || key === "superTypeArguments") continue;
115
+ collectFunctionScopedVarBindingsFromUnknown(value, names);
116
+ }
117
+ }
118
+ }
119
+ function collectFunctionScopedVarBindingsFromUnknown(value, names) {
120
+ if (Array.isArray(value)) {
121
+ for (const item of value) collectFunctionScopedVarBindingsFromUnknown(item, names);
122
+ return;
123
+ }
124
+ if (!isNode(value)) return;
125
+ collectFunctionScopedVarBindingsInto(value, names);
126
+ }
127
+ //#endregion
128
+ //#region src/client-module-transform-state.ts
129
+ function createStatementStates(program) {
130
+ return program.body.map((node) => ({
131
+ node,
132
+ removed: false,
133
+ removedDeclarators: /* @__PURE__ */ new Set(),
134
+ removedSpecifiers: /* @__PURE__ */ new Set()
135
+ }));
136
+ }
137
+ function getRemainingDeclaratorIndices(state) {
138
+ const declaration = getStatementDeclaration(state.node);
139
+ if (!declaration || declaration.type !== "VariableDeclaration") return [];
140
+ return declaration.declarations.map((_item, index) => index).filter((index) => !state.removedDeclarators.has(index));
141
+ }
142
+ function getRemainingSpecifierIndices(state) {
143
+ const statement = state.node;
144
+ if (!("specifiers" in statement) || !Array.isArray(statement.specifiers)) return [];
145
+ return statement.specifiers.map((_item, index) => index).filter((index) => !state.removedSpecifiers.has(index));
146
+ }
147
+ function collectBindingNamesFromDeclaration(declaration) {
148
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return declaration.id ? [declaration.id.name] : [];
149
+ if (declaration.type === "VariableDeclaration") return declaration.declarations.flatMap((declarator) => collectBindingNamesFromPattern(declarator.id));
150
+ return [];
151
+ }
152
+ function normalizeRetainedStatements(states) {
153
+ return states.map((state) => normalizeRetainedStatement(state)).filter((state) => state !== null);
154
+ }
155
+ function normalizeRetainedStatement(state) {
156
+ if (state.removed) return null;
157
+ const statement = state.node;
158
+ if (statement.type === "ImportDeclaration" && state.removedSpecifiers.size > 0) return { node: {
159
+ ...statement,
160
+ specifiers: getRemainingSpecifierIndices(state).map((index) => statement.specifiers[index])
161
+ } };
162
+ if (statement.type === "ExportNamedDeclaration" && !statement.declaration && state.removedSpecifiers.size > 0) return { node: {
163
+ ...statement,
164
+ specifiers: getRemainingSpecifierIndices(state).map((index) => statement.specifiers[index])
165
+ } };
166
+ const declaration = getStatementDeclaration(statement);
167
+ if (declaration?.type === "VariableDeclaration" && state.removedDeclarators.size > 0) {
168
+ const retainedDeclaration = {
169
+ ...declaration,
170
+ declarations: getRemainingDeclaratorIndices(state).map((index) => declaration.declarations[index])
171
+ };
172
+ if (statement.type === "ExportNamedDeclaration") return { node: {
173
+ ...statement,
174
+ declaration: retainedDeclaration
175
+ } };
176
+ return { node: retainedDeclaration };
177
+ }
178
+ return { node: statement };
179
+ }
180
+ //#endregion
181
+ //#region src/client-module-transform-render.ts
182
+ function renderProgram(code, states) {
183
+ let cursor = 0;
184
+ let out = "";
185
+ for (const state of states) {
186
+ const statement = state.node;
187
+ out += code.slice(cursor, statement.start);
188
+ out += renderStatement(code, state);
189
+ cursor = statement.end;
190
+ }
191
+ out += code.slice(cursor);
192
+ return out;
193
+ }
194
+ function renderStatement(code, state) {
195
+ if (state.removed) return "";
196
+ const statement = state.node;
197
+ const declaration = getStatementDeclaration(statement);
198
+ if (statement.type === "ImportDeclaration" && state.removedSpecifiers.size > 0) return renderImportDeclaration(code, statement, state);
199
+ if (statement.type === "ExportNamedDeclaration" && !statement.declaration && state.removedSpecifiers.size > 0) return renderExportSpecifiers(code, statement, state);
200
+ if (declaration?.type === "VariableDeclaration" && state.removedDeclarators.size > 0) return renderVariableDeclaration(code, statement, declaration, state);
201
+ return code.slice(statement.start, statement.end);
202
+ }
203
+ function renderImportDeclaration(code, statement, state) {
204
+ const remaining = getRemainingSpecifierIndices(state).map((index) => statement.specifiers[index]);
205
+ if (remaining.length === 0) return "";
206
+ const defaultSpecifier = remaining.find((specifier) => specifier.type === "ImportDefaultSpecifier");
207
+ const namespaceSpecifier = remaining.find((specifier) => specifier.type === "ImportNamespaceSpecifier");
208
+ const namedSpecifiers = remaining.filter((specifier) => specifier.type === "ImportSpecifier");
209
+ const clauseParts = [];
210
+ if (defaultSpecifier) clauseParts.push(code.slice(defaultSpecifier.start, defaultSpecifier.end));
211
+ if (namespaceSpecifier) clauseParts.push(code.slice(namespaceSpecifier.start, namespaceSpecifier.end));
212
+ if (namedSpecifiers.length > 0) clauseParts.push(`{ ${namedSpecifiers.map((specifier) => code.slice(specifier.start, specifier.end)).join(", ")} }`);
213
+ const importPrefix = ["import"];
214
+ if (statement.importKind === "type") importPrefix.push("type");
215
+ if (typeof statement.phase === "string" && statement.phase.length > 0) importPrefix.push(statement.phase);
216
+ return `${importPrefix.join(" ")} ${clauseParts.join(", ")} from ${code.slice(statement.source.start, statement.end)}`;
217
+ }
218
+ function renderExportSpecifiers(code, statement, state) {
219
+ const remaining = getRemainingSpecifierIndices(state).map((index) => statement.specifiers[index]);
220
+ if (remaining.length === 0) return "";
221
+ const exportPrefix = statement.exportKind === "type" ? "export type" : "export";
222
+ const sourceSuffix = statement.source ? ` from ${code.slice(statement.source.start, statement.end)}` : ";";
223
+ return `${exportPrefix} { ${remaining.map((specifier) => code.slice(specifier.start, specifier.end)).join(", ")} }${sourceSuffix}`;
39
224
  }
225
+ function renderVariableDeclaration(code, statement, declaration, state) {
226
+ const remaining = getRemainingDeclaratorIndices(state).map((index) => declaration.declarations[index]);
227
+ if (remaining.length === 0) return "";
228
+ return `${statement.type === "ExportNamedDeclaration" ? "export " : ""}${declaration.kind} ${remaining.map((item) => code.slice(item.start, item.end)).join(", ")};`;
229
+ }
230
+ //#endregion
231
+ //#region src/scope-analysis-declare.ts
40
232
  function createScope(type, parent, node) {
41
233
  return {
42
234
  bindings: /* @__PURE__ */ new Map(),
@@ -45,6 +237,16 @@ function createScope(type, parent, node) {
45
237
  type
46
238
  };
47
239
  }
240
+ function declareBinding(scope, name, kind, node) {
241
+ const binding = {
242
+ kind,
243
+ name,
244
+ node,
245
+ scope
246
+ };
247
+ scope.bindings.set(name, binding);
248
+ return binding;
249
+ }
48
250
  function declareProgramScopes(statements, programScope, scopesByNode) {
49
251
  for (const statement of statements) declareTopLevelStatement(statement.node, programScope);
50
252
  for (const statement of statements) declareNodeScopes(statement.node, programScope, scopesByNode);
@@ -54,19 +256,19 @@ function declareTopLevelStatement(statement, programScope) {
54
256
  if (statement.importKind === "type") return;
55
257
  for (const specifier of statement.specifiers) {
56
258
  if (specifier.type === "ImportSpecifier" && specifier.importKind === "type") continue;
57
- const localName = getIdentifierName$1(specifier.local);
259
+ const localName = getIdentifierName(specifier.local);
58
260
  if (localName) declareBinding(programScope, localName, "import", specifier);
59
261
  }
60
262
  return;
61
263
  }
62
- const declaration = getStatementDeclaration$1(statement);
264
+ const declaration = getStatementDeclaration(statement);
63
265
  if (!declaration) return;
64
266
  declareDeclarationBindings(programScope, declaration);
65
267
  }
66
268
  function declareNodeScopes(node, currentScope, scopesByNode) {
67
269
  if (!node) return;
68
270
  if (node.type.startsWith("TS")) {
69
- declareTsRuntimeChildren(node, currentScope, scopesByNode);
271
+ for (const child of getTsRuntimeChildren(node)) declareNodeScopes(child, currentScope, scopesByNode);
70
272
  return;
71
273
  }
72
274
  switch (node.type) {
@@ -144,7 +346,7 @@ function declareNodeScopes(node, currentScope, scopesByNode) {
144
346
  declareNodeScopes(node.superClass, currentScope, scopesByNode);
145
347
  const classScope = createScope("class", currentScope, node);
146
348
  scopesByNode.set(node, classScope);
147
- const name = getIdentifierName$1(node.id);
349
+ const name = getIdentifierName(node.id);
148
350
  if (name) declareBinding(classScope, name, "class", node);
149
351
  declareNodeScopes(node.body, classScope, scopesByNode);
150
352
  return;
@@ -171,14 +373,14 @@ function declareUnknownValue(value, currentScope, scopesByNode) {
171
373
  declareNodeScopes(value, currentScope, scopesByNode);
172
374
  }
173
375
  function declareFunctionBindings(node, scope) {
174
- const functionName = getIdentifierName$1(node.id);
376
+ const functionName = getIdentifierName(node.id);
175
377
  if (functionName) declareBinding(scope, functionName, "function", node);
176
- for (const param of node.params) for (const name of collectBindingNamesFromPattern$1(param)) declareBinding(scope, name, "param", param);
378
+ for (const param of node.params) for (const name of collectBindingNamesFromPattern(param)) declareBinding(scope, name, "param", param);
177
379
  for (const name of collectFunctionScopedVarBindings(node.body)) declareBinding(scope, name, "var", node.body);
178
380
  }
179
381
  function declareBlockBindings(statements, scope) {
180
382
  for (const statement of statements) {
181
- const declaration = getStatementDeclaration$1(statement);
383
+ const declaration = getStatementDeclaration(statement);
182
384
  if (!declaration) continue;
183
385
  if (declaration.type === "VariableDeclaration" && declaration.kind === "var") continue;
184
386
  declareDeclarationBindings(scope, declaration);
@@ -186,7 +388,7 @@ function declareBlockBindings(statements, scope) {
186
388
  }
187
389
  function declareCatchBindings(node, scope) {
188
390
  if (!node.param) return;
189
- for (const name of collectBindingNamesFromPattern$1(node.param)) declareBinding(scope, name, "catch", node.param);
391
+ for (const name of collectBindingNamesFromPattern(node.param)) declareBinding(scope, name, "catch", node.param);
190
392
  }
191
393
  function declareSwitchBindings(cases, scope) {
192
394
  const statements = [];
@@ -195,28 +397,20 @@ function declareSwitchBindings(cases, scope) {
195
397
  }
196
398
  function declareDeclarationBindings(scope, declaration) {
197
399
  if (declaration.type === "FunctionDeclaration") {
198
- const name = getIdentifierName$1(declaration.id);
400
+ const name = getIdentifierName(declaration.id);
199
401
  if (name) declareBinding(scope, name, "function", declaration);
200
402
  return;
201
403
  }
202
404
  if (declaration.type === "ClassDeclaration") {
203
- const name = getIdentifierName$1(declaration.id);
405
+ const name = getIdentifierName(declaration.id);
204
406
  if (name) declareBinding(scope, name, "class", declaration);
205
407
  return;
206
408
  }
207
409
  if (declaration.type !== "VariableDeclaration") return;
208
- for (const declarator of declaration.declarations) for (const name of collectBindingNamesFromPattern$1(declarator.id)) declareBinding(scope, name, declaration.kind, declarator);
209
- }
210
- function declareBinding(scope, name, kind, node) {
211
- const binding = {
212
- kind,
213
- name,
214
- node,
215
- scope
216
- };
217
- scope.bindings.set(name, binding);
218
- return binding;
410
+ for (const declarator of declaration.declarations) for (const name of collectBindingNamesFromPattern(declarator.id)) declareBinding(scope, name, declaration.kind, declarator);
219
411
  }
412
+ //#endregion
413
+ //#region src/scope-analysis-references.ts
220
414
  function collectStatementReferences(statement, currentScope, scopesByNode, result, excludedNames) {
221
415
  if (statement.type === "ImportDeclaration") return;
222
416
  if (statement.type === "ExportNamedDeclaration") {
@@ -234,7 +428,7 @@ function collectStatementReferences(statement, currentScope, scopesByNode, resul
234
428
  function collectNodeReferences(node, currentScope, scopesByNode, result, excludedNames) {
235
429
  if (!node) return;
236
430
  if (node.type.startsWith("TS")) {
237
- collectTsRuntimeReferences(node, currentScope, scopesByNode, result, excludedNames);
431
+ for (const child of getTsRuntimeChildren(node)) collectNodeReferences(child, currentScope, scopesByNode, result, excludedNames);
238
432
  return;
239
433
  }
240
434
  switch (node.type) {
@@ -375,7 +569,7 @@ function collectVariableDeclaratorReferences(declarator, currentScope, scopesByN
375
569
  function collectPatternReferences(node, currentScope, scopesByNode, result, excludedNames) {
376
570
  if (!node) return;
377
571
  if (node.type.startsWith("TS")) {
378
- collectTsRuntimeReferences(node, currentScope, scopesByNode, result, excludedNames);
572
+ for (const child of getTsRuntimeChildren(node)) collectNodeReferences(child, currentScope, scopesByNode, result, excludedNames);
379
573
  return;
380
574
  }
381
575
  switch (node.type) {
@@ -423,111 +617,30 @@ function resolveBinding(name, currentScope) {
423
617
  }
424
618
  return null;
425
619
  }
426
- function getStatementDeclaration$1(statement) {
427
- if (statement.type === "ExportNamedDeclaration") return statement.declaration ?? null;
428
- if (statement.type === "ExportDefaultDeclaration" && (statement.declaration.type === "FunctionDeclaration" || statement.declaration.type === "ClassDeclaration")) return statement.declaration;
429
- if (statement.type === "FunctionDeclaration" || statement.type === "ClassDeclaration" || statement.type === "VariableDeclaration") return statement;
430
- return null;
431
- }
432
- function collectBindingNamesFromPattern$1(pattern) {
433
- if (!pattern) return [];
434
- switch (pattern.type) {
435
- case "Identifier": return [pattern.name];
436
- case "AssignmentPattern": return collectBindingNamesFromPattern$1(pattern.left);
437
- case "RestElement": return collectBindingNamesFromPattern$1(pattern.argument);
438
- case "ObjectPattern": return pattern.properties.flatMap((property) => {
439
- if (property.type === "Property") return collectBindingNamesFromPattern$1(property.value);
440
- return collectBindingNamesFromPattern$1(property.argument);
441
- });
442
- case "ArrayPattern": return pattern.elements.flatMap((element) => collectBindingNamesFromPattern$1(element));
443
- default: return [];
444
- }
445
- }
446
- function collectFunctionScopedVarBindings(node) {
447
- const names = /* @__PURE__ */ new Set();
448
- collectFunctionScopedVarBindingsInto(node, names);
449
- return names;
450
- }
451
- function collectFunctionScopedVarBindingsInto(node, names) {
452
- if (!node) return;
453
- if (node.type.startsWith("TS")) {
454
- collectFunctionScopedVarBindingsFromTsNode(node, names);
455
- return;
456
- }
457
- switch (node.type) {
458
- case "ArrowFunctionExpression":
459
- case "FunctionDeclaration":
460
- case "FunctionExpression":
461
- case "ClassDeclaration":
462
- case "ClassExpression": return;
463
- case "VariableDeclaration":
464
- if (node.kind === "var") for (const declarator of node.declarations) for (const name of collectBindingNamesFromPattern$1(declarator.id)) names.add(name);
465
- return;
466
- default: for (const [key, value] of Object.entries(node)) {
467
- if (SKIPPED_KEYS.has(key)) continue;
468
- if (key === "id" || key === "implements" || key === "superTypeArguments") continue;
469
- collectFunctionScopedVarBindingsFromUnknown(value, names);
470
- }
471
- }
472
- }
473
- function collectFunctionScopedVarBindingsFromUnknown(value, names) {
474
- if (Array.isArray(value)) {
475
- for (const item of value) collectFunctionScopedVarBindingsFromUnknown(item, names);
476
- return;
477
- }
478
- if (!isNode(value)) return;
479
- collectFunctionScopedVarBindingsInto(value, names);
480
- }
481
- function declareTsRuntimeChildren(node, currentScope, scopesByNode) {
482
- for (const child of getTsRuntimeChildren(node)) declareNodeScopes(child, currentScope, scopesByNode);
483
- }
484
- function collectTsRuntimeReferences(node, currentScope, scopesByNode, result, excludedNames) {
485
- for (const child of getTsRuntimeChildren(node)) collectNodeReferences(child, currentScope, scopesByNode, result, excludedNames);
486
- }
487
- function collectFunctionScopedVarBindingsFromTsNode(node, names) {
488
- for (const child of getTsRuntimeChildren(node)) collectFunctionScopedVarBindingsInto(child, names);
489
- }
490
- function getTsRuntimeChildren(node) {
491
- switch (node.type) {
492
- case "TSAsExpression":
493
- case "TSInstantiationExpression":
494
- case "TSNonNullExpression":
495
- case "TSSatisfiesExpression":
496
- case "TSTypeAssertion": return [node.expression];
497
- default: return [];
498
- }
499
- }
500
- function getIdentifierName$1(node) {
501
- if (!node) return null;
502
- if (node.type === "Identifier" || node.type === "JSXIdentifier") return node.name;
503
- if (node.type === "Literal" && typeof node.value === "string") return node.value;
504
- return null;
505
- }
506
- function isNode(value) {
507
- return !!value && typeof value === "object" && "type" in value;
620
+ //#endregion
621
+ //#region src/client-module-scope-analysis.ts
622
+ function analyzeRetainedStatements(statements, options = {}) {
623
+ const programScope = createScope("program", null, null);
624
+ for (const name of options.knownTopLevelNames ?? []) declareBinding(programScope, name, "placeholder", null);
625
+ const scopesByNode = /* @__PURE__ */ new WeakMap();
626
+ declareProgramScopes(statements, programScope, scopesByNode);
627
+ const result = {
628
+ programScope,
629
+ referencedTopLevelNames: /* @__PURE__ */ new Set(),
630
+ references: []
631
+ };
632
+ const excludedNames = new Set(options.excludedNames);
633
+ for (const statement of statements) collectStatementReferences(statement.node, programScope, scopesByNode, result, excludedNames);
634
+ return result;
508
635
  }
509
636
  //#endregion
510
637
  //#region src/client-module-transform.ts
511
- const CLIENT_MODULE_QUERY = "pracht-client";
512
638
  const SERVER_ONLY_EXPORTS = new Set([
513
639
  "loader",
514
640
  "head",
515
641
  "headers",
516
642
  "getStaticPaths"
517
643
  ]);
518
- const PRACHT_CLIENT_MODULE_QUERY = `?${CLIENT_MODULE_QUERY}`;
519
- function isPrachtClientModuleId(id) {
520
- const queryStart = id.indexOf("?");
521
- if (queryStart === -1) return false;
522
- return id.slice(queryStart + 1).split("&").includes(CLIENT_MODULE_QUERY);
523
- }
524
- function stripPrachtClientModuleQuery(id) {
525
- const queryStart = id.indexOf("?");
526
- if (queryStart === -1) return id;
527
- const path = id.slice(0, queryStart);
528
- const query = id.slice(queryStart + 1).split("&").filter((part) => part !== CLIENT_MODULE_QUERY);
529
- return query.length > 0 ? `${path}?${query.join("&")}` : path;
530
- }
531
644
  function stripServerOnlyExportsForClient(code, id = "pracht-client-route.tsx") {
532
645
  const states = createStatementStates(parseAst(code, { lang: getRolldownLang(id) }));
533
646
  const initialBindingNames = collectCurrentTopLevelBindingNames(states);
@@ -536,14 +649,6 @@ function stripServerOnlyExportsForClient(code, id = "pracht-client-route.tsx") {
536
649
  pruneDeadBindings(states, initialBindingNames, candidates);
537
650
  return renderProgram(code, states);
538
651
  }
539
- function createStatementStates(program) {
540
- return program.body.map((node) => ({
541
- node,
542
- removed: false,
543
- removedDeclarators: /* @__PURE__ */ new Set(),
544
- removedSpecifiers: /* @__PURE__ */ new Set()
545
- }));
546
- }
547
652
  function removeServerOnlyExports(states, initialBindingNames) {
548
653
  let changed = false;
549
654
  const candidates = /* @__PURE__ */ new Set();
@@ -759,64 +864,6 @@ function removeBinding(states, binding) {
759
864
  }
760
865
  state.removed = true;
761
866
  }
762
- function renderProgram(code, states) {
763
- let cursor = 0;
764
- let out = "";
765
- for (const state of states) {
766
- const statement = state.node;
767
- out += code.slice(cursor, statement.start);
768
- out += renderStatement(code, state);
769
- cursor = statement.end;
770
- }
771
- out += code.slice(cursor);
772
- return out;
773
- }
774
- function renderStatement(code, state) {
775
- if (state.removed) return "";
776
- const statement = state.node;
777
- const declaration = getStatementDeclaration(statement);
778
- if (statement.type === "ImportDeclaration" && state.removedSpecifiers.size > 0) return renderImportDeclaration(code, statement, state);
779
- if (statement.type === "ExportNamedDeclaration" && !statement.declaration && state.removedSpecifiers.size > 0) return renderExportSpecifiers(code, statement, state);
780
- if (declaration?.type === "VariableDeclaration" && state.removedDeclarators.size > 0) return renderVariableDeclaration(code, statement, declaration, state);
781
- return code.slice(statement.start, statement.end);
782
- }
783
- function renderImportDeclaration(code, statement, state) {
784
- const remaining = getRemainingSpecifierIndices(state).map((index) => statement.specifiers[index]);
785
- if (remaining.length === 0) return "";
786
- const defaultSpecifier = remaining.find((specifier) => specifier.type === "ImportDefaultSpecifier");
787
- const namespaceSpecifier = remaining.find((specifier) => specifier.type === "ImportNamespaceSpecifier");
788
- const namedSpecifiers = remaining.filter((specifier) => specifier.type === "ImportSpecifier");
789
- const clauseParts = [];
790
- if (defaultSpecifier) clauseParts.push(code.slice(defaultSpecifier.start, defaultSpecifier.end));
791
- if (namespaceSpecifier) clauseParts.push(code.slice(namespaceSpecifier.start, namespaceSpecifier.end));
792
- if (namedSpecifiers.length > 0) clauseParts.push(`{ ${namedSpecifiers.map((specifier) => code.slice(specifier.start, specifier.end)).join(", ")} }`);
793
- const importPrefix = ["import"];
794
- if (statement.importKind === "type") importPrefix.push("type");
795
- if (typeof statement.phase === "string" && statement.phase.length > 0) importPrefix.push(statement.phase);
796
- return `${importPrefix.join(" ")} ${clauseParts.join(", ")} from ${code.slice(statement.source.start, statement.end)}`;
797
- }
798
- function renderExportSpecifiers(code, statement, state) {
799
- const remaining = getRemainingSpecifierIndices(state).map((index) => statement.specifiers[index]);
800
- if (remaining.length === 0) return "";
801
- const exportPrefix = statement.exportKind === "type" ? "export type" : "export";
802
- const sourceSuffix = statement.source ? ` from ${code.slice(statement.source.start, statement.end)}` : ";";
803
- return `${exportPrefix} { ${remaining.map((specifier) => code.slice(specifier.start, specifier.end)).join(", ")} }${sourceSuffix}`;
804
- }
805
- function renderVariableDeclaration(code, statement, declaration, state) {
806
- const remaining = getRemainingDeclaratorIndices(state).map((index) => declaration.declarations[index]);
807
- if (remaining.length === 0) return "";
808
- return `${statement.type === "ExportNamedDeclaration" ? "export " : ""}${declaration.kind} ${remaining.map((item) => code.slice(item.start, item.end)).join(", ")};`;
809
- }
810
- function getRemainingDeclaratorIndices(state) {
811
- const declaration = getStatementDeclaration(state.node);
812
- if (!declaration || declaration.type !== "VariableDeclaration") return [];
813
- return declaration.declarations.map((_item, index) => index).filter((index) => !state.removedDeclarators.has(index));
814
- }
815
- function getRemainingSpecifierIndices(state) {
816
- const statement = state.node;
817
- if (!("specifiers" in statement) || !Array.isArray(statement.specifiers)) return [];
818
- return statement.specifiers.map((_item, index) => index).filter((index) => !state.removedSpecifiers.has(index));
819
- }
820
867
  function collectVariableDeclaratorDependencies(declarator, declarationKind, topLevelBindingNames, excludedNames) {
821
868
  return collectTopLevelReferences({
822
869
  declarations: [declarator],
@@ -832,88 +879,66 @@ function collectTopLevelReferences(node, topLevelBindingNames, excludedNames) {
832
879
  knownTopLevelNames: topLevelBindingNames
833
880
  }).referencedTopLevelNames;
834
881
  }
835
- function normalizeRetainedStatements(states) {
836
- return states.map((state) => normalizeRetainedStatement(state)).filter((state) => state !== null);
837
- }
838
- function normalizeRetainedStatement(state) {
839
- if (state.removed) return null;
840
- const statement = state.node;
841
- if (statement.type === "ImportDeclaration" && state.removedSpecifiers.size > 0) return { node: {
842
- ...statement,
843
- specifiers: getRemainingSpecifierIndices(state).map((index) => statement.specifiers[index])
844
- } };
845
- if (statement.type === "ExportNamedDeclaration" && !statement.declaration && state.removedSpecifiers.size > 0) return { node: {
846
- ...statement,
847
- specifiers: getRemainingSpecifierIndices(state).map((index) => statement.specifiers[index])
848
- } };
849
- const declaration = getStatementDeclaration(statement);
850
- if (declaration?.type === "VariableDeclaration" && state.removedDeclarators.size > 0) {
851
- const retainedDeclaration = {
852
- ...declaration,
853
- declarations: getRemainingDeclaratorIndices(state).map((index) => declaration.declarations[index])
854
- };
855
- if (statement.type === "ExportNamedDeclaration") return { node: {
856
- ...statement,
857
- declaration: retainedDeclaration
858
- } };
859
- return { node: retainedDeclaration };
860
- }
861
- return { node: statement };
862
- }
863
- function getStatementDeclaration(statement) {
864
- if (statement.type === "ExportNamedDeclaration") return statement.declaration ?? null;
865
- if (statement.type === "ExportDefaultDeclaration" && (statement.declaration.type === "FunctionDeclaration" || statement.declaration.type === "ClassDeclaration")) return statement.declaration;
866
- if (statement.type === "FunctionDeclaration" || statement.type === "ClassDeclaration" || statement.type === "VariableDeclaration") return statement;
867
- return null;
868
- }
869
- function collectBindingNamesFromDeclaration(declaration) {
870
- if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return declaration.id ? [declaration.id.name] : [];
871
- if (declaration.type === "VariableDeclaration") return declaration.declarations.flatMap((declarator) => collectBindingNamesFromPattern(declarator.id));
872
- return [];
873
- }
874
- function collectBindingNamesFromPattern(pattern) {
875
- if (!pattern) return [];
876
- switch (pattern.type) {
877
- case "Identifier": return [pattern.name];
878
- case "AssignmentPattern": return collectBindingNamesFromPattern(pattern.left);
879
- case "RestElement": return collectBindingNamesFromPattern(pattern.argument);
880
- case "ObjectPattern": return pattern.properties.flatMap((property) => {
881
- if (property.type === "Property") return collectBindingNamesFromPattern(property.value);
882
- return collectBindingNamesFromPattern(property.argument);
883
- });
884
- case "ArrayPattern": return pattern.elements.flatMap((element) => collectBindingNamesFromPattern(element));
885
- default: return [];
886
- }
887
- }
888
882
  function enqueueDependencies(target, dependencies) {
889
883
  for (const name of dependencies) target.add(name);
890
884
  }
891
- function getIdentifierName(node) {
892
- if (!node) return null;
893
- if (node.type === "Identifier" || node.type === "JSXIdentifier") return node.name;
894
- if (node.type === "Literal" && typeof node.value === "string") return node.value;
895
- return null;
896
- }
897
- function getRolldownLang(id) {
898
- const path = stripPrachtClientModuleQuery(id).split("?")[0];
899
- if (/\.(c|m)?tsx$/i.test(path)) return "tsx";
900
- if (/\.(c|m)?ts$/i.test(path)) return "ts";
901
- if (/\.(c|m)?jsx$/i.test(path)) return "jsx";
902
- if (/\.mdx?$/i.test(path)) return "jsx";
903
- if (/\.(c|m)?js$/i.test(path)) return "js";
904
- return "tsx";
905
- }
906
885
  //#endregion
907
- //#region src/index.ts
886
+ //#region src/plugin-assets.ts
908
887
  const PRACHT_CLIENT_MODULE_ID = "virtual:pracht/client";
909
888
  const PRACHT_SERVER_MODULE_ID = "virtual:pracht/server";
910
889
  const CLIENT_BROWSER_PATH = "/@pracht/client.js";
890
+ function readClientBuildAssets(root = process.cwd()) {
891
+ const manifestPath = ["dist/client/.vite/manifest.json", "dist/.vite/manifest.json"].map((candidate) => resolve(root, candidate)).find((candidate) => existsSync(candidate));
892
+ if (!manifestPath) return {
893
+ clientEntryUrl: null,
894
+ cssManifest: {},
895
+ jsManifest: {}
896
+ };
897
+ const rawManifest = readFileSync(manifestPath, "utf-8");
898
+ const manifest = JSON.parse(rawManifest);
899
+ const clientEntry = manifest[PRACHT_CLIENT_MODULE_ID];
900
+ const cssManifest = {};
901
+ const jsManifest = {};
902
+ for (const [key, entry] of Object.entries(manifest)) {
903
+ if (!entry.src) continue;
904
+ const deps = collectTransitiveDeps(manifest, key);
905
+ const manifestKey = stripPrachtClientModuleQuery(entry.src);
906
+ if (deps.css.length > 0) cssManifest[manifestKey] = deps.css.map((f) => `/${f}`);
907
+ if (deps.js.length > 0) jsManifest[manifestKey] = deps.js.map((f) => `/${f}`);
908
+ }
909
+ return {
910
+ clientEntryUrl: clientEntry ? `/${clientEntry.file}` : null,
911
+ cssManifest,
912
+ jsManifest
913
+ };
914
+ }
915
+ function collectTransitiveDeps(manifest, key) {
916
+ const css = /* @__PURE__ */ new Set();
917
+ const js = /* @__PURE__ */ new Set();
918
+ const visited = /* @__PURE__ */ new Set();
919
+ function collect(k) {
920
+ if (visited.has(k)) return;
921
+ visited.add(k);
922
+ const entry = manifest[k];
923
+ if (!entry) return;
924
+ for (const c of entry.css ?? []) css.add(c);
925
+ js.add(entry.file);
926
+ for (const imp of entry.imports ?? []) collect(imp);
927
+ }
928
+ collect(key);
929
+ return {
930
+ css: [...css],
931
+ js: [...js]
932
+ };
933
+ }
911
934
  function isClientModule(id) {
912
- return id === "virtual:pracht/client" || id === CLIENT_BROWSER_PATH || id.endsWith("virtual:pracht/client");
935
+ return id === "virtual:pracht/client" || id === "/@pracht/client.js" || id.endsWith("virtual:pracht/client");
913
936
  }
914
937
  function isServerModule(id) {
915
938
  return id === "virtual:pracht/server" || id.endsWith("virtual:pracht/server");
916
939
  }
940
+ //#endregion
941
+ //#region src/plugin-adapter.ts
917
942
  function createDefaultNodeAdapter() {
918
943
  return {
919
944
  id: "node",
@@ -957,6 +982,8 @@ function createDefaultNodeAdapter() {
957
982
  }
958
983
  };
959
984
  }
985
+ //#endregion
986
+ //#region src/plugin-options.ts
960
987
  const DEFAULTS = {
961
988
  appFile: "/src/routes.ts",
962
989
  middlewareDir: "/src/middleware",
@@ -968,115 +995,14 @@ const DEFAULTS = {
968
995
  pagesDir: "",
969
996
  pagesDefaultRender: "ssr"
970
997
  };
971
- async function pracht(options = {}) {
972
- const resolved = resolveOptions(options);
973
- const isPagesMode = !!resolved.pagesDir;
974
- let root = process.cwd();
975
- if (isPagesMode && options.appFile) console.warn("[pracht] Both `pagesDir` and `appFile` are set. `pagesDir` takes precedence — `appFile` will be ignored.");
976
- let isBuild = false;
977
- const prachtPlugin = {
978
- name: "pracht",
979
- enforce: "pre",
980
- config(_config, env) {
981
- const isEdge = resolved.adapter.id === "vercel" || resolved.adapter.id === "cloudflare";
982
- const isSSRBuild = env.isSsrBuild;
983
- return {
984
- appType: "custom",
985
- build: { rollupOptions: { output: { manualChunks(id) {
986
- if (id.includes("node_modules/preact") || id.includes("node_modules/preact-suspense")) return "vendor";
987
- } } } },
988
- ...isEdge && isSSRBuild ? { ssr: { noExternal: true } } : {}
989
- };
990
- },
991
- configResolved(config) {
992
- root = config.root;
993
- isBuild = config.command === "build";
994
- },
995
- resolveId(id) {
996
- if (isClientModule(id)) return PRACHT_CLIENT_MODULE_ID;
997
- if (isServerModule(id)) return PRACHT_SERVER_MODULE_ID;
998
- return null;
999
- },
1000
- load(id) {
1001
- if (isClientModule(id)) return createPrachtClientModuleSource(resolved, { root });
1002
- if (isServerModule(id)) return createPrachtServerModuleSource(resolved, {
1003
- root,
1004
- isBuild
1005
- });
1006
- return null;
1007
- },
1008
- transform(code, id) {
1009
- if (id !== resolve(root, resolved.appFile.slice(1))) return null;
1010
- const transformed = code.replace(/\(\)\s*=>\s*import\(\s*(['"])([^'"]+)\1\s*\)/g, "$1$2$1");
1011
- if (transformed === code) return null;
1012
- return {
1013
- code: transformed,
1014
- map: null
1015
- };
1016
- },
1017
- configureServer(server) {
1018
- if (isPagesMode) {
1019
- const abs = resolve(root, resolved.pagesDir.slice(1));
1020
- server.watcher.on("add", (f) => {
1021
- if (f.startsWith(abs)) server.restart();
1022
- });
1023
- server.watcher.on("unlink", (f) => {
1024
- if (f.startsWith(abs)) server.restart();
1025
- });
1026
- }
1027
- if (resolved.adapter.ownsDevServer) return;
1028
- return () => {
1029
- server.middlewares.use(createDevSSRMiddleware(server, resolved));
1030
- };
1031
- },
1032
- handleHotUpdate({ file, server }) {
1033
- const root = server.config.root;
1034
- const relative = file.startsWith(root) ? file.slice(root.length) : file;
1035
- if (isPagesMode && relative.startsWith(resolved.pagesDir)) {
1036
- const clientMod = server.moduleGraph.getModuleById(PRACHT_CLIENT_MODULE_ID);
1037
- const serverMod = server.moduleGraph.getModuleById(PRACHT_SERVER_MODULE_ID);
1038
- if (clientMod) server.moduleGraph.invalidateModule(clientMod);
1039
- if (serverMod) server.moduleGraph.invalidateModule(serverMod);
1040
- return;
1041
- }
1042
- if (!isPagesMode && relative === resolved.appFile) {
1043
- server.restart();
1044
- return [];
1045
- }
1046
- if ([
1047
- resolved.routesDir,
1048
- resolved.shellsDir,
1049
- resolved.middlewareDir,
1050
- resolved.apiDir,
1051
- resolved.serverDir
1052
- ].some((dir) => relative.startsWith(dir))) {
1053
- const serverMod = server.moduleGraph.getModuleById(PRACHT_SERVER_MODULE_ID);
1054
- if (serverMod) server.moduleGraph.invalidateModule(serverMod);
1055
- }
1056
- }
1057
- };
1058
- const clientModuleTransformPlugin = {
1059
- name: "pracht:client-module-transform",
1060
- enforce: "post",
1061
- transform(code, id) {
1062
- if (!isPrachtClientModuleId(id)) return null;
1063
- const transformed = stripServerOnlyExportsForClient(code, id);
1064
- if (transformed === code) return null;
1065
- return {
1066
- code: transformed,
1067
- map: null
1068
- };
1069
- }
998
+ function resolveOptions(options) {
999
+ return {
1000
+ ...DEFAULTS,
1001
+ ...options
1070
1002
  };
1071
- const plugins = [
1072
- ...preact(),
1073
- prachtPlugin,
1074
- clientModuleTransformPlugin
1075
- ];
1076
- const adapterPlugins = await resolved.adapter.vitePlugins?.();
1077
- if (adapterPlugins?.length) plugins.push(...adapterPlugins);
1078
- return plugins;
1079
1003
  }
1004
+ //#endregion
1005
+ //#region src/plugin-codegen.ts
1080
1006
  function createPrachtClientModuleSource(options = {}, buildOptions = {}) {
1081
1007
  const resolved = resolveOptions(options);
1082
1008
  const isPagesMode = !!resolved.pagesDir;
@@ -1140,7 +1066,7 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
1140
1066
  "export const resolvedApp = resolveApp(app);",
1141
1067
  `export const apiRoutes = resolveApiRoutes(Object.keys(apiModules), ${JSON.stringify(resolved.apiDir)});`,
1142
1068
  `export const buildTarget = ${JSON.stringify(adapter?.id ?? "node")};`,
1143
- `export const clientEntryUrl = ${JSON.stringify(clientBuild.clientEntryUrl ?? CLIENT_BROWSER_PATH)};`,
1069
+ `export const clientEntryUrl = ${JSON.stringify(clientBuild.clientEntryUrl ?? "/@pracht/client.js")};`,
1144
1070
  `export const cssManifest = ${JSON.stringify(clientBuild.cssManifest)};`,
1145
1071
  `export const jsManifest = ${JSON.stringify(clientBuild.jsManifest)};`,
1146
1072
  "export { prerenderApp };",
@@ -1178,7 +1104,11 @@ function generatePagesAppInlineSource(options, root = process.cwd()) {
1178
1104
  pagesDirPrefix: options.pagesDir
1179
1105
  });
1180
1106
  }
1181
- function createDevSSRMiddleware(server, _pluginOptions) {
1107
+ //#endregion
1108
+ //#region src/plugin-dev-ssr.ts
1109
+ const BODYLESS_METHODS = new Set(["GET", "HEAD"]);
1110
+ const MAX_BODY_SIZE = 1024 * 1024;
1111
+ function createDevSSRMiddleware(server) {
1182
1112
  return async (req, res, next) => {
1183
1113
  const url = req.url ?? "/";
1184
1114
  const pathname = new URL(url, "http://localhost").pathname;
@@ -1214,34 +1144,36 @@ function createDevSSRMiddleware(server, _pluginOptions) {
1214
1144
  });
1215
1145
  res.end(body);
1216
1146
  } catch (error) {
1217
- if (error instanceof Error) server.ssrFixStacktrace(error);
1218
- if (req.headers["x-pracht-route-state-request"] === "1") {
1219
- res.statusCode = 500;
1220
- res.setHeader("content-type", "application/json; charset=utf-8");
1221
- res.end(JSON.stringify({ error: {
1222
- message: error instanceof Error ? error.message : String(error),
1223
- name: error instanceof Error ? error.name : "Error",
1224
- status: 500
1225
- } }));
1226
- return;
1227
- }
1228
- try {
1229
- const { buildErrorOverlayHtml } = await server.ssrLoadModule("pracht/error-overlay");
1230
- let html = buildErrorOverlayHtml({
1231
- message: error instanceof Error ? error.message : String(error),
1232
- stack: error instanceof Error ? error.stack : void 0
1233
- });
1234
- html = await server.transformIndexHtml(url, html);
1235
- res.statusCode = 500;
1236
- res.setHeader("content-type", "text/html; charset=utf-8");
1237
- res.end(html);
1238
- } catch {
1239
- next(error);
1240
- }
1147
+ await handleDevError(server, req, res, next, url, error);
1241
1148
  }
1242
1149
  };
1243
1150
  }
1244
- const BODYLESS_METHODS = new Set(["GET", "HEAD"]);
1151
+ async function handleDevError(server, req, res, next, url, error) {
1152
+ if (error instanceof Error) server.ssrFixStacktrace(error);
1153
+ if (req.headers["x-pracht-route-state-request"] === "1") {
1154
+ res.statusCode = 500;
1155
+ res.setHeader("content-type", "application/json; charset=utf-8");
1156
+ res.end(JSON.stringify({ error: {
1157
+ message: error instanceof Error ? error.message : String(error),
1158
+ name: error instanceof Error ? error.name : "Error",
1159
+ status: 500
1160
+ } }));
1161
+ return;
1162
+ }
1163
+ try {
1164
+ const { buildErrorOverlayHtml } = await server.ssrLoadModule("pracht/error-overlay");
1165
+ let html = buildErrorOverlayHtml({
1166
+ message: error instanceof Error ? error.message : String(error),
1167
+ stack: error instanceof Error ? error.stack : void 0
1168
+ });
1169
+ html = await server.transformIndexHtml(url, html);
1170
+ res.statusCode = 500;
1171
+ res.setHeader("content-type", "text/html; charset=utf-8");
1172
+ res.end(html);
1173
+ } catch {
1174
+ next(error);
1175
+ }
1176
+ }
1245
1177
  async function nodeToWebRequest(req) {
1246
1178
  const protocol = "http";
1247
1179
  const host = req.headers.host ?? "localhost";
@@ -1258,7 +1190,6 @@ async function nodeToWebRequest(req) {
1258
1190
  headers
1259
1191
  };
1260
1192
  if (!BODYLESS_METHODS.has(method.toUpperCase())) {
1261
- const MAX_BODY_SIZE = 1024 * 1024;
1262
1193
  const chunks = [];
1263
1194
  let totalSize = 0;
1264
1195
  for await (const chunk of req) {
@@ -1275,55 +1206,151 @@ async function nodeToWebRequest(req) {
1275
1206
  }
1276
1207
  return new Request(url, init);
1277
1208
  }
1278
- function resolveOptions(options) {
1279
- return {
1280
- ...DEFAULTS,
1281
- ...options
1282
- };
1283
- }
1284
- function readClientBuildAssets(root = process.cwd()) {
1285
- const manifestPath = ["dist/client/.vite/manifest.json", "dist/.vite/manifest.json"].map((candidate) => resolve(root, candidate)).find((candidate) => existsSync(candidate));
1286
- if (!manifestPath) return {
1287
- clientEntryUrl: null,
1288
- cssManifest: {},
1289
- jsManifest: {}
1209
+ //#endregion
1210
+ //#region src/index.ts
1211
+ async function pracht(options = {}) {
1212
+ const resolved = resolveOptions(options);
1213
+ const isPagesMode = !!resolved.pagesDir;
1214
+ let root = process.cwd();
1215
+ let routeFileDirs = [];
1216
+ if (isPagesMode && options.appFile) console.warn("[pracht] Both `pagesDir` and `appFile` are set. `pagesDir` takes precedence — `appFile` will be ignored.");
1217
+ let isBuild = false;
1218
+ const prachtPlugin = {
1219
+ name: "pracht",
1220
+ enforce: "pre",
1221
+ config(_config, env) {
1222
+ const isEdge = resolved.adapter.edge === true;
1223
+ const isSSRBuild = env.isSsrBuild;
1224
+ return {
1225
+ appType: "custom",
1226
+ build: { rollupOptions: { output: { manualChunks(id) {
1227
+ if (id.includes("node_modules/preact") || id.includes("node_modules/preact-suspense")) return "vendor";
1228
+ } } } },
1229
+ ...isEdge && isSSRBuild ? { ssr: { noExternal: true } } : {}
1230
+ };
1231
+ },
1232
+ configResolved(config) {
1233
+ root = config.root;
1234
+ isBuild = config.command === "build";
1235
+ routeFileDirs = computeRouteFileDirs(root, resolved);
1236
+ },
1237
+ resolveId(id) {
1238
+ if (isClientModule(id)) return PRACHT_CLIENT_MODULE_ID;
1239
+ if (isServerModule(id)) return PRACHT_SERVER_MODULE_ID;
1240
+ return null;
1241
+ },
1242
+ load(id) {
1243
+ if (isClientModule(id)) return createPrachtClientModuleSource(resolved, { root });
1244
+ if (isServerModule(id)) return createPrachtServerModuleSource(resolved, {
1245
+ root,
1246
+ isBuild
1247
+ });
1248
+ return null;
1249
+ },
1250
+ transform(code, id) {
1251
+ if (id !== resolve(root, resolved.appFile.slice(1))) return null;
1252
+ const transformed = code.replace(/\(\)\s*=>\s*import\(\s*(['"])([^'"]+)\1\s*\)/g, "$1$2$1");
1253
+ if (transformed === code) return null;
1254
+ return {
1255
+ code: transformed,
1256
+ map: null
1257
+ };
1258
+ },
1259
+ configureServer(server) {
1260
+ if (isPagesMode) watchPagesDirectory(server, resolved, root);
1261
+ if (resolved.adapter.ownsDevServer) return;
1262
+ return () => {
1263
+ server.middlewares.use(createDevSSRMiddleware(server));
1264
+ };
1265
+ },
1266
+ handleHotUpdate({ file, server }) {
1267
+ const serverRoot = server.config.root;
1268
+ const relative = file.startsWith(serverRoot) ? file.slice(serverRoot.length) : file;
1269
+ if (isPagesMode && relative.startsWith(resolved.pagesDir)) {
1270
+ invalidateVirtualModules(server);
1271
+ return;
1272
+ }
1273
+ if (!isPagesMode && relative === resolved.appFile) {
1274
+ server.restart();
1275
+ return [];
1276
+ }
1277
+ if ([
1278
+ resolved.routesDir,
1279
+ resolved.shellsDir,
1280
+ resolved.middlewareDir,
1281
+ resolved.apiDir,
1282
+ resolved.serverDir
1283
+ ].some((dir) => relative.startsWith(dir))) {
1284
+ const serverMod = server.moduleGraph.getModuleById(PRACHT_SERVER_MODULE_ID);
1285
+ if (serverMod) server.moduleGraph.invalidateModule(serverMod);
1286
+ }
1287
+ }
1290
1288
  };
1291
- const rawManifest = readFileSync(manifestPath, "utf-8");
1292
- const manifest = JSON.parse(rawManifest);
1293
- const clientEntry = manifest[PRACHT_CLIENT_MODULE_ID];
1294
- function collectTransitiveDeps(key) {
1295
- const css = /* @__PURE__ */ new Set();
1296
- const js = /* @__PURE__ */ new Set();
1297
- const visited = /* @__PURE__ */ new Set();
1298
- function collect(k) {
1299
- if (visited.has(k)) return;
1300
- visited.add(k);
1301
- const entry = manifest[k];
1302
- if (!entry) return;
1303
- for (const c of entry.css ?? []) css.add(c);
1304
- js.add(entry.file);
1305
- for (const imp of entry.imports ?? []) collect(imp);
1289
+ const clientModuleTransformPlugin = {
1290
+ name: "pracht:client-module-transform",
1291
+ enforce: "post",
1292
+ transform(code, id, transformOptions) {
1293
+ if (!(isPrachtClientModuleId(id) || !transformOptions?.ssr && isRouteOrShellFile(id, routeFileDirs))) return null;
1294
+ const transformed = stripServerOnlyExportsForClient(code, id);
1295
+ if (transformed === code) return null;
1296
+ return {
1297
+ code: transformed,
1298
+ map: null
1299
+ };
1306
1300
  }
1307
- collect(key);
1308
- return {
1309
- css: [...css],
1310
- js: [...js]
1311
- };
1312
- }
1313
- const cssManifest = {};
1314
- const jsManifest = {};
1315
- for (const [key, entry] of Object.entries(manifest)) {
1316
- if (!entry.src) continue;
1317
- const deps = collectTransitiveDeps(key);
1318
- const manifestKey = stripPrachtClientModuleQuery(entry.src);
1319
- if (deps.css.length > 0) cssManifest[manifestKey] = deps.css.map((f) => `/${f}`);
1320
- if (deps.js.length > 0) jsManifest[manifestKey] = deps.js.map((f) => `/${f}`);
1321
- }
1322
- return {
1323
- clientEntryUrl: clientEntry ? `/${clientEntry.file}` : null,
1324
- cssManifest,
1325
- jsManifest
1326
1301
  };
1302
+ const plugins = [
1303
+ ...preact(),
1304
+ prachtPlugin,
1305
+ clientModuleTransformPlugin
1306
+ ];
1307
+ const adapterPlugins = await resolved.adapter.vitePlugins?.();
1308
+ if (adapterPlugins?.length) plugins.push(...adapterPlugins);
1309
+ return plugins;
1310
+ }
1311
+ function watchPagesDirectory(server, resolved, root) {
1312
+ const abs = resolve(root, resolved.pagesDir.slice(1));
1313
+ server.watcher.on("add", (f) => {
1314
+ if (f.startsWith(abs)) server.restart();
1315
+ });
1316
+ server.watcher.on("unlink", (f) => {
1317
+ if (f.startsWith(abs)) server.restart();
1318
+ });
1319
+ }
1320
+ function invalidateVirtualModules(server) {
1321
+ const clientMod = server.moduleGraph.getModuleById(PRACHT_CLIENT_MODULE_ID);
1322
+ const serverMod = server.moduleGraph.getModuleById(PRACHT_SERVER_MODULE_ID);
1323
+ if (clientMod) server.moduleGraph.invalidateModule(clientMod);
1324
+ if (serverMod) server.moduleGraph.invalidateModule(serverMod);
1325
+ }
1326
+ const ROUTE_FILE_EXTENSIONS = new Set([
1327
+ ".ts",
1328
+ ".tsx",
1329
+ ".js",
1330
+ ".jsx",
1331
+ ".md",
1332
+ ".mdx"
1333
+ ]);
1334
+ function computeRouteFileDirs(root, resolved) {
1335
+ return (resolved.pagesDir ? [resolved.pagesDir] : [resolved.routesDir, resolved.shellsDir]).map((dir) => toPosixPath(resolve(root, dir.replace(/^\//, "")))).map(withTrailingSep);
1336
+ }
1337
+ function isRouteOrShellFile(id, dirs) {
1338
+ if (dirs.length === 0) return false;
1339
+ const queryStart = id.indexOf("?");
1340
+ const path = queryStart === -1 ? id : id.slice(0, queryStart);
1341
+ if (path.startsWith("\0") || path.startsWith("virtual:")) return false;
1342
+ const extIndex = path.lastIndexOf(".");
1343
+ if (extIndex === -1) return false;
1344
+ const ext = path.slice(extIndex);
1345
+ if (!ROUTE_FILE_EXTENSIONS.has(ext)) return false;
1346
+ const normalized = toPosixPath(path);
1347
+ return dirs.some((dir) => normalized.startsWith(dir));
1348
+ }
1349
+ function toPosixPath(p) {
1350
+ return p.replace(/\\/g, "/");
1351
+ }
1352
+ function withTrailingSep(p) {
1353
+ return p.endsWith("/") ? p : `${p}/`;
1327
1354
  }
1328
1355
  //#endregion
1329
1356
  export { PRACHT_CLIENT_MODULE_ID, PRACHT_SERVER_MODULE_ID, createPrachtClientModuleSource, createPrachtRegistryModuleSource, createPrachtServerModuleSource, pracht };
@@ -1,5 +1,5 @@
1
- import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
2
1
  import { basename, extname, join, relative } from "node:path";
2
+ import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
3
3
  //#region src/pages-router.ts
4
4
  const PAGE_EXTENSIONS = new Set([
5
5
  ".tsx",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pracht/vite-plugin",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "homepage": "https://github.com/JoviDeCroock/pracht/tree/main/packages/vite-plugin",
6
6
  "bugs": {
@@ -31,8 +31,8 @@
31
31
  "dependencies": {
32
32
  "@preact/preset-vite": "^2.10.5",
33
33
  "@prefresh/vite": "^2.0.0",
34
- "@pracht/adapter-node": "0.1.8",
35
- "@pracht/core": "0.2.7"
34
+ "@pracht/adapter-node": "0.1.9",
35
+ "@pracht/core": "0.3.0"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "vite": "^8.0.0"