@vesk/adapter 0.2.10 → 0.2.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/dist/client-bundle.d.ts +29 -0
- package/dist/client-bundle.d.ts.map +1 -1
- package/dist/client-bundle.js +333 -52
- package/dist/dev-api.d.ts +78 -0
- package/dist/dev-api.d.ts.map +1 -0
- package/dist/dev-api.js +338 -0
- package/dist/dev-config.d.ts +48 -0
- package/dist/dev-config.d.ts.map +1 -0
- package/dist/dev-config.js +964 -0
- package/dist/dev-server.d.ts +88 -0
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +304 -4
- package/dist/error-codeframe.d.ts +23 -0
- package/dist/error-codeframe.d.ts.map +1 -0
- package/dist/error-codeframe.js +127 -0
- package/dist/error-tips.d.ts +7 -0
- package/dist/error-tips.d.ts.map +1 -0
- package/dist/error-tips.js +91 -0
- package/dist/hmr-utils.d.ts +14 -0
- package/dist/hmr-utils.d.ts.map +1 -0
- package/dist/hmr-utils.js +56 -0
- package/dist/hmr.d.ts +40 -0
- package/dist/hmr.d.ts.map +1 -1
- package/dist/hmr.js +139 -20
- package/dist/index.d.ts +37 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +105 -22
- package/dist/plugins.d.ts +147 -0
- package/dist/plugins.d.ts.map +1 -0
- package/dist/plugins.js +1109 -0
- package/dist/prod-server.d.ts.map +1 -1
- package/dist/prod-server.js +14 -2
- package/package.json +4 -4
package/dist/client-bundle.d.ts
CHANGED
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
import type { RouteNode, ClientBundleOptions, ClientBundleResult } from '@vesk/adapter/src/types';
|
|
2
|
+
/**
|
|
3
|
+
* Pure name-collection used by `generateClientBundle`'s tree-shake request.
|
|
4
|
+
*
|
|
5
|
+
* Parses the (already compiled) client module with the compiler's AST pass and
|
|
6
|
+
* keeps only genuine top-level `import { … } from '@vesk/runtime…'` declarations.
|
|
7
|
+
* Import-shaped text inside template literals, strings, or comments — e.g. a
|
|
8
|
+
* doc code sample stored as `const md = \`import { VeskResponse } from
|
|
9
|
+
* '@vesk/runtime/server'\`` — never becomes an ImportDeclaration node, so those
|
|
10
|
+
* server-only names cannot poison the client runtime request. This is
|
|
11
|
+
* deliberately AST-only (no text scanning): text heuristics are what let
|
|
12
|
+
* server-only names leak into the client tree-shake request in the first
|
|
13
|
+
* place. If the compiled module ever fails to parse, we collect nothing rather
|
|
14
|
+
* than guess.
|
|
15
|
+
*/
|
|
16
|
+
export declare function extractRuntimeImportNames(code: string): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Builds the eval-safe HMR update snippet for one compiled file: drops
|
|
19
|
+
* imports, re-exports and the registry preamble (`const __components = …`,
|
|
20
|
+
* `const __hydrators = …`), demotes remaining value exports, and wraps the
|
|
21
|
+
* rest in a block. Component registrations (`__components["X"] = …`) still
|
|
22
|
+
* reach the live registries and stay visible to nothing else, file
|
|
23
|
+
* top-level bindings (`const navItems`, helpers, …) stay visible to the
|
|
24
|
+
* component closures that reference them, and repeated evals of the same
|
|
25
|
+
* file can no longer redeclare its top-level consts. Without the file
|
|
26
|
+
* scope, an updated component renders against missing bindings and the
|
|
27
|
+
* swap fails with a ReferenceError that surfaces nowhere the tests assert
|
|
28
|
+
* (no reload, stale DOM, zero page errors).
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildHmrEvalSnippet(code: string): string;
|
|
2
31
|
export declare function generateClientBundle(routeTree: RouteNode[], appDir: string, componentMap?: Map<string, string>, options?: ClientBundleOptions): Promise<ClientBundleResult>;
|
|
3
32
|
export declare function buildRuntimeCode(runtimeDir: string): string;
|
|
4
33
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-bundle.d.ts","sourceRoot":"","sources":["../src/client-bundle.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client-bundle.d.ts","sourceRoot":"","sources":["../src/client-bundle.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAqC,MAAM,yBAAyB,CAAC;AAoCrI;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA8BhE;AAoLD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBxD;AAmCD,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,SAAS,EAAE,EACtB,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CA0S7B;AAMD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA+B3D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAUlE;AAYD;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuCrG"}
|
package/dist/client-bundle.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { readFileSync, existsSync, writeFileSync, unlinkSync, statSync } from 'node:fs';
|
|
1
|
+
import { readFileSync, existsSync, writeFileSync, unlinkSync, statSync, rmSync } from 'node:fs';
|
|
2
2
|
import { resolve, join, dirname, relative, sep } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { build } from './esbuild-fallback.js';
|
|
5
5
|
import { stripCodeTypes } from '@vesk/compiler/src/strip-ts';
|
|
6
|
+
import { parse } from '@vesk/compiler/src/parser';
|
|
6
7
|
import { compileClient, compileClientBoth } from '@vesk/compiler/src/client-codegen';
|
|
7
8
|
import { resolveComponentName } from '@vesk/compiler/src/server-codegen';
|
|
8
9
|
import { collectVskImportPaths, vskImportLines } from '@vesk/compiler/src/vsk-imports';
|
|
@@ -39,6 +40,276 @@ function findRuntimeSrc(appDir) {
|
|
|
39
40
|
}
|
|
40
41
|
throw new Error('@vesk/runtime/dist not found — run "npm run build" first');
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Pure name-collection used by `generateClientBundle`'s tree-shake request.
|
|
45
|
+
*
|
|
46
|
+
* Parses the (already compiled) client module with the compiler's AST pass and
|
|
47
|
+
* keeps only genuine top-level `import { … } from '@vesk/runtime…'` declarations.
|
|
48
|
+
* Import-shaped text inside template literals, strings, or comments — e.g. a
|
|
49
|
+
* doc code sample stored as `const md = \`import { VeskResponse } from
|
|
50
|
+
* '@vesk/runtime/server'\`` — never becomes an ImportDeclaration node, so those
|
|
51
|
+
* server-only names cannot poison the client runtime request. This is
|
|
52
|
+
* deliberately AST-only (no text scanning): text heuristics are what let
|
|
53
|
+
* server-only names leak into the client tree-shake request in the first
|
|
54
|
+
* place. If the compiled module ever fails to parse, we collect nothing rather
|
|
55
|
+
* than guess.
|
|
56
|
+
*/
|
|
57
|
+
export function extractRuntimeImportNames(code) {
|
|
58
|
+
const names = [];
|
|
59
|
+
let ast;
|
|
60
|
+
try {
|
|
61
|
+
ast = parse(code);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return names;
|
|
65
|
+
}
|
|
66
|
+
const body = ast.body ?? [];
|
|
67
|
+
for (const raw of body) {
|
|
68
|
+
const node = raw;
|
|
69
|
+
if (node.type !== 'ImportDeclaration')
|
|
70
|
+
continue;
|
|
71
|
+
const src = node.source?.value;
|
|
72
|
+
if (typeof src !== 'string' || !(src === '@vesk/runtime' || src.startsWith('@vesk/runtime/')))
|
|
73
|
+
continue;
|
|
74
|
+
if (node.importKind === 'type')
|
|
75
|
+
continue;
|
|
76
|
+
for (const spec of node.specifiers ?? []) {
|
|
77
|
+
const name = spec.type === 'ImportSpecifier' ? spec.imported?.name : spec.local?.name;
|
|
78
|
+
if (name)
|
|
79
|
+
names.push(name);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return names;
|
|
83
|
+
}
|
|
84
|
+
function isRuntimeImport(node) {
|
|
85
|
+
if (node.type !== 'ImportDeclaration')
|
|
86
|
+
return false;
|
|
87
|
+
const src = node.source?.value;
|
|
88
|
+
return src === '@vesk/runtime' || (typeof src === 'string' && src.startsWith('@vesk/runtime/'));
|
|
89
|
+
}
|
|
90
|
+
function isVskImport(node) {
|
|
91
|
+
if (node.type !== 'ImportDeclaration')
|
|
92
|
+
return false;
|
|
93
|
+
const src = node.source?.value;
|
|
94
|
+
return typeof src === 'string' && src.endsWith('.vsk');
|
|
95
|
+
}
|
|
96
|
+
function isAnyImport(node) {
|
|
97
|
+
return node.type === 'ImportDeclaration';
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Generated client-preamble nodes the bundle must not re-emit: the local
|
|
101
|
+
* `const __components = {}` registry plus the `__cleanup` / `__place` helpers —
|
|
102
|
+
* the chunk IIFEs already define their own `__components`, so a second
|
|
103
|
+
* declaration in the same scope would be a syntax error.
|
|
104
|
+
*/
|
|
105
|
+
function isRuntimePreamble(node) {
|
|
106
|
+
if (node.type === 'FunctionDeclaration' && (node.id?.name === '__cleanup' || node.id?.name === '__place')) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (node.type === 'VariableDeclaration') {
|
|
110
|
+
return node.declarations?.[0]?.id?.name === '__components';
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* `export default __components[<name>];` / `export const <name> = __components[<name>];`
|
|
116
|
+
* aliases emitted by codegen. The bundle registers components on `__components`
|
|
117
|
+
* directly, so these module-level re-exports must go.
|
|
118
|
+
*/
|
|
119
|
+
function isComponentExport(node) {
|
|
120
|
+
if (node.type === 'ExportDefaultDeclaration') {
|
|
121
|
+
const d = node.declaration;
|
|
122
|
+
return !!d && d.type === 'MemberExpression' && d.object?.type === 'Identifier' && d.object.name === '__components';
|
|
123
|
+
}
|
|
124
|
+
if (node.type === 'ExportNamedDeclaration') {
|
|
125
|
+
const init = node.declaration?.type === 'VariableDeclaration' ? node.declaration.declarations?.[0]?.init : undefined;
|
|
126
|
+
return !!init && init.type === 'MemberExpression' && init.object?.type === 'Identifier' && init.object.name === '__components';
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Extracts matched top-level statements from a compiled client module using
|
|
132
|
+
* the parser's exact `start`/`end` offsets — the complement of
|
|
133
|
+
* `removeCompiledNodes`. AST-only, like everything else in this file.
|
|
134
|
+
*/
|
|
135
|
+
function extractCompiledNodes(code, isTarget) {
|
|
136
|
+
let ast;
|
|
137
|
+
try {
|
|
138
|
+
ast = parse(code);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
const parts = [];
|
|
144
|
+
for (const raw of ast.body ?? []) {
|
|
145
|
+
const node = raw;
|
|
146
|
+
if (isTarget(node) && typeof node.start === 'number' && typeof node.end === 'number') {
|
|
147
|
+
let cut = node.end;
|
|
148
|
+
if (code[cut] === ';')
|
|
149
|
+
cut++;
|
|
150
|
+
parts.push(code.slice(node.start, cut));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return parts.join('\n');
|
|
154
|
+
}
|
|
155
|
+
function isModuleLevel(node) {
|
|
156
|
+
// import/export statements must stay at module top level (hoisted imports
|
|
157
|
+
// of the same module are harmless duplicates); everything else is scoped.
|
|
158
|
+
return node.type === 'ImportDeclaration'
|
|
159
|
+
|| node.type === 'ExportNamedDeclaration'
|
|
160
|
+
|| node.type === 'ExportDefaultDeclaration';
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Demotes surviving value exports to plain declarations (`export const x`
|
|
164
|
+
* becomes `const x`) and drops bare re-export lists (`export { x }`,
|
|
165
|
+
* `export default …`). Code-split chunks execute as classic scripts inside
|
|
166
|
+
* an IIFE, so any `export` statement is a SyntaxError — and the chunk-local
|
|
167
|
+
* binding is all cross-file references need (components resolve through the
|
|
168
|
+
* `__components` / `__hydrators` registries). Offset-based on the parser AST.
|
|
169
|
+
*/
|
|
170
|
+
function demoteExports(code) {
|
|
171
|
+
let ast;
|
|
172
|
+
try {
|
|
173
|
+
ast = parse(code);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return code;
|
|
177
|
+
}
|
|
178
|
+
const body = ast.body ?? [];
|
|
179
|
+
const edits = [];
|
|
180
|
+
for (const raw of body) {
|
|
181
|
+
const node = raw;
|
|
182
|
+
if (typeof node.start !== 'number' || typeof node.end !== 'number')
|
|
183
|
+
continue;
|
|
184
|
+
if (node.type === 'ExportNamedDeclaration' && node.declaration
|
|
185
|
+
&& typeof node.declaration.start === 'number' && typeof node.declaration.end === 'number') {
|
|
186
|
+
edits.push({ start: node.start, end: node.end, text: code.slice(node.declaration.start, node.declaration.end) });
|
|
187
|
+
}
|
|
188
|
+
else if (node.type === 'ExportNamedDeclaration' || node.type === 'ExportDefaultDeclaration') {
|
|
189
|
+
let cut = node.end;
|
|
190
|
+
if (code[cut] === ';')
|
|
191
|
+
cut++;
|
|
192
|
+
edits.push({ start: node.start, end: cut, text: '' });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
edits.sort((a, b) => b.start - a.start);
|
|
196
|
+
for (const e of edits)
|
|
197
|
+
code = code.slice(0, e.start) + e.text + code.slice(e.end);
|
|
198
|
+
return code;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Code-split chunks execute as classic scripts, so NO import statement may
|
|
202
|
+
* survive in a contribution (runtime and `.vsk` imports are stripped
|
|
203
|
+
* earlier; npm packages and relative `.ts` values are unresolvable from a
|
|
204
|
+
* `/_vesk/static/` classic script). Strips every remaining import and
|
|
205
|
+
* demotes value exports — server-only components never execute
|
|
206
|
+
* client-side, and islands must receive data through props/serialization
|
|
207
|
+
* rather than module imports (a stripped binding referenced by an island
|
|
208
|
+
* fails loudly at that island instead of killing the whole chunk).
|
|
209
|
+
*/
|
|
210
|
+
function stripChunkImports(code) {
|
|
211
|
+
return demoteExports(removeCompiledNodes(code, isAnyImport));
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Scopes one file's contribution to the shared chunk: module-level
|
|
215
|
+
* import/export statements pass through untouched, all other top-level
|
|
216
|
+
* statements (component registrations, top-level `const` helpers, …) are
|
|
217
|
+
* wrapped in a block. Without this, same-named top-level bindings from
|
|
218
|
+
* different `.vsk` files (e.g. two files declaring `const stages`) collide
|
|
219
|
+
* in the concatenated chunk scope and the whole chunk fails to parse —
|
|
220
|
+
* killing hydration for the entire page. Cross-file references always go
|
|
221
|
+
* through the `__components` / `__hydrators` registries (never bare
|
|
222
|
+
* identifiers), so hiding file locals in a block is safe; assignments like
|
|
223
|
+
* `__components["X"] = …` still reach the outer binding.
|
|
224
|
+
*/
|
|
225
|
+
function scopeFileContribution(code) {
|
|
226
|
+
const head = extractCompiledNodes(code, isModuleLevel);
|
|
227
|
+
const rest = removeCompiledNodes(code, isModuleLevel);
|
|
228
|
+
if (!rest.trim())
|
|
229
|
+
return head;
|
|
230
|
+
const scoped = (head ? head + '\n' : '') + '{\n' + rest + '\n}';
|
|
231
|
+
// Never trade a potential duplicate-binding error for a certain syntax
|
|
232
|
+
// error: if the scoped form does not parse, keep the original.
|
|
233
|
+
try {
|
|
234
|
+
parse(scoped);
|
|
235
|
+
return scoped;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return code;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Builds the eval-safe HMR update snippet for one compiled file: drops
|
|
243
|
+
* imports, re-exports and the registry preamble (`const __components = …`,
|
|
244
|
+
* `const __hydrators = …`), demotes remaining value exports, and wraps the
|
|
245
|
+
* rest in a block. Component registrations (`__components["X"] = …`) still
|
|
246
|
+
* reach the live registries and stay visible to nothing else, file
|
|
247
|
+
* top-level bindings (`const navItems`, helpers, …) stay visible to the
|
|
248
|
+
* component closures that reference them, and repeated evals of the same
|
|
249
|
+
* file can no longer redeclare its top-level consts. Without the file
|
|
250
|
+
* scope, an updated component renders against missing bindings and the
|
|
251
|
+
* swap fails with a ReferenceError that surfaces nowhere the tests assert
|
|
252
|
+
* (no reload, stale DOM, zero page errors).
|
|
253
|
+
*/
|
|
254
|
+
export function buildHmrEvalSnippet(code) {
|
|
255
|
+
const isSnippetDrop = (node) => {
|
|
256
|
+
if (isAnyImport(node) || isComponentExport(node) || isRuntimePreamble(node))
|
|
257
|
+
return true;
|
|
258
|
+
return node.type === 'VariableDeclaration'
|
|
259
|
+
&& (node.declarations?.[0]?.id?.name === '__components'
|
|
260
|
+
|| node.declarations?.[0]?.id?.name === '__hydrators');
|
|
261
|
+
};
|
|
262
|
+
const body = demoteExports(removeCompiledNodes(code, isSnippetDrop)).trim();
|
|
263
|
+
if (!body)
|
|
264
|
+
return '';
|
|
265
|
+
const scoped = `{\n${body}\n}`;
|
|
266
|
+
// Never trade a potential duplicate-binding error for a certain syntax
|
|
267
|
+
// error: if the scoped form does not parse, keep the unscoped body.
|
|
268
|
+
try {
|
|
269
|
+
parse(scoped);
|
|
270
|
+
return scoped;
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return body;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Removes matched top-level statements from a compiled client module using the
|
|
278
|
+
* parser's exact `start`/`end` offsets — never a text scan, so a doc sample
|
|
279
|
+
* stored inside a template literal can never be mistaken for a real statement.
|
|
280
|
+
* If the module cannot be parsed it is returned untouched (the compiler's own
|
|
281
|
+
* output is always valid ESM, so that branch is unreachable in practice).
|
|
282
|
+
*/
|
|
283
|
+
function removeCompiledNodes(code, isTarget) {
|
|
284
|
+
let ast;
|
|
285
|
+
try {
|
|
286
|
+
ast = parse(code);
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return code;
|
|
290
|
+
}
|
|
291
|
+
const ranges = [];
|
|
292
|
+
for (const raw of ast.body ?? []) {
|
|
293
|
+
const node = raw;
|
|
294
|
+
if (isTarget(node) && typeof node.start === 'number' && typeof node.end === 'number') {
|
|
295
|
+
ranges.push([node.start, node.end]);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (ranges.length === 0)
|
|
299
|
+
return code;
|
|
300
|
+
ranges.sort((a, b) => b[0] - a[0]);
|
|
301
|
+
for (const [start, end] of ranges) {
|
|
302
|
+
let cut = end;
|
|
303
|
+
if (code[cut] === ';')
|
|
304
|
+
cut++;
|
|
305
|
+
if (code.startsWith('\r\n', cut))
|
|
306
|
+
cut += 2;
|
|
307
|
+
else if (code[cut] === '\n' || code[cut] === '\r')
|
|
308
|
+
cut++;
|
|
309
|
+
code = code.slice(0, start) + code.slice(cut);
|
|
310
|
+
}
|
|
311
|
+
return code;
|
|
312
|
+
}
|
|
42
313
|
export async function generateClientBundle(routeTree, appDir, componentMap, options) {
|
|
43
314
|
const runtimeDir = findRuntimeSrc(appDir);
|
|
44
315
|
const seen = new Set();
|
|
@@ -55,27 +326,22 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
|
|
|
55
326
|
function mustReuseWithoutStat(filePath) {
|
|
56
327
|
return !!only && !only.has(filePath);
|
|
57
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Collects the runtime names a module genuinely imports from `@vesk/runtime*`
|
|
331
|
+
* (see `extractRuntimeImportNames`). Deliberately AST-only — import-shaped
|
|
332
|
+
* text inside template literals (e.g. a doc sample `const md = \`import {
|
|
333
|
+
* VeskResponse } from '@vesk/runtime/server'\``) must NOT force a client
|
|
334
|
+
* tree-shake request for a server-only name.
|
|
335
|
+
*/
|
|
58
336
|
function collectRuntimeImports(code) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const re = /^import\s*\{([^}]*)\}\s*from\s*['"]@vesk\/runtime(\/[a-zA-Z0-9-]+)*['"];?\s*\n?/gm;
|
|
62
|
-
for (const m of code.matchAll(re)) {
|
|
63
|
-
for (const name of m[1].split(',')) {
|
|
64
|
-
const trimmed = name.trim().replace(/^(\w+)\s+as\s+.*$/, '$1');
|
|
65
|
-
if (!trimmed || /^(type|typeof)\s/.test(trimmed))
|
|
66
|
-
continue;
|
|
67
|
-
runtimeImportNames.add(trimmed);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
337
|
+
for (const name of extractRuntimeImportNames(code))
|
|
338
|
+
runtimeImportNames.add(name);
|
|
70
339
|
}
|
|
71
340
|
function stripRuntimeImport(code) {
|
|
72
|
-
return code
|
|
73
|
-
.replace(/const\s+__components\s*=\s*\{\};\s*\n?/g, '')
|
|
74
|
-
.replace(/^function __cleanup\(start, end\) \{[\s\S]*?\n\}\s*\n?/gm, '')
|
|
75
|
-
.replace(/^function __place\(start, end, nodes, fallback\) \{[\s\S]*?\n\}\s*\n?/gm, '');
|
|
341
|
+
return removeCompiledNodes(code, (node) => isRuntimeImport(node) || isRuntimePreamble(node));
|
|
76
342
|
}
|
|
77
343
|
function stripVskImports(code) {
|
|
78
|
-
return code
|
|
344
|
+
return removeCompiledNodes(code, isVskImport);
|
|
79
345
|
}
|
|
80
346
|
function resolveVskImports(filePath, compile) {
|
|
81
347
|
const src = readFileSync(filePath, 'utf-8');
|
|
@@ -97,9 +363,7 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
|
|
|
97
363
|
return resolved;
|
|
98
364
|
}
|
|
99
365
|
function stripExports(code) {
|
|
100
|
-
return code
|
|
101
|
-
.replace(/^export\s+default\s+__components\[.*?\];?\s*\n?/gm, '')
|
|
102
|
-
.replace(/^export\s+(const|let|var)\s+\w+\s*=\s*__components\[.*?\];?\s*\n?/gm, '');
|
|
366
|
+
return removeCompiledNodes(code, isComponentExport);
|
|
103
367
|
}
|
|
104
368
|
function compileFile(filePath, resolvedName, output) {
|
|
105
369
|
if (seen.has(filePath))
|
|
@@ -127,10 +391,12 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
|
|
|
127
391
|
cachedFileHits++;
|
|
128
392
|
for (const dep of cached.imports)
|
|
129
393
|
compileFile(dep, cache?.files.get(dep)?.actualName ?? '', output);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
394
|
+
// Comp and hyd contributions of one file share its top-level bindings
|
|
395
|
+
// (e.g. `const links` used by both the component and its hydrator),
|
|
396
|
+
// so they must be scoped together in a single block.
|
|
397
|
+
const cachedFileCode = [cached.compCode, cached.hydCode].filter(Boolean).join('\n');
|
|
398
|
+
if (cachedFileCode.trim())
|
|
399
|
+
output.push(scopeFileContribution(cachedFileCode));
|
|
134
400
|
for (const n of cached.runtimeNames)
|
|
135
401
|
runtimeImportNames.add(n);
|
|
136
402
|
if (cached.actualName && resolvedName !== null && cached.actualName !== resolvedName) {
|
|
@@ -149,28 +415,26 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
|
|
|
149
415
|
// One parse/IR pass feeds both client modes AND the component-name
|
|
150
416
|
// lookup — the dev hot path pays the acorn+TS parse once per edit
|
|
151
417
|
// instead of three times.
|
|
152
|
-
const { comp: rawComp, hyd: rawHyd, name: actualName } = compileClientBoth(src, null);
|
|
153
|
-
const compCode = rawComp ? stripExports(stripVskImports(stripRuntimeImport(rawComp))).replace(/^\n+/, '').replace(/\n+$/, '') : '';
|
|
154
|
-
const hydCode = rawHyd ? stripExports(stripVskImports(stripRuntimeImport(rawHyd))).replace(/__components/g, '__hydrators').replace(/^\n+/, '').replace(/\n+$/, '') : '';
|
|
418
|
+
const { comp: rawComp, hyd: rawHyd, name: actualName } = compileClientBoth(src, null, filePath);
|
|
419
|
+
const compCode = rawComp ? stripChunkImports(stripExports(stripVskImports(stripRuntimeImport(rawComp)))).replace(/^\n+/, '').replace(/\n+$/, '') : '';
|
|
420
|
+
const hydCode = rawHyd ? stripChunkImports(stripExports(stripVskImports(stripRuntimeImport(rawHyd)))).replace(/__components/g, '__hydrators').replace(/^\n+/, '').replace(/\n+$/, '') : '';
|
|
155
421
|
if (rawComp)
|
|
156
422
|
collectRuntimeImports(rawComp);
|
|
157
423
|
if (rawHyd)
|
|
158
424
|
collectRuntimeImports(rawHyd);
|
|
159
425
|
if (returnEdited && only.has(filePath)) {
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
.replace(/^import\s*[\s\S]*?from\s*['"][^'"]+['"];?\s*\n?/gm, '')
|
|
166
|
-
: '';
|
|
167
|
-
editedSources.set(filePath, bare);
|
|
426
|
+
// Eval-safe snippet for HMR fnSources (see buildHmrEvalSnippet):
|
|
427
|
+
// full file scope so component closures keep their top-level
|
|
428
|
+
// bindings, without imports/exports or the registry preamble, and
|
|
429
|
+
// block-wrapped so repeated evals cannot redeclare them.
|
|
430
|
+
editedSources.set(filePath, rawComp ? buildHmrEvalSnippet(rawComp) : '');
|
|
168
431
|
editedNames.set(filePath, actualName);
|
|
169
432
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
433
|
+
// Comp and hyd contributions of one file share its top-level bindings,
|
|
434
|
+
// so they are scoped together in a single block (see replay path above).
|
|
435
|
+
const fileCode = [compCode, hydCode].filter(Boolean).join('\n');
|
|
436
|
+
if (fileCode.trim())
|
|
437
|
+
output.push(scopeFileContribution(fileCode));
|
|
174
438
|
if (actualName && resolvedName !== null && actualName !== resolvedName) {
|
|
175
439
|
output.push(`Object.defineProperty(__components, ${JSON.stringify(resolvedName)}, { get: () => __components[${JSON.stringify(actualName)}], configurable: true });`);
|
|
176
440
|
output.push(`Object.defineProperty(__hydrators, ${JSON.stringify(resolvedName)}, { get: () => __hydrators[${JSON.stringify(actualName)}], configurable: true });`);
|
|
@@ -286,19 +550,18 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
|
|
|
286
550
|
seen.add(filePath);
|
|
287
551
|
const src = readFileSync(filePath, 'utf-8');
|
|
288
552
|
resolveVskImports(filePath, (p, n) => compileFileMono(p, n || ''));
|
|
289
|
-
const compCode = compileClient(src, null, { forceClient: true });
|
|
290
|
-
|
|
553
|
+
const compCode = compileClient(src, null, { forceClient: true, sourcePath: filePath });
|
|
554
|
+
const hydCode = compileClient(src, null, { hydrate: true, forceClient: true, includeTopLevel: false, sourcePath: filePath });
|
|
555
|
+
if (compCode)
|
|
291
556
|
collectRuntimeImports(compCode);
|
|
292
|
-
|
|
293
|
-
componentLines.push(stripped.replace(/^\n+/, '').replace(/\n+$/, ''));
|
|
294
|
-
}
|
|
295
|
-
const hydCode = compileClient(src, null, { hydrate: true, forceClient: true, includeTopLevel: false });
|
|
296
|
-
if (hydCode) {
|
|
557
|
+
if (hydCode)
|
|
297
558
|
collectRuntimeImports(hydCode);
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
559
|
+
// Comp and hyd share the file's top-level bindings — scope together.
|
|
560
|
+
const strippedComp = compCode ? stripExports(stripVskImports(stripRuntimeImport(compCode))).replace(/^\n+/, '').replace(/\n+$/, '') : '';
|
|
561
|
+
const strippedHyd = hydCode ? stripExports(stripVskImports(stripRuntimeImport(hydCode))).replace(/__components/g, '__hydrators').replace(/^\n+/, '').replace(/\n+$/, '') : '';
|
|
562
|
+
const fileCode = [strippedComp, strippedHyd].filter(Boolean).join('\n');
|
|
563
|
+
if (fileCode.trim())
|
|
564
|
+
componentLines.push(scopeFileContribution(fileCode));
|
|
302
565
|
const actualName = resolveComponentName(src);
|
|
303
566
|
if (actualName && actualName !== resolvedName) {
|
|
304
567
|
aliasLines.push(`Object.defineProperty(__components, ${JSON.stringify(resolvedName)}, { get: () => __components[${JSON.stringify(actualName)}], configurable: true });`);
|
|
@@ -395,6 +658,13 @@ export function runtimeExportNames(runtimeDir) {
|
|
|
395
658
|
return names;
|
|
396
659
|
}
|
|
397
660
|
let runtimeEntryId = 0;
|
|
661
|
+
/**
|
|
662
|
+
* Fixed entry filename for the runtime tree-shake. A stable entry path keeps
|
|
663
|
+
* esbuild's banner comment constant, so the output is deterministic across
|
|
664
|
+
* builds (a per-call unique filename would embed a new path into the bundle
|
|
665
|
+
* every time).
|
|
666
|
+
*/
|
|
667
|
+
const RUNTIME_ENTRY = '.runtime-tree-entry.mjs';
|
|
398
668
|
/**
|
|
399
669
|
* Builds a single self-contained runtime module for the given used names.
|
|
400
670
|
*
|
|
@@ -412,8 +682,12 @@ export async function buildTreeShakenRuntime(runtimeDir, usedNames) {
|
|
|
412
682
|
console.error(`vesk: runtime names not exported — ${missing.join(', ')}; falling back to full runtime`);
|
|
413
683
|
return buildRuntimeCode(runtimeDir);
|
|
414
684
|
}
|
|
415
|
-
const entry = join(runtimeDir,
|
|
685
|
+
const entry = join(runtimeDir, RUNTIME_ENTRY);
|
|
416
686
|
try {
|
|
687
|
+
try {
|
|
688
|
+
rmSync(entry);
|
|
689
|
+
}
|
|
690
|
+
catch { /* not present yet */ }
|
|
417
691
|
writeFileSync(entry, `export { ${unique.join(', ')} } from './index-client.js';\n`);
|
|
418
692
|
const result = await build({
|
|
419
693
|
entryPoints: [entry],
|
|
@@ -423,7 +697,14 @@ export async function buildTreeShakenRuntime(runtimeDir, usedNames) {
|
|
|
423
697
|
platform: 'browser',
|
|
424
698
|
target: ['es2022'],
|
|
425
699
|
treeShaking: true,
|
|
426
|
-
|
|
700
|
+
// Do not let esbuild rename identifiers here. This bundle is one large
|
|
701
|
+
// IIFE built from many runtime modules, and the renaming pass of some
|
|
702
|
+
// esbuild versions (observed on 0.25.12) emits a free reference to an
|
|
703
|
+
// undefined minified name (ReferenceError: m is not defined) that kills
|
|
704
|
+
// client hydration for the whole app. Whitespace-only output keeps the
|
|
705
|
+
// runtime deterministic at a modest size cost; tree-shaking still prunes
|
|
706
|
+
// unreachable modules, so names coming out of the IIFE are untouched.
|
|
707
|
+
minify: false,
|
|
427
708
|
write: false,
|
|
428
709
|
logLevel: 'silent',
|
|
429
710
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { DevHmrState } from './dev-server';
|
|
2
|
+
import type { PluginRouterChangeEvent } from './dev-server';
|
|
3
|
+
export interface DiagnosticFinding {
|
|
4
|
+
severity: 'error' | 'warning' | 'info';
|
|
5
|
+
code: string;
|
|
6
|
+
file?: string | null;
|
|
7
|
+
line?: number | null;
|
|
8
|
+
column?: number | null;
|
|
9
|
+
message: string;
|
|
10
|
+
hint?: string | null;
|
|
11
|
+
}
|
|
12
|
+
export type CapabilityName = 'config.read' | 'config.write' | 'plugins' | 'diagnostics' | 'build' | 'file.read' | 'command';
|
|
13
|
+
export type DevApiCapabilities = Record<CapabilityName, boolean>;
|
|
14
|
+
/** Default DevTools capability set — `command` is gated off by default. */
|
|
15
|
+
export declare const DEFAULT_CAPABILITIES: DevApiCapabilities;
|
|
16
|
+
/** Gated-command allowlist (read-only/status commands only by default). */
|
|
17
|
+
export declare const DEFAULT_COMMAND_ALLOWLIST: RegExp[];
|
|
18
|
+
/** Server-enforced capability/permission table. */
|
|
19
|
+
export declare class CapabilityTable {
|
|
20
|
+
private caps;
|
|
21
|
+
private commandAllowlist;
|
|
22
|
+
constructor(caps?: Partial<DevApiCapabilities>, commandAllowlist?: RegExp[]);
|
|
23
|
+
allows(cap: CapabilityName): boolean;
|
|
24
|
+
commandAllowed(argv: string[]): boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface DevPanelResponse {
|
|
27
|
+
status: number;
|
|
28
|
+
headers: Record<string, string>;
|
|
29
|
+
body: string;
|
|
30
|
+
encoding?: 'utf8' | 'base64';
|
|
31
|
+
/** When set, `body`/`encoding` are ignored and this async iterable of
|
|
32
|
+
(already-framed) strings is streamed to the client instead — used for
|
|
33
|
+
SSE agent progress. */
|
|
34
|
+
stream?: AsyncIterable<string>;
|
|
35
|
+
}
|
|
36
|
+
export interface RebuildResult {
|
|
37
|
+
ok: boolean;
|
|
38
|
+
error?: string;
|
|
39
|
+
ms?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface CommandResult {
|
|
42
|
+
ok: boolean;
|
|
43
|
+
code: number;
|
|
44
|
+
stdout: string;
|
|
45
|
+
stderr: string;
|
|
46
|
+
}
|
|
47
|
+
export interface DevApiRouterOptions {
|
|
48
|
+
/** Directory containing app/ (parent of projectDir when config is at project root). */
|
|
49
|
+
appDir: string;
|
|
50
|
+
/** The `.vesk` state dir. */
|
|
51
|
+
veskDir: string;
|
|
52
|
+
/** Plugin names declared in config. */
|
|
53
|
+
configPluginNames: string[];
|
|
54
|
+
getHmrState?: () => DevHmrState;
|
|
55
|
+
onPluginChange?: (event: PluginRouterChangeEvent) => void | Promise<void>;
|
|
56
|
+
/** Capability overrides; `command` is off unless enabled + allowlisted. */
|
|
57
|
+
caps?: Partial<DevApiCapabilities>;
|
|
58
|
+
commandAllowlist?: RegExp[];
|
|
59
|
+
/** Dir where vesk.config.{ts,js} lives (defaults to dirname(appDir)). */
|
|
60
|
+
projectDir?: string;
|
|
61
|
+
/** Trigger a full rebuild + HMR reload (POST /__vesk/build). */
|
|
62
|
+
rebuild?: () => Promise<RebuildResult>;
|
|
63
|
+
/** Live diagnostics snapshot (GET /__vesk/diagnostics). */
|
|
64
|
+
getDiagnostics?: () => DiagnosticFinding[];
|
|
65
|
+
/** Gated command executor (POST /__vesk/command). */
|
|
66
|
+
runCommand?: (argv: string[]) => Promise<CommandResult>;
|
|
67
|
+
/** Response body cap. */
|
|
68
|
+
maxBodyBytes?: number;
|
|
69
|
+
}
|
|
70
|
+
export interface DevApiRouter {
|
|
71
|
+
route: (method: string, pathname: string, body?: unknown, search?: string) => Promise<DevPanelResponse | null>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build the unified DevTools API router. Exportable and dependency-injectable
|
|
75
|
+
* so the adapter + CLI dev servers can both route through it.
|
|
76
|
+
*/
|
|
77
|
+
export declare function createDevApiRouter(opts: DevApiRouterOptions): DevApiRouter;
|
|
78
|
+
//# sourceMappingURL=dev-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-api.d.ts","sourceRoot":"","sources":["../src/dev-api.ts"],"names":[],"mappings":"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"}
|