@pracht/vite-plugin 0.2.3 → 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.mjs CHANGED
@@ -1,17 +1,944 @@
1
1
  import { generatePagesManifestSource, scanPagesDirectory } from "./pages-router.mjs";
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { resolve } from "node:path";
4
2
  import preact from "@preact/preset-vite";
5
- //#region src/index.ts
3
+ import { resolve } from "node:path";
4
+ import { parseAst } from "vite";
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
32
+ const JSX_COMPONENT_RE = /^[A-Z]/;
33
+ const SKIPPED_KEYS = new Set([
34
+ "attributes",
35
+ "decorators",
36
+ "end",
37
+ "exportKind",
38
+ "importKind",
39
+ "optional",
40
+ "phase",
41
+ "raw",
42
+ "returnType",
43
+ "start",
44
+ "superTypeArguments",
45
+ "type",
46
+ "typeAnnotation",
47
+ "typeArguments",
48
+ "typeParameters",
49
+ "value"
50
+ ]);
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}`;
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
232
+ function createScope(type, parent, node) {
233
+ return {
234
+ bindings: /* @__PURE__ */ new Map(),
235
+ node,
236
+ parent,
237
+ type
238
+ };
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
+ }
250
+ function declareProgramScopes(statements, programScope, scopesByNode) {
251
+ for (const statement of statements) declareTopLevelStatement(statement.node, programScope);
252
+ for (const statement of statements) declareNodeScopes(statement.node, programScope, scopesByNode);
253
+ }
254
+ function declareTopLevelStatement(statement, programScope) {
255
+ if (statement.type === "ImportDeclaration") {
256
+ if (statement.importKind === "type") return;
257
+ for (const specifier of statement.specifiers) {
258
+ if (specifier.type === "ImportSpecifier" && specifier.importKind === "type") continue;
259
+ const localName = getIdentifierName(specifier.local);
260
+ if (localName) declareBinding(programScope, localName, "import", specifier);
261
+ }
262
+ return;
263
+ }
264
+ const declaration = getStatementDeclaration(statement);
265
+ if (!declaration) return;
266
+ declareDeclarationBindings(programScope, declaration);
267
+ }
268
+ function declareNodeScopes(node, currentScope, scopesByNode) {
269
+ if (!node) return;
270
+ if (node.type.startsWith("TS")) {
271
+ for (const child of getTsRuntimeChildren(node)) declareNodeScopes(child, currentScope, scopesByNode);
272
+ return;
273
+ }
274
+ switch (node.type) {
275
+ case "ImportDeclaration": return;
276
+ case "ArrowFunctionExpression":
277
+ case "FunctionDeclaration":
278
+ case "FunctionExpression": {
279
+ const functionScope = createScope("function", currentScope, node);
280
+ scopesByNode.set(node, functionScope);
281
+ declareFunctionBindings(node, functionScope);
282
+ for (const param of node.params) declareNodeScopes(param, functionScope, scopesByNode);
283
+ declareNodeScopes(node.body, functionScope, scopesByNode);
284
+ return;
285
+ }
286
+ case "BlockStatement": {
287
+ const blockScope = createScope("block", currentScope, node);
288
+ scopesByNode.set(node, blockScope);
289
+ declareBlockBindings(node.body, blockScope);
290
+ for (const statement of node.body) declareNodeScopes(statement, blockScope, scopesByNode);
291
+ return;
292
+ }
293
+ case "CatchClause": {
294
+ const catchScope = createScope("catch", currentScope, node);
295
+ scopesByNode.set(node, catchScope);
296
+ declareCatchBindings(node, catchScope);
297
+ if (node.param) declareNodeScopes(node.param, catchScope, scopesByNode);
298
+ declareNodeScopes(node.body, catchScope, scopesByNode);
299
+ return;
300
+ }
301
+ case "ForStatement": {
302
+ const init = node.init;
303
+ if (init?.type === "VariableDeclaration" && init.kind !== "var") {
304
+ const loopScope = createScope("for", currentScope, node);
305
+ scopesByNode.set(node, loopScope);
306
+ declareDeclarationBindings(loopScope, init);
307
+ declareNodeScopes(init, loopScope, scopesByNode);
308
+ declareNodeScopes(node.test, loopScope, scopesByNode);
309
+ declareNodeScopes(node.update, loopScope, scopesByNode);
310
+ declareNodeScopes(node.body, loopScope, scopesByNode);
311
+ return;
312
+ }
313
+ declareNodeScopes(init, currentScope, scopesByNode);
314
+ declareNodeScopes(node.test, currentScope, scopesByNode);
315
+ declareNodeScopes(node.update, currentScope, scopesByNode);
316
+ declareNodeScopes(node.body, currentScope, scopesByNode);
317
+ return;
318
+ }
319
+ case "ForInStatement":
320
+ case "ForOfStatement": {
321
+ const left = node.left;
322
+ if (left?.type === "VariableDeclaration" && left.kind !== "var") {
323
+ const loopScope = createScope("for", currentScope, node);
324
+ scopesByNode.set(node, loopScope);
325
+ declareDeclarationBindings(loopScope, left);
326
+ declareNodeScopes(left, loopScope, scopesByNode);
327
+ declareNodeScopes(node.right, loopScope, scopesByNode);
328
+ declareNodeScopes(node.body, loopScope, scopesByNode);
329
+ return;
330
+ }
331
+ declareNodeScopes(left, currentScope, scopesByNode);
332
+ declareNodeScopes(node.right, currentScope, scopesByNode);
333
+ declareNodeScopes(node.body, currentScope, scopesByNode);
334
+ return;
335
+ }
336
+ case "SwitchStatement": {
337
+ declareNodeScopes(node.discriminant, currentScope, scopesByNode);
338
+ const switchScope = createScope("switch", currentScope, node);
339
+ scopesByNode.set(node, switchScope);
340
+ declareSwitchBindings(node.cases, switchScope);
341
+ for (const switchCase of node.cases) declareNodeScopes(switchCase, switchScope, scopesByNode);
342
+ return;
343
+ }
344
+ case "ClassDeclaration":
345
+ case "ClassExpression": {
346
+ declareNodeScopes(node.superClass, currentScope, scopesByNode);
347
+ const classScope = createScope("class", currentScope, node);
348
+ scopesByNode.set(node, classScope);
349
+ const name = getIdentifierName(node.id);
350
+ if (name) declareBinding(classScope, name, "class", node);
351
+ declareNodeScopes(node.body, classScope, scopesByNode);
352
+ return;
353
+ }
354
+ case "ExportNamedDeclaration":
355
+ if (node.declaration) declareNodeScopes(node.declaration, currentScope, scopesByNode);
356
+ return;
357
+ case "ExportDefaultDeclaration":
358
+ if (node.declaration.type !== "Identifier") declareNodeScopes(node.declaration, currentScope, scopesByNode);
359
+ return;
360
+ default: for (const [key, value] of Object.entries(node)) {
361
+ if (SKIPPED_KEYS.has(key)) continue;
362
+ if (key === "id" || key === "implements" || key === "superTypeArguments") continue;
363
+ declareUnknownValue(value, currentScope, scopesByNode);
364
+ }
365
+ }
366
+ }
367
+ function declareUnknownValue(value, currentScope, scopesByNode) {
368
+ if (Array.isArray(value)) {
369
+ for (const item of value) declareUnknownValue(item, currentScope, scopesByNode);
370
+ return;
371
+ }
372
+ if (!isNode(value)) return;
373
+ declareNodeScopes(value, currentScope, scopesByNode);
374
+ }
375
+ function declareFunctionBindings(node, scope) {
376
+ const functionName = getIdentifierName(node.id);
377
+ if (functionName) declareBinding(scope, functionName, "function", node);
378
+ for (const param of node.params) for (const name of collectBindingNamesFromPattern(param)) declareBinding(scope, name, "param", param);
379
+ for (const name of collectFunctionScopedVarBindings(node.body)) declareBinding(scope, name, "var", node.body);
380
+ }
381
+ function declareBlockBindings(statements, scope) {
382
+ for (const statement of statements) {
383
+ const declaration = getStatementDeclaration(statement);
384
+ if (!declaration) continue;
385
+ if (declaration.type === "VariableDeclaration" && declaration.kind === "var") continue;
386
+ declareDeclarationBindings(scope, declaration);
387
+ }
388
+ }
389
+ function declareCatchBindings(node, scope) {
390
+ if (!node.param) return;
391
+ for (const name of collectBindingNamesFromPattern(node.param)) declareBinding(scope, name, "catch", node.param);
392
+ }
393
+ function declareSwitchBindings(cases, scope) {
394
+ const statements = [];
395
+ for (const switchCase of cases) for (const statement of switchCase.consequent) statements.push(statement);
396
+ declareBlockBindings(statements, scope);
397
+ }
398
+ function declareDeclarationBindings(scope, declaration) {
399
+ if (declaration.type === "FunctionDeclaration") {
400
+ const name = getIdentifierName(declaration.id);
401
+ if (name) declareBinding(scope, name, "function", declaration);
402
+ return;
403
+ }
404
+ if (declaration.type === "ClassDeclaration") {
405
+ const name = getIdentifierName(declaration.id);
406
+ if (name) declareBinding(scope, name, "class", declaration);
407
+ return;
408
+ }
409
+ if (declaration.type !== "VariableDeclaration") return;
410
+ for (const declarator of declaration.declarations) for (const name of collectBindingNamesFromPattern(declarator.id)) declareBinding(scope, name, declaration.kind, declarator);
411
+ }
412
+ //#endregion
413
+ //#region src/scope-analysis-references.ts
414
+ function collectStatementReferences(statement, currentScope, scopesByNode, result, excludedNames) {
415
+ if (statement.type === "ImportDeclaration") return;
416
+ if (statement.type === "ExportNamedDeclaration") {
417
+ const declaration = statement.declaration;
418
+ if (declaration) collectNodeReferences(declaration, currentScope, scopesByNode, result, excludedNames);
419
+ return;
420
+ }
421
+ if (statement.type === "ExportDefaultDeclaration") {
422
+ const declaration = statement.declaration;
423
+ if (declaration.type !== "Identifier") collectNodeReferences(declaration, currentScope, scopesByNode, result, excludedNames);
424
+ return;
425
+ }
426
+ collectNodeReferences(statement, currentScope, scopesByNode, result, excludedNames);
427
+ }
428
+ function collectNodeReferences(node, currentScope, scopesByNode, result, excludedNames) {
429
+ if (!node) return;
430
+ if (node.type.startsWith("TS")) {
431
+ for (const child of getTsRuntimeChildren(node)) collectNodeReferences(child, currentScope, scopesByNode, result, excludedNames);
432
+ return;
433
+ }
434
+ switch (node.type) {
435
+ case "Identifier":
436
+ recordReference(node.name, node, currentScope, result, excludedNames);
437
+ return;
438
+ case "JSXIdentifier":
439
+ if (JSX_COMPONENT_RE.test(node.name)) recordReference(node.name, node, currentScope, result, excludedNames);
440
+ return;
441
+ case "ArrowFunctionExpression":
442
+ case "FunctionDeclaration":
443
+ case "FunctionExpression": {
444
+ const functionScope = scopesByNode.get(node) ?? currentScope;
445
+ for (const param of node.params) collectPatternReferences(param, functionScope, scopesByNode, result, excludedNames);
446
+ collectNodeReferences(node.body, functionScope, scopesByNode, result, excludedNames);
447
+ return;
448
+ }
449
+ case "BlockStatement": {
450
+ const blockScope = scopesByNode.get(node) ?? currentScope;
451
+ for (const statement of node.body) collectStatementReferences(statement, blockScope, scopesByNode, result, excludedNames);
452
+ return;
453
+ }
454
+ case "CatchClause": {
455
+ const catchScope = scopesByNode.get(node) ?? currentScope;
456
+ if (node.param) collectPatternReferences(node.param, catchScope, scopesByNode, result, excludedNames);
457
+ collectNodeReferences(node.body, catchScope, scopesByNode, result, excludedNames);
458
+ return;
459
+ }
460
+ case "ForStatement": {
461
+ const loopScope = scopesByNode.get(node) ?? currentScope;
462
+ collectNodeReferences(node.init, loopScope, scopesByNode, result, excludedNames);
463
+ collectNodeReferences(node.test, loopScope, scopesByNode, result, excludedNames);
464
+ collectNodeReferences(node.update, loopScope, scopesByNode, result, excludedNames);
465
+ collectNodeReferences(node.body, loopScope, scopesByNode, result, excludedNames);
466
+ return;
467
+ }
468
+ case "ForInStatement":
469
+ case "ForOfStatement": {
470
+ const loopScope = scopesByNode.get(node) ?? currentScope;
471
+ collectNodeReferences(node.left, loopScope, scopesByNode, result, excludedNames);
472
+ collectNodeReferences(node.right, loopScope, scopesByNode, result, excludedNames);
473
+ collectNodeReferences(node.body, loopScope, scopesByNode, result, excludedNames);
474
+ return;
475
+ }
476
+ case "SwitchStatement": {
477
+ collectNodeReferences(node.discriminant, currentScope, scopesByNode, result, excludedNames);
478
+ const switchScope = scopesByNode.get(node) ?? currentScope;
479
+ for (const switchCase of node.cases) {
480
+ collectNodeReferences(switchCase.test, switchScope, scopesByNode, result, excludedNames);
481
+ for (const statement of switchCase.consequent) collectStatementReferences(statement, switchScope, scopesByNode, result, excludedNames);
482
+ }
483
+ return;
484
+ }
485
+ case "ClassDeclaration":
486
+ case "ClassExpression": {
487
+ collectNodeReferences(node.superClass, currentScope, scopesByNode, result, excludedNames);
488
+ const classScope = scopesByNode.get(node) ?? currentScope;
489
+ collectNodeReferences(node.body, classScope, scopesByNode, result, excludedNames);
490
+ return;
491
+ }
492
+ case "VariableDeclaration":
493
+ for (const declarator of node.declarations) collectVariableDeclaratorReferences(declarator, currentScope, scopesByNode, result, excludedNames);
494
+ return;
495
+ case "MemberExpression":
496
+ collectNodeReferences(node.object, currentScope, scopesByNode, result, excludedNames);
497
+ if (node.computed) collectNodeReferences(node.property, currentScope, scopesByNode, result, excludedNames);
498
+ return;
499
+ case "MetaProperty": return;
500
+ case "LabeledStatement":
501
+ collectNodeReferences(node.body, currentScope, scopesByNode, result, excludedNames);
502
+ return;
503
+ case "BreakStatement":
504
+ case "ContinueStatement": return;
505
+ case "Property":
506
+ if (node.computed) collectNodeReferences(node.key, currentScope, scopesByNode, result, excludedNames);
507
+ collectNodeReferences(node.value, currentScope, scopesByNode, result, excludedNames);
508
+ return;
509
+ case "ObjectPattern":
510
+ case "ArrayPattern":
511
+ case "AssignmentPattern":
512
+ case "RestElement":
513
+ collectPatternReferences(node, currentScope, scopesByNode, result, excludedNames);
514
+ return;
515
+ case "JSXElement":
516
+ collectNodeReferences(node.openingElement, currentScope, scopesByNode, result, excludedNames);
517
+ for (const child of node.children) collectNodeReferences(child, currentScope, scopesByNode, result, excludedNames);
518
+ return;
519
+ case "JSXFragment":
520
+ for (const child of node.children) collectNodeReferences(child, currentScope, scopesByNode, result, excludedNames);
521
+ return;
522
+ case "JSXOpeningElement":
523
+ collectNodeReferences(node.name, currentScope, scopesByNode, result, excludedNames);
524
+ for (const attribute of node.attributes) collectNodeReferences(attribute, currentScope, scopesByNode, result, excludedNames);
525
+ return;
526
+ case "JSXClosingElement":
527
+ collectNodeReferences(node.name, currentScope, scopesByNode, result, excludedNames);
528
+ return;
529
+ case "JSXAttribute":
530
+ collectNodeReferences(node.value, currentScope, scopesByNode, result, excludedNames);
531
+ return;
532
+ case "JSXExpressionContainer":
533
+ collectNodeReferences(node.expression, currentScope, scopesByNode, result, excludedNames);
534
+ return;
535
+ case "JSXMemberExpression":
536
+ collectNodeReferences(node.object, currentScope, scopesByNode, result, excludedNames);
537
+ return;
538
+ case "MethodDefinition":
539
+ case "PropertyDefinition":
540
+ if (node.computed) collectNodeReferences(node.key, currentScope, scopesByNode, result, excludedNames);
541
+ collectNodeReferences(node.value, currentScope, scopesByNode, result, excludedNames);
542
+ return;
543
+ case "ImportDeclaration": return;
544
+ case "ExportNamedDeclaration":
545
+ if (node.declaration) collectNodeReferences(node.declaration, currentScope, scopesByNode, result, excludedNames);
546
+ return;
547
+ case "ExportDefaultDeclaration":
548
+ if (node.declaration.type !== "Identifier") collectNodeReferences(node.declaration, currentScope, scopesByNode, result, excludedNames);
549
+ return;
550
+ default: for (const [key, value] of Object.entries(node)) {
551
+ if (SKIPPED_KEYS.has(key)) continue;
552
+ if (key === "id" || key === "implements" || key === "superTypeArguments") continue;
553
+ collectUnknownValueReferences(value, currentScope, scopesByNode, result, excludedNames);
554
+ }
555
+ }
556
+ }
557
+ function collectUnknownValueReferences(value, currentScope, scopesByNode, result, excludedNames) {
558
+ if (Array.isArray(value)) {
559
+ for (const item of value) collectUnknownValueReferences(item, currentScope, scopesByNode, result, excludedNames);
560
+ return;
561
+ }
562
+ if (!isNode(value)) return;
563
+ collectNodeReferences(value, currentScope, scopesByNode, result, excludedNames);
564
+ }
565
+ function collectVariableDeclaratorReferences(declarator, currentScope, scopesByNode, result, excludedNames) {
566
+ collectPatternReferences(declarator.id, currentScope, scopesByNode, result, excludedNames);
567
+ collectNodeReferences(declarator.init, currentScope, scopesByNode, result, excludedNames);
568
+ }
569
+ function collectPatternReferences(node, currentScope, scopesByNode, result, excludedNames) {
570
+ if (!node) return;
571
+ if (node.type.startsWith("TS")) {
572
+ for (const child of getTsRuntimeChildren(node)) collectNodeReferences(child, currentScope, scopesByNode, result, excludedNames);
573
+ return;
574
+ }
575
+ switch (node.type) {
576
+ case "AssignmentPattern":
577
+ collectNodeReferences(node.right, currentScope, scopesByNode, result, excludedNames);
578
+ collectPatternReferences(node.left, currentScope, scopesByNode, result, excludedNames);
579
+ return;
580
+ case "ObjectPattern":
581
+ for (const property of node.properties) {
582
+ if (property.type === "Property") {
583
+ if (property.computed) collectNodeReferences(property.key, currentScope, scopesByNode, result, excludedNames);
584
+ collectPatternReferences(property.value, currentScope, scopesByNode, result, excludedNames);
585
+ continue;
586
+ }
587
+ collectPatternReferences(property.argument, currentScope, scopesByNode, result, excludedNames);
588
+ }
589
+ return;
590
+ case "ArrayPattern":
591
+ for (const element of node.elements) collectPatternReferences(element, currentScope, scopesByNode, result, excludedNames);
592
+ return;
593
+ case "RestElement":
594
+ collectPatternReferences(node.argument, currentScope, scopesByNode, result, excludedNames);
595
+ return;
596
+ default: return;
597
+ }
598
+ }
599
+ function recordReference(name, node, currentScope, result, excludedNames) {
600
+ const resolvedBinding = resolveBinding(name, currentScope);
601
+ result.references.push({
602
+ name,
603
+ node,
604
+ resolvedBinding
605
+ });
606
+ if (!resolvedBinding) return;
607
+ if (resolvedBinding.scope.type !== "program") return;
608
+ if (excludedNames.has(name)) return;
609
+ result.referencedTopLevelNames.add(name);
610
+ }
611
+ function resolveBinding(name, currentScope) {
612
+ let scope = currentScope;
613
+ while (scope) {
614
+ const binding = scope.bindings.get(name);
615
+ if (binding) return binding;
616
+ scope = scope.parent;
617
+ }
618
+ return null;
619
+ }
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;
635
+ }
636
+ //#endregion
637
+ //#region src/client-module-transform.ts
638
+ const SERVER_ONLY_EXPORTS = new Set([
639
+ "loader",
640
+ "head",
641
+ "headers",
642
+ "getStaticPaths"
643
+ ]);
644
+ function stripServerOnlyExportsForClient(code, id = "pracht-client-route.tsx") {
645
+ const states = createStatementStates(parseAst(code, { lang: getRolldownLang(id) }));
646
+ const initialBindingNames = collectCurrentTopLevelBindingNames(states);
647
+ const { changed, candidates } = removeServerOnlyExports(states, initialBindingNames);
648
+ if (!changed) return code;
649
+ pruneDeadBindings(states, initialBindingNames, candidates);
650
+ return renderProgram(code, states);
651
+ }
652
+ function removeServerOnlyExports(states, initialBindingNames) {
653
+ let changed = false;
654
+ const candidates = /* @__PURE__ */ new Set();
655
+ for (const state of states) {
656
+ const statement = state.node;
657
+ if (statement.type !== "ExportNamedDeclaration" || statement.exportKind === "type") continue;
658
+ const declaration = statement.declaration;
659
+ if (declaration?.type === "FunctionDeclaration") {
660
+ const name = declaration.id?.name;
661
+ if (!name || !SERVER_ONLY_EXPORTS.has(name)) continue;
662
+ changed = true;
663
+ state.removed = true;
664
+ enqueueDependencies(candidates, collectTopLevelReferences(declaration, initialBindingNames, new Set([name])));
665
+ continue;
666
+ }
667
+ if (declaration?.type === "VariableDeclaration") {
668
+ const removable = getRemainingDeclaratorIndices(state).filter((index) => collectBindingNamesFromPattern(declaration.declarations[index].id).some((name) => SERVER_ONLY_EXPORTS.has(name)));
669
+ if (removable.length === 0) continue;
670
+ changed = true;
671
+ for (const index of removable) {
672
+ const declarator = declaration.declarations[index];
673
+ const declaredNames = new Set(collectBindingNamesFromPattern(declarator.id));
674
+ enqueueDependencies(candidates, collectVariableDeclaratorDependencies(declarator, declaration.kind, initialBindingNames, declaredNames));
675
+ state.removedDeclarators.add(index);
676
+ }
677
+ if (getRemainingDeclaratorIndices(state).length === 0) state.removed = true;
678
+ continue;
679
+ }
680
+ const removableSpecifiers = getRemainingSpecifierIndices(state).filter((index) => {
681
+ const specifier = statement.specifiers[index];
682
+ if (specifier.type !== "ExportSpecifier" || specifier.exportKind === "type") return false;
683
+ const localName = getIdentifierName(specifier.local);
684
+ const exportedName = getIdentifierName(specifier.exported);
685
+ return SERVER_ONLY_EXPORTS.has(localName ?? "") || SERVER_ONLY_EXPORTS.has(exportedName ?? "");
686
+ });
687
+ if (removableSpecifiers.length === 0) continue;
688
+ changed = true;
689
+ for (const index of removableSpecifiers) {
690
+ const specifier = statement.specifiers[index];
691
+ if (!statement.source) {
692
+ const localName = getIdentifierName(specifier.local);
693
+ if (localName) candidates.add(localName);
694
+ }
695
+ state.removedSpecifiers.add(index);
696
+ }
697
+ if (getRemainingSpecifierIndices(state).length === 0) state.removed = true;
698
+ }
699
+ return {
700
+ changed,
701
+ candidates
702
+ };
703
+ }
704
+ function pruneDeadBindings(states, initialBindingNames, candidates) {
705
+ let changed = true;
706
+ while (changed) {
707
+ changed = false;
708
+ const bindings = collectTopLevelBindings(states, initialBindingNames);
709
+ const exportedNames = collectExportedBindingNames(states);
710
+ const referencedNames = collectProgramReferences(states);
711
+ const pendingNames = Array.from(candidates);
712
+ for (const name of pendingNames) {
713
+ const binding = bindings.get(name);
714
+ if (!binding) continue;
715
+ if (exportedNames.has(name) || referencedNames.has(name)) continue;
716
+ removeBinding(states, binding);
717
+ enqueueDependencies(candidates, binding.dependencies);
718
+ changed = true;
719
+ }
720
+ }
721
+ }
722
+ function collectTopLevelBindings(states, dependencyBindingNames) {
723
+ const bindings = /* @__PURE__ */ new Map();
724
+ for (const [statementIndex, state] of states.entries()) {
725
+ if (state.removed) continue;
726
+ const statement = state.node;
727
+ if (statement.type === "ImportDeclaration") {
728
+ if (statement.importKind === "type") continue;
729
+ for (const index of getRemainingSpecifierIndices(state)) {
730
+ const specifier = statement.specifiers[index];
731
+ if (specifier.type === "ImportSpecifier" && specifier.importKind === "type") continue;
732
+ const local = specifier.local;
733
+ const name = getIdentifierName(local);
734
+ if (!name) continue;
735
+ const info = {
736
+ dependencies: /* @__PURE__ */ new Set(),
737
+ kind: "import",
738
+ names: new Set([name]),
739
+ node: specifier,
740
+ specifierIndex: index,
741
+ statementIndex
742
+ };
743
+ bindings.set(name, info);
744
+ }
745
+ continue;
746
+ }
747
+ const declaration = getStatementDeclaration(statement);
748
+ if (!declaration) continue;
749
+ if (declaration.type === "FunctionDeclaration") {
750
+ const name = getIdentifierName(declaration.id);
751
+ if (!name) continue;
752
+ const info = {
753
+ dependencies: collectTopLevelReferences(declaration, dependencyBindingNames, new Set([name])),
754
+ kind: "function",
755
+ names: new Set([name]),
756
+ node: declaration,
757
+ statementIndex
758
+ };
759
+ bindings.set(name, info);
760
+ continue;
761
+ }
762
+ if (declaration.type === "ClassDeclaration") {
763
+ const name = getIdentifierName(declaration.id);
764
+ if (!name) continue;
765
+ const info = {
766
+ dependencies: collectTopLevelReferences(declaration, dependencyBindingNames, new Set([name])),
767
+ kind: "class",
768
+ names: new Set([name]),
769
+ node: declaration,
770
+ statementIndex
771
+ };
772
+ bindings.set(name, info);
773
+ continue;
774
+ }
775
+ if (declaration.type !== "VariableDeclaration") continue;
776
+ for (const index of getRemainingDeclaratorIndices(state)) {
777
+ const declarator = declaration.declarations[index];
778
+ const names = new Set(collectBindingNamesFromPattern(declarator.id));
779
+ if (names.size === 0) continue;
780
+ const info = {
781
+ declaratorIndex: index,
782
+ dependencies: collectVariableDeclaratorDependencies(declarator, declaration.kind, dependencyBindingNames, names),
783
+ kind: "variable",
784
+ names,
785
+ node: declarator,
786
+ statementIndex
787
+ };
788
+ for (const name of names) bindings.set(name, info);
789
+ }
790
+ }
791
+ return bindings;
792
+ }
793
+ function collectCurrentTopLevelBindingNames(states) {
794
+ const names = /* @__PURE__ */ new Set();
795
+ for (const state of states) {
796
+ if (state.removed) continue;
797
+ const statement = state.node;
798
+ if (statement.type === "ImportDeclaration") {
799
+ if (statement.importKind === "type") continue;
800
+ for (const index of getRemainingSpecifierIndices(state)) {
801
+ const specifier = statement.specifiers[index];
802
+ if (specifier.type === "ImportSpecifier" && specifier.importKind === "type") continue;
803
+ const localName = getIdentifierName(specifier.local);
804
+ if (localName) names.add(localName);
805
+ }
806
+ continue;
807
+ }
808
+ const declaration = getStatementDeclaration(statement);
809
+ if (!declaration) continue;
810
+ if (declaration.type === "VariableDeclaration") {
811
+ for (const index of getRemainingDeclaratorIndices(state)) {
812
+ const declarator = declaration.declarations[index];
813
+ for (const name of collectBindingNamesFromPattern(declarator.id)) names.add(name);
814
+ }
815
+ continue;
816
+ }
817
+ for (const name of collectBindingNamesFromDeclaration(declaration)) names.add(name);
818
+ }
819
+ return names;
820
+ }
821
+ function collectExportedBindingNames(states) {
822
+ const names = /* @__PURE__ */ new Set();
823
+ for (const state of states) {
824
+ if (state.removed) continue;
825
+ const statement = state.node;
826
+ if (statement.type === "ExportNamedDeclaration") {
827
+ const declaration = statement.declaration;
828
+ if (declaration) if (declaration.type === "VariableDeclaration") for (const index of getRemainingDeclaratorIndices(state)) {
829
+ const declarator = declaration.declarations[index];
830
+ for (const name of collectBindingNamesFromPattern(declarator.id)) names.add(name);
831
+ }
832
+ else for (const name of collectBindingNamesFromDeclaration(declaration)) names.add(name);
833
+ for (const index of getRemainingSpecifierIndices(state)) {
834
+ const specifier = statement.specifiers[index];
835
+ if (specifier.type !== "ExportSpecifier" || specifier.exportKind === "type") continue;
836
+ const localName = getIdentifierName(specifier.local);
837
+ if (localName) names.add(localName);
838
+ }
839
+ }
840
+ if (statement.type !== "ExportDefaultDeclaration") continue;
841
+ const declaration = statement.declaration;
842
+ if (declaration.type === "Identifier") {
843
+ names.add(declaration.name);
844
+ continue;
845
+ }
846
+ if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && declaration.id) names.add(declaration.id.name);
847
+ }
848
+ return names;
849
+ }
850
+ function collectProgramReferences(states) {
851
+ return analyzeRetainedStatements(normalizeRetainedStatements(states)).referencedTopLevelNames;
852
+ }
853
+ function removeBinding(states, binding) {
854
+ const state = states[binding.statementIndex];
855
+ if (binding.kind === "import" && binding.specifierIndex !== void 0) {
856
+ state.removedSpecifiers.add(binding.specifierIndex);
857
+ if (getRemainingSpecifierIndices(state).length === 0) state.removed = true;
858
+ return;
859
+ }
860
+ if (binding.kind === "variable" && binding.declaratorIndex !== void 0) {
861
+ state.removedDeclarators.add(binding.declaratorIndex);
862
+ if (getRemainingDeclaratorIndices(state).length === 0) state.removed = true;
863
+ return;
864
+ }
865
+ state.removed = true;
866
+ }
867
+ function collectVariableDeclaratorDependencies(declarator, declarationKind, topLevelBindingNames, excludedNames) {
868
+ return collectTopLevelReferences({
869
+ declarations: [declarator],
870
+ end: declarator.end,
871
+ kind: declarationKind,
872
+ start: declarator.start,
873
+ type: "VariableDeclaration"
874
+ }, topLevelBindingNames, excludedNames);
875
+ }
876
+ function collectTopLevelReferences(node, topLevelBindingNames, excludedNames) {
877
+ return analyzeRetainedStatements([{ node }], {
878
+ excludedNames,
879
+ knownTopLevelNames: topLevelBindingNames
880
+ }).referencedTopLevelNames;
881
+ }
882
+ function enqueueDependencies(target, dependencies) {
883
+ for (const name of dependencies) target.add(name);
884
+ }
885
+ //#endregion
886
+ //#region src/plugin-assets.ts
6
887
  const PRACHT_CLIENT_MODULE_ID = "virtual:pracht/client";
7
888
  const PRACHT_SERVER_MODULE_ID = "virtual:pracht/server";
8
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
+ }
9
934
  function isClientModule(id) {
10
- 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");
11
936
  }
12
937
  function isServerModule(id) {
13
938
  return id === "virtual:pracht/server" || id.endsWith("virtual:pracht/server");
14
939
  }
940
+ //#endregion
941
+ //#region src/plugin-adapter.ts
15
942
  function createDefaultNodeAdapter() {
16
943
  return {
17
944
  id: "node",
@@ -55,6 +982,8 @@ function createDefaultNodeAdapter() {
55
982
  }
56
983
  };
57
984
  }
985
+ //#endregion
986
+ //#region src/plugin-options.ts
58
987
  const DEFAULTS = {
59
988
  appFile: "/src/routes.ts",
60
989
  middlewareDir: "/src/middleware",
@@ -66,98 +995,14 @@ const DEFAULTS = {
66
995
  pagesDir: "",
67
996
  pagesDefaultRender: "ssr"
68
997
  };
69
- async function pracht(options = {}) {
70
- const resolved = resolveOptions(options);
71
- const isPagesMode = !!resolved.pagesDir;
72
- let root = process.cwd();
73
- if (isPagesMode && options.appFile) console.warn("[pracht] Both `pagesDir` and `appFile` are set. `pagesDir` takes precedence — `appFile` will be ignored.");
74
- let isBuild = false;
75
- const prachtPlugin = {
76
- name: "pracht",
77
- enforce: "pre",
78
- config(_config, env) {
79
- const isEdge = resolved.adapter.id === "vercel" || resolved.adapter.id === "cloudflare";
80
- const isSSRBuild = env.isSsrBuild;
81
- return {
82
- appType: "custom",
83
- build: { rollupOptions: { output: { manualChunks(id) {
84
- if (id.includes("node_modules/preact") || id.includes("node_modules/preact-suspense")) return "vendor";
85
- } } } },
86
- ...isEdge && isSSRBuild ? { ssr: { noExternal: true } } : {}
87
- };
88
- },
89
- configResolved(config) {
90
- root = config.root;
91
- isBuild = config.command === "build";
92
- },
93
- resolveId(id) {
94
- if (isClientModule(id)) return PRACHT_CLIENT_MODULE_ID;
95
- if (isServerModule(id)) return PRACHT_SERVER_MODULE_ID;
96
- return null;
97
- },
98
- load(id) {
99
- if (isClientModule(id)) return createPrachtClientModuleSource(resolved, { root });
100
- if (isServerModule(id)) return createPrachtServerModuleSource(resolved, {
101
- root,
102
- isBuild
103
- });
104
- return null;
105
- },
106
- transform(code, id) {
107
- if (id !== resolve(root, resolved.appFile.slice(1))) return null;
108
- const transformed = code.replace(/\(\)\s*=>\s*import\(\s*(['"])([^'"]+)\1\s*\)/g, "$1$2$1");
109
- if (transformed === code) return null;
110
- return {
111
- code: transformed,
112
- map: null
113
- };
114
- },
115
- configureServer(server) {
116
- if (isPagesMode) {
117
- const abs = resolve(root, resolved.pagesDir.slice(1));
118
- server.watcher.on("add", (f) => {
119
- if (f.startsWith(abs)) server.restart();
120
- });
121
- server.watcher.on("unlink", (f) => {
122
- if (f.startsWith(abs)) server.restart();
123
- });
124
- }
125
- if (resolved.adapter.ownsDevServer) return;
126
- return () => {
127
- server.middlewares.use(createDevSSRMiddleware(server, resolved));
128
- };
129
- },
130
- handleHotUpdate({ file, server }) {
131
- const root = server.config.root;
132
- const relative = file.startsWith(root) ? file.slice(root.length) : file;
133
- if (isPagesMode && relative.startsWith(resolved.pagesDir)) {
134
- const clientMod = server.moduleGraph.getModuleById(PRACHT_CLIENT_MODULE_ID);
135
- const serverMod = server.moduleGraph.getModuleById(PRACHT_SERVER_MODULE_ID);
136
- if (clientMod) server.moduleGraph.invalidateModule(clientMod);
137
- if (serverMod) server.moduleGraph.invalidateModule(serverMod);
138
- return;
139
- }
140
- if (!isPagesMode && relative === resolved.appFile) {
141
- server.restart();
142
- return [];
143
- }
144
- if ([
145
- resolved.routesDir,
146
- resolved.shellsDir,
147
- resolved.middlewareDir,
148
- resolved.apiDir,
149
- resolved.serverDir
150
- ].some((dir) => relative.startsWith(dir))) {
151
- const serverMod = server.moduleGraph.getModuleById(PRACHT_SERVER_MODULE_ID);
152
- if (serverMod) server.moduleGraph.invalidateModule(serverMod);
153
- }
154
- }
998
+ function resolveOptions(options) {
999
+ return {
1000
+ ...DEFAULTS,
1001
+ ...options
155
1002
  };
156
- const plugins = [...preact(), prachtPlugin];
157
- const adapterPlugins = await resolved.adapter.vitePlugins?.();
158
- if (adapterPlugins?.length) plugins.push(...adapterPlugins);
159
- return plugins;
160
1003
  }
1004
+ //#endregion
1005
+ //#region src/plugin-codegen.ts
161
1006
  function createPrachtClientModuleSource(options = {}, buildOptions = {}) {
162
1007
  const resolved = resolveOptions(options);
163
1008
  const isPagesMode = !!resolved.pagesDir;
@@ -168,16 +1013,21 @@ function createPrachtClientModuleSource(options = {}, buildOptions = {}) {
168
1013
  "import { resolveApp, initClientRouter, readHydrationState } from \"@pracht/core\";",
169
1014
  appImport,
170
1015
  "",
171
- `const routeModules = import.meta.glob(${JSON.stringify(routeGlob)});`,
172
- `const shellModules = import.meta.glob(${JSON.stringify(shellGlob)});`,
1016
+ `const routeModules = import.meta.glob(${JSON.stringify(routeGlob)}, { query: ${JSON.stringify(PRACHT_CLIENT_MODULE_QUERY)} });`,
1017
+ `const shellModules = import.meta.glob(${JSON.stringify(shellGlob)}, { query: ${JSON.stringify(PRACHT_CLIENT_MODULE_QUERY)} });`,
173
1018
  "",
174
1019
  "const resolvedApp = resolveApp(app);",
175
1020
  "",
1021
+ "function normalizeModuleKey(key) {",
1022
+ " return key.split(\"?\")[0];",
1023
+ "}",
1024
+ "",
176
1025
  "function findModuleKey(modules, file) {",
177
1026
  " if (file in modules) return file;",
178
1027
  " const suffix = file.replace(/^\\.\\//,\"\");",
179
1028
  " for (const key of Object.keys(modules)) {",
180
- " if (key.endsWith(\"/\" + suffix) || key.endsWith(suffix)) return key;",
1029
+ " const normalizedKey = normalizeModuleKey(key);",
1030
+ " if (normalizedKey.endsWith(\"/\" + suffix) || normalizedKey.endsWith(suffix)) return key;",
181
1031
  " }",
182
1032
  " return null;",
183
1033
  "}",
@@ -216,7 +1066,7 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
216
1066
  "export const resolvedApp = resolveApp(app);",
217
1067
  `export const apiRoutes = resolveApiRoutes(Object.keys(apiModules), ${JSON.stringify(resolved.apiDir)});`,
218
1068
  `export const buildTarget = ${JSON.stringify(adapter?.id ?? "node")};`,
219
- `export const clientEntryUrl = ${JSON.stringify(clientBuild.clientEntryUrl ?? CLIENT_BROWSER_PATH)};`,
1069
+ `export const clientEntryUrl = ${JSON.stringify(clientBuild.clientEntryUrl ?? "/@pracht/client.js")};`,
220
1070
  `export const cssManifest = ${JSON.stringify(clientBuild.cssManifest)};`,
221
1071
  `export const jsManifest = ${JSON.stringify(clientBuild.jsManifest)};`,
222
1072
  "export { prerenderApp };",
@@ -254,7 +1104,11 @@ function generatePagesAppInlineSource(options, root = process.cwd()) {
254
1104
  pagesDirPrefix: options.pagesDir
255
1105
  });
256
1106
  }
257
- 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) {
258
1112
  return async (req, res, next) => {
259
1113
  const url = req.url ?? "/";
260
1114
  const pathname = new URL(url, "http://localhost").pathname;
@@ -290,34 +1144,36 @@ function createDevSSRMiddleware(server, _pluginOptions) {
290
1144
  });
291
1145
  res.end(body);
292
1146
  } catch (error) {
293
- if (error instanceof Error) server.ssrFixStacktrace(error);
294
- if (req.headers["x-pracht-route-state-request"] === "1") {
295
- res.statusCode = 500;
296
- res.setHeader("content-type", "application/json; charset=utf-8");
297
- res.end(JSON.stringify({ error: {
298
- message: error instanceof Error ? error.message : String(error),
299
- name: error instanceof Error ? error.name : "Error",
300
- status: 500
301
- } }));
302
- return;
303
- }
304
- try {
305
- const { buildErrorOverlayHtml } = await server.ssrLoadModule("pracht/error-overlay");
306
- let html = buildErrorOverlayHtml({
307
- message: error instanceof Error ? error.message : String(error),
308
- stack: error instanceof Error ? error.stack : void 0
309
- });
310
- html = await server.transformIndexHtml(url, html);
311
- res.statusCode = 500;
312
- res.setHeader("content-type", "text/html; charset=utf-8");
313
- res.end(html);
314
- } catch {
315
- next(error);
316
- }
1147
+ await handleDevError(server, req, res, next, url, error);
317
1148
  }
318
1149
  };
319
1150
  }
320
- 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
+ }
321
1177
  async function nodeToWebRequest(req) {
322
1178
  const protocol = "http";
323
1179
  const host = req.headers.host ?? "localhost";
@@ -334,7 +1190,6 @@ async function nodeToWebRequest(req) {
334
1190
  headers
335
1191
  };
336
1192
  if (!BODYLESS_METHODS.has(method.toUpperCase())) {
337
- const MAX_BODY_SIZE = 1024 * 1024;
338
1193
  const chunks = [];
339
1194
  let totalSize = 0;
340
1195
  for await (const chunk of req) {
@@ -351,54 +1206,151 @@ async function nodeToWebRequest(req) {
351
1206
  }
352
1207
  return new Request(url, init);
353
1208
  }
354
- function resolveOptions(options) {
355
- return {
356
- ...DEFAULTS,
357
- ...options
358
- };
359
- }
360
- function readClientBuildAssets(root = process.cwd()) {
361
- const manifestPath = ["dist/client/.vite/manifest.json", "dist/.vite/manifest.json"].map((candidate) => resolve(root, candidate)).find((candidate) => existsSync(candidate));
362
- if (!manifestPath) return {
363
- clientEntryUrl: null,
364
- cssManifest: {},
365
- 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
+ }
366
1288
  };
367
- const rawManifest = readFileSync(manifestPath, "utf-8");
368
- const manifest = JSON.parse(rawManifest);
369
- const clientEntry = manifest[PRACHT_CLIENT_MODULE_ID];
370
- function collectTransitiveDeps(key) {
371
- const css = /* @__PURE__ */ new Set();
372
- const js = /* @__PURE__ */ new Set();
373
- const visited = /* @__PURE__ */ new Set();
374
- function collect(k) {
375
- if (visited.has(k)) return;
376
- visited.add(k);
377
- const entry = manifest[k];
378
- if (!entry) return;
379
- for (const c of entry.css ?? []) css.add(c);
380
- js.add(entry.file);
381
- for (const imp of entry.imports ?? []) collect(imp);
382
- }
383
- collect(key);
384
- return {
385
- css: [...css],
386
- js: [...js]
387
- };
388
- }
389
- const cssManifest = {};
390
- const jsManifest = {};
391
- for (const [key, entry] of Object.entries(manifest)) {
392
- if (!entry.src) continue;
393
- const deps = collectTransitiveDeps(key);
394
- if (deps.css.length > 0) cssManifest[key] = deps.css.map((f) => `/${f}`);
395
- if (deps.js.length > 0) jsManifest[key] = deps.js.map((f) => `/${f}`);
396
- }
397
- return {
398
- clientEntryUrl: clientEntry ? `/${clientEntry.file}` : null,
399
- cssManifest,
400
- jsManifest
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
+ };
1300
+ }
401
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}/`;
402
1354
  }
403
1355
  //#endregion
404
1356
  export { PRACHT_CLIENT_MODULE_ID, PRACHT_SERVER_MODULE_ID, createPrachtClientModuleSource, createPrachtRegistryModuleSource, createPrachtServerModuleSource, pracht };