@zenithbuild/cli 0.7.11 → 0.7.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/dist/adapters/adapter-netlify-static.d.ts +2 -5
- package/dist/adapters/adapter-netlify.d.ts +2 -5
- package/dist/adapters/adapter-netlify.js +22 -5
- package/dist/adapters/adapter-types.d.ts +32 -13
- package/dist/adapters/adapter-types.js +0 -59
- package/dist/adapters/adapter-vercel-static.d.ts +2 -5
- package/dist/adapters/adapter-vercel.d.ts +2 -5
- package/dist/adapters/adapter-vercel.js +21 -6
- package/dist/adapters/copy-hosted-page-runtime.d.ts +2 -1
- package/dist/adapters/copy-hosted-page-runtime.js +68 -3
- package/dist/adapters/resolve-adapter.d.ts +6 -4
- package/dist/build/expression-rewrites.d.ts +3 -1
- package/dist/build/expression-rewrites.js +14 -2
- package/dist/build/page-component-loop.d.ts +1 -0
- package/dist/build/page-component-loop.js +66 -6
- package/dist/build/page-ir-normalization.js +7 -0
- package/dist/build/page-loop-state.d.ts +2 -1
- package/dist/build/page-loop-state.js +9 -2
- package/dist/build/page-loop.js +10 -1
- package/dist/build/scoped-expression-context.d.ts +5 -0
- package/dist/build/scoped-expression-context.js +133 -0
- package/dist/build/type-declarations.d.ts +2 -1
- package/dist/build/type-declarations.js +31 -1
- package/dist/build-output-manifest.d.ts +10 -6
- package/dist/build-output-manifest.js +4 -1
- package/dist/build.js +11 -2
- package/dist/component-instance-ir.js +1 -0
- package/dist/component-occurrences.d.ts +9 -0
- package/dist/component-occurrences.js +18 -0
- package/dist/config-plugins.d.ts +12 -0
- package/dist/config-plugins.js +100 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +56 -5
- package/dist/dev-server/request-handler.js +46 -4
- package/dist/dev-server.js +92 -4
- package/dist/global-middleware-runtime-source.d.ts +15 -0
- package/dist/global-middleware-runtime-source.js +62 -0
- package/dist/global-middleware.d.ts +13 -0
- package/dist/global-middleware.js +252 -0
- package/dist/manifest.d.ts +9 -1
- package/dist/manifest.js +66 -26
- package/dist/preview/request-handler.js +78 -5
- package/dist/preview/server-runner.d.ts +7 -2
- package/dist/preview/server-runner.js +19 -6
- package/dist/preview/server-script-runner-template.js +97 -29
- package/dist/route-classification.d.ts +2 -1
- package/dist/route-classification.js +6 -2
- package/dist/scoped-server-data/analyze-owner-file.d.ts +3 -0
- package/dist/scoped-server-data/analyze-owner-file.js +149 -0
- package/dist/scoped-server-data/diagnostics.d.ts +18 -0
- package/dist/scoped-server-data/diagnostics.js +32 -0
- package/dist/scoped-server-data/lowering.d.ts +27 -0
- package/dist/scoped-server-data/lowering.js +242 -0
- package/dist/scoped-server-data/manifest-integration.d.ts +4 -0
- package/dist/scoped-server-data/manifest-integration.js +125 -0
- package/dist/scoped-server-data/owner-scanner.d.ts +6 -0
- package/dist/scoped-server-data/owner-scanner.js +55 -0
- package/dist/scoped-server-data/parse-owner-server-block.d.ts +12 -0
- package/dist/scoped-server-data/parse-owner-server-block.js +35 -0
- package/dist/scoped-server-data/runtime.d.ts +24 -0
- package/dist/scoped-server-data/runtime.js +121 -0
- package/dist/scoped-server-data/serialization-set.d.ts +2 -0
- package/dist/scoped-server-data/serialization-set.js +52 -0
- package/dist/scoped-server-data/static-props.d.ts +12 -0
- package/dist/scoped-server-data/static-props.js +307 -0
- package/dist/scoped-server-data/type-declarations.d.ts +10 -0
- package/dist/scoped-server-data/type-declarations.js +368 -0
- package/dist/scoped-server-data/types.d.ts +74 -0
- package/dist/scoped-server-data/types.js +1 -0
- package/dist/server-contract/auth-control-flow.d.ts +1 -0
- package/dist/server-contract/auth-control-flow.js +10 -0
- package/dist/server-contract/resolve.d.ts +19 -0
- package/dist/server-contract/resolve.js +85 -13
- package/dist/server-contract/resolved-envelope.d.ts +9 -0
- package/dist/server-contract/resolved-envelope.js +14 -0
- package/dist/server-contract/stage.js +1 -10
- package/dist/server-module-output.d.ts +9 -0
- package/dist/server-module-output.js +250 -0
- package/dist/server-output.d.ts +7 -1
- package/dist/server-output.js +138 -179
- package/dist/server-runtime/matched-route-pipeline.d.ts +1 -0
- package/dist/server-runtime/matched-route-pipeline.js +1 -0
- package/dist/server-runtime/node-server.js +21 -1
- package/dist/server-runtime/route-render.d.ts +12 -3
- package/dist/server-runtime/route-render.js +67 -13
- package/package.json +3 -3
|
@@ -4,6 +4,7 @@ import { cloneComponentIrForInstance } from '../component-instance-ir.js';
|
|
|
4
4
|
import { renderPropsLiteralFromAttrs } from './scoped-identifier-rewrite.js';
|
|
5
5
|
import { extractTemplate, isDocumentMode } from '../resolve-components.js';
|
|
6
6
|
import { buildComponentExpressionRewrite, mergeExpressionRewriteMaps, resolveStateKeyFromBindings } from './expression-rewrites.js';
|
|
7
|
+
import { applyScopedDataContextToExpressionRewrite, resolveScopedExpressionContext } from './scoped-expression-context.js';
|
|
7
8
|
import { mergeComponentIr } from './merge-component-ir.js';
|
|
8
9
|
import { stripStyleBlocks } from './compiler-runtime.js';
|
|
9
10
|
import { extractDeclaredIdentifiers } from './typescript-expression-utils.js';
|
|
@@ -17,6 +18,55 @@ function createEmptyExpressionRewrite() {
|
|
|
17
18
|
sequence: []
|
|
18
19
|
};
|
|
19
20
|
}
|
|
21
|
+
const SERVER_CONST_RE = /(?:^|\n)\s*const\s+([A-Za-z_$][\w$]*)\s*=/g;
|
|
22
|
+
function stripOwnerServerBlocks(source) {
|
|
23
|
+
return String(source || '').replace(/<script\b([^>]*)>([\s\S]*?)<\/script>/gi, (full, attrs) => {
|
|
24
|
+
if (/\bserver\b/i.test(String(attrs || ''))) {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
return full;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function collectServerConstNames(source) {
|
|
31
|
+
const names = [];
|
|
32
|
+
for (const match of String(source || '').matchAll(SERVER_CONST_RE)) {
|
|
33
|
+
const name = String(match[1] || '');
|
|
34
|
+
if (name && !names.includes(name)) {
|
|
35
|
+
names.push(name);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return names;
|
|
39
|
+
}
|
|
40
|
+
function createClientPlaceholderPrelude(source, strippedSource) {
|
|
41
|
+
const names = [];
|
|
42
|
+
String(source || '').replace(/<script\b([^>]*)>([\s\S]*?)<\/script>/gi, (full, attrs, body) => {
|
|
43
|
+
if (/\bserver\b/i.test(String(attrs || ''))) {
|
|
44
|
+
for (const name of collectServerConstNames(body)) {
|
|
45
|
+
const refRe = new RegExp(`\\b${escapeRegExp(name)}\\b`);
|
|
46
|
+
if (refRe.test(strippedSource) && !names.includes(name)) {
|
|
47
|
+
names.push(name);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return full;
|
|
52
|
+
});
|
|
53
|
+
if (names.length === 0) {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
return [
|
|
57
|
+
'<script lang="ts">',
|
|
58
|
+
...names.map((name) => `const ${name} = undefined;`),
|
|
59
|
+
'</script>',
|
|
60
|
+
''
|
|
61
|
+
].join('\n');
|
|
62
|
+
}
|
|
63
|
+
function prepareOwnerClientCompileSource(source) {
|
|
64
|
+
const strippedSource = stripOwnerServerBlocks(source);
|
|
65
|
+
return stripStyleBlocks(`${createClientPlaceholderPrelude(source, strippedSource)}${strippedSource}`);
|
|
66
|
+
}
|
|
67
|
+
function escapeRegExp(value) {
|
|
68
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
69
|
+
}
|
|
20
70
|
async function resolveComponentIr({ compPath, componentSource, compilerOpts, compilerBin, timedRunCompiler, cooperativeYield, componentIrCache, compilerTotals, pageStats, startupProfile, emitCompilerWarning }) {
|
|
21
71
|
let compIr;
|
|
22
72
|
if (componentIrCache.has(compPath)) {
|
|
@@ -27,7 +77,7 @@ async function resolveComponentIr({ compPath, componentSource, compilerOpts, com
|
|
|
27
77
|
else {
|
|
28
78
|
await cooperativeYield();
|
|
29
79
|
const componentCompileStartedAt = performance.now();
|
|
30
|
-
compIr = timedRunCompiler('component', compPath,
|
|
80
|
+
compIr = timedRunCompiler('component', compPath, prepareOwnerClientCompileSource(componentSource), compilerOpts, { compilerToolchain: compilerBin, onWarning: emitCompilerWarning });
|
|
31
81
|
pageStats.pageComponentCompileMs += startupProfile.roundMs(performance.now() - componentCompileStartedAt);
|
|
32
82
|
compilerTotals.componentCacheMisses += 1;
|
|
33
83
|
pageStats.pageComponentCacheMisses += 1;
|
|
@@ -72,7 +122,7 @@ async function resolveOwnerRewriteContext({ occurrence, sourceFile, compilerOpts
|
|
|
72
122
|
pageComponentLoopBreakdown.ownerSourceReadMs += startupProfile.roundMs(performance.now() - ownerSourceReadStartedAt);
|
|
73
123
|
await cooperativeYield();
|
|
74
124
|
const ownerCompileStartedAt = performance.now();
|
|
75
|
-
ownerIr = timedRunCompiler('component', ownerPath,
|
|
125
|
+
ownerIr = timedRunCompiler('component', ownerPath, prepareOwnerClientCompileSource(ownerSource), compilerOpts, { compilerToolchain: compilerBin, onWarning: emitCompilerWarning });
|
|
76
126
|
pageStats.pageComponentCompileMs += startupProfile.roundMs(performance.now() - ownerCompileStartedAt);
|
|
77
127
|
compilerTotals.componentCacheMisses += 1;
|
|
78
128
|
pageStats.pageComponentCacheMisses += 1;
|
|
@@ -136,6 +186,7 @@ export async function buildPageOwnerContext({ componentOccurrences, sourceFile,
|
|
|
136
186
|
}
|
|
137
187
|
export async function runPageComponentLoop({ componentOccurrences, occurrenceCountByPath, sourceFile, registry, compilerOpts, compilerBin, timedRunCompiler, cooperativeYield, startupProfile, compilerTotals, emitCompilerWarning, componentIrCache, componentDocumentModeCache, componentExpressionRewriteCache, expressionRewriteMetrics, pageOwnerExpressionRewrite, pageIr, pageIrMergeCache, seenStaticImports, pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap, knownRefKeys, componentOccurrencePlans, imagePropsLiterals, pagePhase, pageBindingResolutionBreakdown, pageMergeBreakdown, pageComponentLoopBreakdown, hoistedCodeTransformCache, pageStats }) {
|
|
138
188
|
let componentInstanceCounter = 0;
|
|
189
|
+
const scopedOccurrenceIndexByOwnerKey = new Map();
|
|
139
190
|
for (const occurrence of componentOccurrences) {
|
|
140
191
|
await cooperativeYield();
|
|
141
192
|
const compName = occurrence.name;
|
|
@@ -146,7 +197,9 @@ export async function runPageComponentLoop({ componentOccurrences, occurrenceCou
|
|
|
146
197
|
const componentSourceReadStartedAt = performance.now();
|
|
147
198
|
const componentSource = readFileSync(compPath, 'utf8');
|
|
148
199
|
pageComponentLoopBreakdown.componentSourceReadMs += startupProfile.roundMs(performance.now() - componentSourceReadStartedAt);
|
|
149
|
-
const occurrenceCount = occurrenceCountByPath.get(compPath)
|
|
200
|
+
const occurrenceCount = occurrenceCountByPath.get(compPath)
|
|
201
|
+
|| occurrenceCountByPath.get(compName)
|
|
202
|
+
|| 0;
|
|
150
203
|
const compIr = await resolveComponentIr({
|
|
151
204
|
compPath,
|
|
152
205
|
componentSource,
|
|
@@ -175,6 +228,10 @@ export async function runPageComponentLoop({ componentOccurrences, occurrenceCou
|
|
|
175
228
|
pageComponentLoopBreakdown,
|
|
176
229
|
startupProfile
|
|
177
230
|
});
|
|
231
|
+
const scopedContext = resolveScopedExpressionContext(pageIr, compPath, scopedOccurrenceIndexByOwnerKey);
|
|
232
|
+
const scopedExpressionRewrite = scopedContext
|
|
233
|
+
? applyScopedDataContextToExpressionRewrite(expressionRewrite, scopedContext)
|
|
234
|
+
: expressionRewrite;
|
|
178
235
|
const { attrExpressionRewrite } = await resolveOwnerRewriteContext({
|
|
179
236
|
occurrence,
|
|
180
237
|
sourceFile,
|
|
@@ -220,13 +277,16 @@ export async function runPageComponentLoop({ componentOccurrences, occurrenceCou
|
|
|
220
277
|
}, seenStaticImports, knownRefKeys, pageIrMergeCache, pageMergeBreakdown, hoistedCodeTransformCache);
|
|
221
278
|
pagePhase.mergeMs += startupProfile.roundMs(performance.now() - mergeStartedAt);
|
|
222
279
|
if (useIsolatedInstance) {
|
|
280
|
+
const scopedInstanceRewrite = scopedContext
|
|
281
|
+
? applyScopedDataContextToExpressionRewrite(instanceState.instanceRewrite, scopedContext)
|
|
282
|
+
: instanceState.instanceRewrite;
|
|
223
283
|
componentOccurrencePlans.push({
|
|
224
|
-
rewrite:
|
|
225
|
-
expressionSequence:
|
|
284
|
+
rewrite: scopedInstanceRewrite,
|
|
285
|
+
expressionSequence: scopedInstanceRewrite.sequence,
|
|
226
286
|
refSequence: instanceState.refIdentifierPairs
|
|
227
287
|
});
|
|
228
288
|
continue;
|
|
229
289
|
}
|
|
230
|
-
mergeExpressionRewriteMaps(pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap,
|
|
290
|
+
mergeExpressionRewriteMaps(pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap, scopedExpressionRewrite, pageIrMergeCache, pageBindingResolutionBreakdown);
|
|
231
291
|
}
|
|
232
292
|
}
|
|
@@ -50,6 +50,12 @@ export function applyExpressionRewrites(pageIr, expressionMap, bindingMap, ambig
|
|
|
50
50
|
if (rewritten && rewritten !== current && bindings[index].literal === current) {
|
|
51
51
|
bindings[index].literal = rewritten;
|
|
52
52
|
}
|
|
53
|
+
if (rewrittenBinding &&
|
|
54
|
+
typeof bindings[index].scoped_data_key === 'string' &&
|
|
55
|
+
bindings[index].scoped_data_key.length > 0 &&
|
|
56
|
+
!(typeof rewrittenBinding.scoped_data_key === 'string' && rewrittenBinding.scoped_data_key.length > 0)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
53
59
|
if (rewrittenBinding) {
|
|
54
60
|
bindings[index].compiled_expr = rewrittenBinding.compiled_expr;
|
|
55
61
|
bindings[index].signal_index = rewrittenBinding.signal_index;
|
|
@@ -57,6 +63,7 @@ export function applyExpressionRewrites(pageIr, expressionMap, bindingMap, ambig
|
|
|
57
63
|
bindings[index].state_index = rewrittenBinding.state_index;
|
|
58
64
|
bindings[index].component_instance = rewrittenBinding.component_instance;
|
|
59
65
|
bindings[index].component_binding = rewrittenBinding.component_binding;
|
|
66
|
+
bindings[index].scoped_data_key = rewrittenBinding.scoped_data_key;
|
|
60
67
|
}
|
|
61
68
|
else if (rewritten && rewritten !== current && bindings[index].compiled_expr === current) {
|
|
62
69
|
bindings[index].compiled_expr = rewritten;
|
|
@@ -169,12 +169,13 @@ export function createPageLoopExecutionState(): {
|
|
|
169
169
|
envelopes: never[];
|
|
170
170
|
};
|
|
171
171
|
export function preparePageIrForMerge(pageIr: any): void;
|
|
172
|
-
export function applyServerEnvelopeToPageIr({ pageIr, composedServer, entry, srcDir, sourceFile }: {
|
|
172
|
+
export function applyServerEnvelopeToPageIr({ pageIr, composedServer, entry, srcDir, sourceFile, scopedMetadata }: {
|
|
173
173
|
pageIr: any;
|
|
174
174
|
composedServer: any;
|
|
175
175
|
entry: any;
|
|
176
176
|
srcDir: any;
|
|
177
177
|
sourceFile: any;
|
|
178
|
+
scopedMetadata: any;
|
|
178
179
|
}): void;
|
|
179
180
|
export function buildOccurrenceCountByPath(componentOccurrences: any): Map<any, any>;
|
|
180
181
|
export function createPageBuildState(): {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { relative } from 'node:path';
|
|
2
2
|
import { classifyPageRoute } from '../route-classification.js';
|
|
3
|
+
import { assertNoScopedServerBuildErrors } from '../manifest.js';
|
|
3
4
|
import { createBindingResolutionTotals, createComponentLoopPhaseTotals, createMergePhaseTotals, createOccurrenceApplyPhaseTotals, createPagePhaseTotals, createScopedRewritePhaseTotals } from './page-loop-metrics.js';
|
|
4
5
|
export function createPageLoopState() {
|
|
5
6
|
return {
|
|
@@ -47,10 +48,12 @@ export function preparePageIrForMerge(pageIr) {
|
|
|
47
48
|
pageIr.hoisted.state = pageIr.hoisted.state || [];
|
|
48
49
|
pageIr.hoisted.code = pageIr.hoisted.code || [];
|
|
49
50
|
}
|
|
50
|
-
export function applyServerEnvelopeToPageIr({ pageIr, composedServer, entry, srcDir, sourceFile }) {
|
|
51
|
+
export function applyServerEnvelopeToPageIr({ pageIr, composedServer, entry, srcDir, sourceFile, scopedMetadata }) {
|
|
52
|
+
assertNoScopedServerBuildErrors(scopedMetadata.diagnostics, entry.file);
|
|
51
53
|
const classification = classifyPageRoute({
|
|
52
54
|
file: entry.file,
|
|
53
|
-
serverScript: composedServer.serverScript
|
|
55
|
+
serverScript: composedServer.serverScript,
|
|
56
|
+
hasScopedServerData: scopedMetadata.hasScopedServerData
|
|
54
57
|
});
|
|
55
58
|
if (composedServer.serverScript) {
|
|
56
59
|
const { has_action: _unusedHasAction, export_paths: _unusedExportPaths, ...serverScript } = composedServer.serverScript;
|
|
@@ -63,6 +66,10 @@ export function applyServerEnvelopeToPageIr({ pageIr, composedServer, entry, src
|
|
|
63
66
|
pageIr.has_guard = classification.hasGuard;
|
|
64
67
|
pageIr.has_load = classification.hasLoad;
|
|
65
68
|
pageIr.has_action = classification.hasAction;
|
|
69
|
+
pageIr.has_scoped_server_data = scopedMetadata.hasScopedServerData;
|
|
70
|
+
pageIr.scoped_server_data = scopedMetadata.hasScopedServerData
|
|
71
|
+
? scopedMetadata.scopedServerData
|
|
72
|
+
: [];
|
|
66
73
|
pageIr.guard_module_ref = composedServer.guardPath ? relative(srcDir, composedServer.guardPath).replaceAll('\\', '/') : null;
|
|
67
74
|
pageIr.load_module_ref = composedServer.loadPath ? relative(srcDir, composedServer.loadPath).replaceAll('\\', '/') : null;
|
|
68
75
|
pageIr.action_module_ref = composedServer.actionPath ? relative(srcDir, composedServer.actionPath).replaceAll('\\', '/') : null;
|
package/dist/build/page-loop.js
CHANGED
|
@@ -14,6 +14,7 @@ import { deferComponentRuntimeBlock } from './hoisted-code-transforms.js';
|
|
|
14
14
|
import { addBreakdown, emitPageLoopSummary, recordPageProfile } from './page-loop-metrics.js';
|
|
15
15
|
import { applyServerEnvelopeToPageIr, buildOccurrenceCountByPath, createPageBuildState, createPageLoopCaches, createPageLoopExecutionState } from './page-loop-state.js';
|
|
16
16
|
import { extractServerScript } from './server-script.js';
|
|
17
|
+
import { analyzeRouteScopedServerMetadata } from '../manifest.js';
|
|
17
18
|
/**
|
|
18
19
|
* @param {{
|
|
19
20
|
* manifest: Array<{ path: string, file: string }>
|
|
@@ -78,12 +79,20 @@ export async function buildPageEnvelopes(input) {
|
|
|
78
79
|
adjacentLoadPath: adjacentLoad,
|
|
79
80
|
adjacentActionPath: adjacentAction
|
|
80
81
|
});
|
|
82
|
+
const scopedMetadata = analyzeRouteScopedServerMetadata({
|
|
83
|
+
pageSource: rawSource,
|
|
84
|
+
pageFile: sourceFile,
|
|
85
|
+
registry,
|
|
86
|
+
srcDir,
|
|
87
|
+
compilerOpts
|
|
88
|
+
});
|
|
81
89
|
applyServerEnvelopeToPageIr({
|
|
82
90
|
pageIr,
|
|
83
91
|
composedServer,
|
|
84
92
|
entry,
|
|
85
93
|
srcDir,
|
|
86
|
-
sourceFile
|
|
94
|
+
sourceFile,
|
|
95
|
+
scopedMetadata
|
|
87
96
|
});
|
|
88
97
|
const pageIrMergeCache = createPageIrMergeCache(pageIr);
|
|
89
98
|
const seenStaticImports = new Set();
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export function applyScopedDataContextToExpressionRewrite(componentRewrite: any, scopedContext: any): any;
|
|
2
|
+
export function resolveScopedExpressionContext(pageIr: any, compPath: any, occurrenceIndexByOwnerKey: any): {
|
|
3
|
+
scopedDataKey: any;
|
|
4
|
+
serializedVariableNames: any;
|
|
5
|
+
} | null;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
function escapeRegExp(value) {
|
|
2
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3
|
+
}
|
|
4
|
+
function scopedDataKey(scopedContext) {
|
|
5
|
+
return typeof scopedContext?.scopedDataKey === 'string' && scopedContext.scopedDataKey.length > 0
|
|
6
|
+
? scopedContext.scopedDataKey
|
|
7
|
+
: null;
|
|
8
|
+
}
|
|
9
|
+
function scopedDataExpressionForSource(expression, scopedContext) {
|
|
10
|
+
const match = String(expression || '').trim().match(/^([A-Za-z_$][\w$]*)([\s\S]*)$/);
|
|
11
|
+
if (!match)
|
|
12
|
+
return null;
|
|
13
|
+
const root = match[1];
|
|
14
|
+
const rest = match[2] || '';
|
|
15
|
+
if (root === 'data' || root === 'ssr')
|
|
16
|
+
return `${root}${rest}`;
|
|
17
|
+
for (const name of Array.isArray(scopedContext?.serializedVariableNames) ? scopedContext.serializedVariableNames : []) {
|
|
18
|
+
if (typeof name === 'string' &&
|
|
19
|
+
name.length > 0 &&
|
|
20
|
+
(root === name || new RegExp(`(?:^|_)${escapeRegExp(name)}(?:__inst\\d+)?$`).test(root))) {
|
|
21
|
+
return `data.${name}${rest}`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function scopedExpressionCandidate(raw, rewritten, binding, scopedContext) {
|
|
27
|
+
if (!scopedDataKey(scopedContext))
|
|
28
|
+
return null;
|
|
29
|
+
for (const candidate of [rewritten, binding?.compiled_expr, binding?.literal, raw]) {
|
|
30
|
+
const scopedExpression = scopedDataExpressionForSource(candidate, scopedContext);
|
|
31
|
+
if (scopedExpression)
|
|
32
|
+
return scopedExpression;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function createScopedBinding(scopedExpression, scopedContext, baseBinding = null) {
|
|
37
|
+
return scopedExpression
|
|
38
|
+
? {
|
|
39
|
+
...(baseBinding && typeof baseBinding === 'object' ? baseBinding : {}),
|
|
40
|
+
compiled_expr: scopedExpression,
|
|
41
|
+
literal: scopedExpression,
|
|
42
|
+
signal_index: null,
|
|
43
|
+
signal_indices: [],
|
|
44
|
+
state_index: null,
|
|
45
|
+
component_instance: null,
|
|
46
|
+
component_binding: null,
|
|
47
|
+
scoped_data_key: scopedDataKey(scopedContext)
|
|
48
|
+
}
|
|
49
|
+
: null;
|
|
50
|
+
}
|
|
51
|
+
function applyScopedContextToBinding(binding, scopedContext, fallbackExpression = null) {
|
|
52
|
+
if (!binding || typeof binding !== 'object' || !scopedDataKey(scopedContext))
|
|
53
|
+
return binding;
|
|
54
|
+
const scopedExpression = scopedDataExpressionForSource(binding.compiled_expr, scopedContext)
|
|
55
|
+
|| scopedDataExpressionForSource(binding.literal, scopedContext)
|
|
56
|
+
|| scopedDataExpressionForSource(fallbackExpression, scopedContext);
|
|
57
|
+
return scopedExpression ? createScopedBinding(scopedExpression, scopedContext, binding) : binding;
|
|
58
|
+
}
|
|
59
|
+
function hasScopedBinding(binding) {
|
|
60
|
+
return typeof binding?.scoped_data_key === 'string' && binding.scoped_data_key.length > 0;
|
|
61
|
+
}
|
|
62
|
+
export function applyScopedDataContextToExpressionRewrite(componentRewrite, scopedContext) {
|
|
63
|
+
if (!componentRewrite || !scopedDataKey(scopedContext))
|
|
64
|
+
return componentRewrite;
|
|
65
|
+
const next = {
|
|
66
|
+
map: new Map(),
|
|
67
|
+
bindings: new Map(),
|
|
68
|
+
signals: componentRewrite.signals,
|
|
69
|
+
stateBindings: componentRewrite.stateBindings,
|
|
70
|
+
ambiguous: new Set(componentRewrite.ambiguous),
|
|
71
|
+
sequence: []
|
|
72
|
+
};
|
|
73
|
+
for (const item of Array.isArray(componentRewrite.sequence) ? componentRewrite.sequence : []) {
|
|
74
|
+
const scopedBinding = applyScopedContextToBinding(item.binding, scopedContext, item.rewritten || item.raw);
|
|
75
|
+
const scopedExpression = scopedExpressionCandidate(item.raw, item.rewritten, scopedBinding, scopedContext);
|
|
76
|
+
next.sequence.push({
|
|
77
|
+
raw: item.raw,
|
|
78
|
+
rewritten: scopedExpression || item.rewritten,
|
|
79
|
+
binding: hasScopedBinding(scopedBinding)
|
|
80
|
+
? scopedBinding
|
|
81
|
+
: createScopedBinding(scopedExpression, scopedContext)
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
for (const [raw, binding] of componentRewrite.bindings.entries()) {
|
|
85
|
+
next.bindings.set(raw, applyScopedContextToBinding(binding, scopedContext, raw));
|
|
86
|
+
}
|
|
87
|
+
for (const [raw, rewritten] of componentRewrite.map.entries()) {
|
|
88
|
+
const binding = next.bindings.get(raw);
|
|
89
|
+
next.map.set(raw, scopedExpressionCandidate(raw, rewritten, binding, scopedContext) || rewritten);
|
|
90
|
+
}
|
|
91
|
+
return next;
|
|
92
|
+
}
|
|
93
|
+
function normalizeSourcePath(value) {
|
|
94
|
+
return String(value || '').replaceAll('\\', '/');
|
|
95
|
+
}
|
|
96
|
+
function ownerKeyMatchesPath(ownerKey, filePath) {
|
|
97
|
+
const key = normalizeSourcePath(ownerKey);
|
|
98
|
+
const file = normalizeSourcePath(filePath);
|
|
99
|
+
return key.length > 0 && (file === key || file.endsWith(`/${key}`));
|
|
100
|
+
}
|
|
101
|
+
export function resolveScopedExpressionContext(pageIr, compPath, occurrenceIndexByOwnerKey) {
|
|
102
|
+
const entries = Array.isArray(pageIr?.scoped_server_data) ? pageIr.scoped_server_data : [];
|
|
103
|
+
const matches = entries.filter((entry) => entry &&
|
|
104
|
+
typeof entry === 'object' &&
|
|
105
|
+
(entry.ownerKind === 'layout' || entry.ownerKind === 'component') &&
|
|
106
|
+
ownerKeyMatchesPath(entry.ownerKey, compPath));
|
|
107
|
+
const entry = matches.length === 1 ? matches[0] : null;
|
|
108
|
+
if (!entry || typeof entry.ownerKey !== 'string')
|
|
109
|
+
return null;
|
|
110
|
+
let scopedDataKey = null;
|
|
111
|
+
if (entry.ownerKind === 'layout') {
|
|
112
|
+
scopedDataKey = `layout:${entry.ownerKey}`;
|
|
113
|
+
}
|
|
114
|
+
else if (entry.ownerKind === 'component' && entry.instanceStrategy === 'per-instance') {
|
|
115
|
+
const index = occurrenceIndexByOwnerKey.get(entry.ownerKey) || 0;
|
|
116
|
+
occurrenceIndexByOwnerKey.set(entry.ownerKey, index + 1);
|
|
117
|
+
const instance = Array.isArray(entry.instances) ? entry.instances[index] : null;
|
|
118
|
+
scopedDataKey = typeof instance?.key === 'string' && instance.key.length > 0
|
|
119
|
+
? instance.key
|
|
120
|
+
: `component:${entry.ownerKey}:o${index}`;
|
|
121
|
+
}
|
|
122
|
+
else if (entry.ownerKind === 'component') {
|
|
123
|
+
scopedDataKey = `component:${entry.ownerKey}`;
|
|
124
|
+
}
|
|
125
|
+
return scopedDataKey
|
|
126
|
+
? {
|
|
127
|
+
scopedDataKey,
|
|
128
|
+
serializedVariableNames: Array.isArray(entry.serializedVariableNames)
|
|
129
|
+
? entry.serializedVariableNames
|
|
130
|
+
: []
|
|
131
|
+
}
|
|
132
|
+
: null;
|
|
133
|
+
}
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export function deriveProjectRootFromPagesDir(pagesDir: string): string;
|
|
6
6
|
/**
|
|
7
|
-
* @param {{ manifest: Array<{ path: string, file: string }>, pagesDir: string }} input
|
|
7
|
+
* @param {{ manifest: Array<{ path: string, file: string, scoped_server_data?: import('../scoped-server-data/types.js').ManifestScopedServerDataEntry[] }>, pagesDir: string }} input
|
|
8
8
|
* @returns {Promise<void>}
|
|
9
9
|
*/
|
|
10
10
|
export function ensureZenithTypeDeclarations(input: {
|
|
11
11
|
manifest: Array<{
|
|
12
12
|
path: string;
|
|
13
13
|
file: string;
|
|
14
|
+
scoped_server_data?: import("../scoped-server-data/types.js").ManifestScopedServerDataEntry[];
|
|
14
15
|
}>;
|
|
15
16
|
pagesDir: string;
|
|
16
17
|
}): Promise<void>;
|
|
@@ -2,6 +2,8 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { mkdir } from 'node:fs/promises';
|
|
3
3
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { renderZenithEnvDts } from '../types/zenith-env-dts.js';
|
|
5
|
+
const SCOPED_SERVER_TYPE_DECLARATIONS_HELPER_UNAVAILABLE = '[Zenith:ScopedServerData] Type declaration helper is unavailable. Run the CLI build step before generating scoped server data type declarations.';
|
|
6
|
+
let scopedTypeDeclarationRendererPromise = null;
|
|
5
7
|
/**
|
|
6
8
|
* @param {string} targetPath
|
|
7
9
|
* @param {string} next
|
|
@@ -61,6 +63,27 @@ function renderZenithRouteDts(manifest) {
|
|
|
61
63
|
lines.push('');
|
|
62
64
|
return `${lines.join('\n')}\n`;
|
|
63
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* @returns {Promise<(input: { manifest: Array<{ scoped_server_data?: import('../scoped-server-data/types.js').ManifestScopedServerDataEntry[] }>, srcDir: string }) => string>}
|
|
68
|
+
*/
|
|
69
|
+
async function getScopedServerDataTypeRenderer() {
|
|
70
|
+
if (!scopedTypeDeclarationRendererPromise) {
|
|
71
|
+
scopedTypeDeclarationRendererPromise = import('../scoped-server-data/type-declarations.js')
|
|
72
|
+
.then((helper) => {
|
|
73
|
+
if (typeof helper.renderScopedServerDataDts !== 'function') {
|
|
74
|
+
throw new Error(SCOPED_SERVER_TYPE_DECLARATIONS_HELPER_UNAVAILABLE);
|
|
75
|
+
}
|
|
76
|
+
return helper.renderScopedServerDataDts;
|
|
77
|
+
})
|
|
78
|
+
.catch((error) => {
|
|
79
|
+
if (error && error.message === SCOPED_SERVER_TYPE_DECLARATIONS_HELPER_UNAVAILABLE) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
throw new Error(SCOPED_SERVER_TYPE_DECLARATIONS_HELPER_UNAVAILABLE);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return scopedTypeDeclarationRendererPromise;
|
|
86
|
+
}
|
|
64
87
|
/**
|
|
65
88
|
* @param {string} pagesDir
|
|
66
89
|
* @returns {string}
|
|
@@ -74,17 +97,24 @@ export function deriveProjectRootFromPagesDir(pagesDir) {
|
|
|
74
97
|
return parent;
|
|
75
98
|
}
|
|
76
99
|
/**
|
|
77
|
-
* @param {{ manifest: Array<{ path: string, file: string }>, pagesDir: string }} input
|
|
100
|
+
* @param {{ manifest: Array<{ path: string, file: string, scoped_server_data?: import('../scoped-server-data/types.js').ManifestScopedServerDataEntry[] }>, pagesDir: string }} input
|
|
78
101
|
* @returns {Promise<void>}
|
|
79
102
|
*/
|
|
80
103
|
export async function ensureZenithTypeDeclarations(input) {
|
|
81
104
|
const projectRoot = deriveProjectRootFromPagesDir(input.pagesDir);
|
|
105
|
+
const srcDir = dirname(resolve(input.pagesDir));
|
|
82
106
|
const zenithDir = resolve(projectRoot, '.zenith');
|
|
83
107
|
await mkdir(zenithDir, { recursive: true });
|
|
84
108
|
const envPath = join(zenithDir, 'zenith-env.d.ts');
|
|
85
109
|
const routesPath = join(zenithDir, 'zenith-routes.d.ts');
|
|
110
|
+
const scopedPath = join(zenithDir, 'zenith-scoped-server-data.d.ts');
|
|
111
|
+
const renderScopedServerDataDts = await getScopedServerDataTypeRenderer();
|
|
86
112
|
writeIfChanged(envPath, renderZenithEnvDts());
|
|
87
113
|
writeIfChanged(routesPath, renderZenithRouteDts(input.manifest));
|
|
114
|
+
writeIfChanged(scopedPath, renderScopedServerDataDts({
|
|
115
|
+
manifest: input.manifest,
|
|
116
|
+
srcDir
|
|
117
|
+
}));
|
|
88
118
|
const tsconfigPath = resolve(projectRoot, 'tsconfig.json');
|
|
89
119
|
if (!existsSync(tsconfigPath)) {
|
|
90
120
|
return;
|
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
export function writeBuildOutputManifest({ coreOutputDir, staticDir, target, routeManifest, basePath }: {
|
|
1
|
+
export function writeBuildOutputManifest({ coreOutputDir, staticDir, target, routeManifest, basePath, globalMiddleware }: {
|
|
2
2
|
coreOutputDir: any;
|
|
3
3
|
staticDir: any;
|
|
4
4
|
target: any;
|
|
5
5
|
routeManifest: any;
|
|
6
6
|
basePath?: string | undefined;
|
|
7
|
+
globalMiddleware?: null | undefined;
|
|
7
8
|
}): Promise<{
|
|
8
|
-
schema_version: number;
|
|
9
|
-
zenith_version: any;
|
|
10
|
-
target: any;
|
|
11
|
-
base_path: string;
|
|
12
|
-
content_hash: any;
|
|
13
9
|
routes: {
|
|
14
10
|
html: any;
|
|
15
11
|
assets: any[];
|
|
@@ -26,4 +22,12 @@ export function writeBuildOutputManifest({ coreOutputDir, staticDir, target, rou
|
|
|
26
22
|
css: any[];
|
|
27
23
|
vendor: any;
|
|
28
24
|
};
|
|
25
|
+
global_middleware?: {
|
|
26
|
+
source_file: any;
|
|
27
|
+
} | undefined;
|
|
28
|
+
schema_version: number;
|
|
29
|
+
zenith_version: any;
|
|
30
|
+
target: any;
|
|
31
|
+
base_path: string;
|
|
32
|
+
content_hash: any;
|
|
29
33
|
}>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { normalizeGlobalMiddlewareMetadata } from './global-middleware.js';
|
|
4
5
|
const CLI_VERSION = (() => {
|
|
5
6
|
try {
|
|
6
7
|
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
@@ -39,7 +40,7 @@ async function readRouteHtml(staticDir, htmlPath) {
|
|
|
39
40
|
return '';
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
export async function writeBuildOutputManifest({ coreOutputDir, staticDir, target, routeManifest, basePath = '/' }) {
|
|
43
|
+
export async function writeBuildOutputManifest({ coreOutputDir, staticDir, target, routeManifest, basePath = '/', globalMiddleware = null }) {
|
|
43
44
|
const bundlerManifest = await readJson(join(staticDir, 'manifest.json'), {});
|
|
44
45
|
const routerManifest = await readJson(join(staticDir, 'assets', 'router-manifest.json'), { routes: [] });
|
|
45
46
|
const routeByPath = new Map((Array.isArray(routerManifest.routes) ? routerManifest.routes : []).map((entry) => [entry.path, entry]));
|
|
@@ -85,12 +86,14 @@ export async function writeBuildOutputManifest({ coreOutputDir, staticDir, targe
|
|
|
85
86
|
bundlerManifest.css,
|
|
86
87
|
...routeAssetCss
|
|
87
88
|
].filter((value) => typeof value === 'string' && value.endsWith('.css')));
|
|
89
|
+
const globalMiddlewareMetadata = normalizeGlobalMiddlewareMetadata(globalMiddleware);
|
|
88
90
|
const buildManifest = {
|
|
89
91
|
schema_version: 1,
|
|
90
92
|
zenith_version: CLI_VERSION,
|
|
91
93
|
target,
|
|
92
94
|
base_path: basePath,
|
|
93
95
|
content_hash: typeof bundlerManifest.hash === 'string' ? bundlerManifest.hash : '',
|
|
96
|
+
...(globalMiddlewareMetadata ? { global_middleware: globalMiddlewareMetadata } : {}),
|
|
94
97
|
routes,
|
|
95
98
|
assets: {
|
|
96
99
|
js: [...jsAssets].sort(),
|
package/dist/build.js
CHANGED
|
@@ -16,6 +16,7 @@ import { createImageRuntimePayload, injectImageRuntimePayloadIntoHtmlFiles } fro
|
|
|
16
16
|
import { supportsTargetRouteCheck } from './route-check-support.js';
|
|
17
17
|
import { createStartupProfiler } from './startup-profile.js';
|
|
18
18
|
import { writeServerOutput } from './server-output.js';
|
|
19
|
+
import { resolveGlobalMiddleware } from './global-middleware.js';
|
|
19
20
|
import { resolveBundlerBin } from './toolchain-paths.js';
|
|
20
21
|
import { createBundlerToolchain, createCompilerToolchain, ensureToolchainCompatibility, getActiveToolchainCandidate } from './toolchain-runner.js';
|
|
21
22
|
import { maybeWarnAboutZenithVersionMismatch } from './version-check.js';
|
|
@@ -75,6 +76,7 @@ export async function build(options) {
|
|
|
75
76
|
}));
|
|
76
77
|
}
|
|
77
78
|
const registry = startupProfile.measureSync('build_component_registry', () => buildComponentRegistry(srcDir));
|
|
79
|
+
const globalMiddleware = await startupProfile.measureAsync('resolve_global_middleware', () => resolveGlobalMiddleware({ projectRoot, pagesDir, target }));
|
|
78
80
|
const manifest = await startupProfile.measureAsync('generate_manifest', () => generateManifest(pagesDir, '.zen', { compilerOpts }));
|
|
79
81
|
const pageManifest = manifest.filter((entry) => entry?.route_kind !== 'resource');
|
|
80
82
|
if (mode !== 'legacy') {
|
|
@@ -136,14 +138,21 @@ export async function build(options) {
|
|
|
136
138
|
staticDir: staticOutputDir,
|
|
137
139
|
target,
|
|
138
140
|
routeManifest: pageManifest,
|
|
139
|
-
basePath
|
|
141
|
+
basePath,
|
|
142
|
+
globalMiddleware: globalMiddleware?.metadata || null
|
|
140
143
|
}));
|
|
141
144
|
await startupProfile.measureAsync('write_server_output', () => writeServerOutput({
|
|
142
145
|
coreOutputDir,
|
|
143
146
|
staticDir: staticOutputDir,
|
|
144
147
|
projectRoot,
|
|
145
148
|
config,
|
|
146
|
-
basePath
|
|
149
|
+
basePath,
|
|
150
|
+
globalMiddleware: globalMiddleware?.metadata || null,
|
|
151
|
+
pageManifest,
|
|
152
|
+
pagesDir,
|
|
153
|
+
srcDir,
|
|
154
|
+
registry,
|
|
155
|
+
compilerOpts
|
|
147
156
|
}));
|
|
148
157
|
await startupProfile.measureAsync('adapt_output', () => adapter.adapt({ coreOutput: coreOutputDir, outDir, manifest: buildManifest, config }));
|
|
149
158
|
const assets = await startupProfile.measureAsync('collect_assets', () => collectAssets(outDir));
|
|
@@ -388,6 +388,7 @@ export function applyOccurrenceRewritePlans(pageIr, occurrencePlans, resolveBind
|
|
|
388
388
|
binding.state_index = resolved.state_index;
|
|
389
389
|
binding.component_instance = resolved.component_instance;
|
|
390
390
|
binding.component_binding = resolved.component_binding;
|
|
391
|
+
binding.scoped_data_key = resolved.scoped_data_key;
|
|
391
392
|
}
|
|
392
393
|
}
|
|
393
394
|
exprCursor = found + 1;
|
|
@@ -4,3 +4,12 @@ export function collectExpandedComponentOccurrences(source: any, registry: any,
|
|
|
4
4
|
ownerPath: string;
|
|
5
5
|
componentPath: string;
|
|
6
6
|
}[];
|
|
7
|
+
/**
|
|
8
|
+
* Unique layout/component `.zen` paths reachable from a page dependency graph.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} source
|
|
11
|
+
* @param {Map<string, string>} registry
|
|
12
|
+
* @param {string} sourceFile
|
|
13
|
+
* @returns {string[]}
|
|
14
|
+
*/
|
|
15
|
+
export function collectReachableOwnerPaths(source: string, registry: Map<string, string>, sourceFile: string): string[];
|
|
@@ -10,6 +10,24 @@ export function collectExpandedComponentOccurrences(source, registry, sourceFile
|
|
|
10
10
|
}, [], occurrences);
|
|
11
11
|
return occurrences;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Unique layout/component `.zen` paths reachable from a page dependency graph.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} source
|
|
17
|
+
* @param {Map<string, string>} registry
|
|
18
|
+
* @param {string} sourceFile
|
|
19
|
+
* @returns {string[]}
|
|
20
|
+
*/
|
|
21
|
+
export function collectReachableOwnerPaths(source, registry, sourceFile) {
|
|
22
|
+
const occurrences = collectExpandedComponentOccurrences(source, registry, sourceFile);
|
|
23
|
+
const paths = new Set();
|
|
24
|
+
for (const occurrence of occurrences) {
|
|
25
|
+
if (occurrence.componentPath) {
|
|
26
|
+
paths.add(occurrence.componentPath);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return [...paths];
|
|
30
|
+
}
|
|
13
31
|
function walkSource(source, registry, context, chain, occurrences) {
|
|
14
32
|
let cursor = 0;
|
|
15
33
|
while (cursor < source.length) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function normalizePlugins(value: any): ({
|
|
2
|
+
name: any;
|
|
3
|
+
config: any;
|
|
4
|
+
} | {
|
|
5
|
+
name: any;
|
|
6
|
+
config?: undefined;
|
|
7
|
+
})[];
|
|
8
|
+
export function assertPluginConfigPatch(value: any): void;
|
|
9
|
+
export function cloneConfigValue(value: any, seen?: Map<any, any>): any;
|
|
10
|
+
export function deepFreeze(value: any, seen?: Set<any>): any;
|
|
11
|
+
export function pluginHookError(pluginName: any, hookName: any, error: any): Error;
|
|
12
|
+
export const PLUGIN_CONFIG_PATCH_KEYS: Set<string>;
|