@vesk/adapter 0.1.14 → 0.2.9

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.
@@ -1 +1 @@
1
- {"version":3,"file":"api-function.d.ts","sourceRoot":"","sources":["../src/api-function.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAOhF,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,kBAAkB,GAC3B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAuLtD"}
1
+ {"version":3,"file":"api-function.d.ts","sourceRoot":"","sources":["../src/api-function.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAOhF,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,kBAAkB,GAC3B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAyLtD"}
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
- import { transformSync } from './esbuild-fallback.js';
3
+ import { stripCodeTypes } from '@vesk/compiler/src/strip-ts';
4
4
  function apiRouteName(fullPath) {
5
5
  const parts = fullPath.split('/').filter(Boolean);
6
6
  return parts.map(s => s.startsWith(':') ? s.slice(1) || 'param' : s).join('_') || 'index';
@@ -10,6 +10,8 @@ export function generateApiFunction(apiNode, _apiDir, outDir, options) {
10
10
  const name = apiRouteName(apiNode.fullPath);
11
11
  const funcPath = resolve(outDir, 'server', 'api', `${name}.js`);
12
12
  const routeFilePath = apiNode.filePath;
13
+ if (!routeFilePath)
14
+ throw new Error(`api route ${apiNode.fullPath} has no filePath`);
13
15
  let routeSrc = readFileSync(routeFilePath, 'utf-8');
14
16
  routeSrc = routeSrc
15
17
  .replace(/from\s+['"]@vesk\/runtime['"]\s*;?/g, "from '../runtime.js';")
@@ -18,8 +20,7 @@ export function generateApiFunction(apiNode, _apiDir, outDir, options) {
18
20
  });
19
21
  if (routeFilePath.endsWith('.ts')) {
20
22
  try {
21
- const result = transformSync(routeSrc, { loader: 'ts' });
22
- routeSrc = result.code;
23
+ routeSrc = stripCodeTypes(routeSrc);
23
24
  }
24
25
  catch {
25
26
  // fall back to original source if stripping fails
@@ -57,12 +58,13 @@ export function generateApiFunction(apiNode, _apiDir, outDir, options) {
57
58
  ' get: () => Object.fromEntries(url.searchParams.entries()),',
58
59
  ' enumerable: true,',
59
60
  ' });',
61
+ " const __rootLocals = (globalThis.__vesk_request && globalThis.__vesk_request.locals) || {};",
60
62
  ' // ── Middleware context ──',
61
63
  ' const __ctx = {',
62
64
  ' request,',
63
65
  ' params,',
64
66
  ' url,',
65
- ' locals: {},',
67
+ ' locals: Object.assign({}, __rootLocals),',
66
68
  " cookies: parseCookies(request.headers.get('cookie') || ''),",
67
69
  ' set(key, value) { this.locals[key] = value; },',
68
70
  ' get(key) { return this.locals[key]; },',
@@ -124,12 +126,13 @@ export function generateApiFunction(apiNode, _apiDir, outDir, options) {
124
126
  ' get: () => Object.fromEntries(url.searchParams.entries()),',
125
127
  ' enumerable: true,',
126
128
  ' });',
129
+ " const __rootLocals = (globalThis.__vesk_request && globalThis.__vesk_request.locals) || {};",
127
130
  ' const ctx = {',
128
131
  " headers: Object.fromEntries(request.headers.entries()),",
129
132
  ' url: request.url,',
130
133
  ' method,',
131
134
  " cookies: parseCookies(request.headers.get('cookie') || ''),",
132
- ' locals: {},',
135
+ ' locals: Object.assign({}, __rootLocals),',
133
136
  ' };',
134
137
  " Object.defineProperty(request, 'locals', {",
135
138
  ' get: () => ctx.locals,',
@@ -1 +1 @@
1
- {"version":3,"file":"client-bundle.d.ts","sourceRoot":"","sources":["../src/client-bundle.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAqC,MAAM,yBAAyB,CAAC;AA2BrI,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,CAqN7B;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;AAID;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA+BrG"}
1
+ {"version":3,"file":"client-bundle.d.ts","sourceRoot":"","sources":["../src/client-bundle.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAqC,MAAM,yBAAyB,CAAC;AAoCrI,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,CAoT7B;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;AAID;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA+BrG"}
@@ -1,11 +1,22 @@
1
- import { readFileSync, existsSync, writeFileSync, unlinkSync } from 'node:fs';
1
+ import { readFileSync, existsSync, writeFileSync, unlinkSync, statSync } from 'node:fs';
2
2
  import { resolve, join, dirname, relative, sep } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
- import { build, transformSync } from './esbuild-fallback.js';
5
- import { compileClient } from '@vesk/compiler/src/client-codegen';
4
+ import { build } from './esbuild-fallback.js';
5
+ import { stripCodeTypes } from '@vesk/compiler/src/strip-ts';
6
+ import { compileClient, compileClientBoth } from '@vesk/compiler/src/client-codegen';
6
7
  import { resolveComponentName } from '@vesk/compiler/src/server-codegen';
7
8
  import { collectVskImportPaths, vskImportLines } from '@vesk/compiler/src/vsk-imports';
9
+ import { inlineMdContentAttrs, guessProjectRoots } from '@vesk/compiler/src/md-inline';
8
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ function fileUnchanged(filePath, cached) {
12
+ try {
13
+ const st = statSync(filePath);
14
+ return st.mtimeMs === cached.mtimeMs && st.size === cached.size;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
9
20
  function buildRouterOpts(options) {
10
21
  const ttl = options?.routeDataCache;
11
22
  if (typeof ttl === 'number' && ttl > 0) {
@@ -33,8 +44,21 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
33
44
  const seen = new Set();
34
45
  const chunks = [];
35
46
  const runtimeImportNames = new Set();
47
+ const cache = options?.cache;
48
+ const only = options?.only && options.only.length > 0 ? new Set(options.only) : null;
49
+ const returnEdited = !!options?.returnEditedSources && !!only;
50
+ const editedSources = returnEdited ? new Map() : undefined;
51
+ const editedNames = returnEdited ? new Map() : undefined;
52
+ let cachedFileHits = 0;
53
+ let compiledFiles = 0;
54
+ let mainFromCache = false;
55
+ function mustReuseWithoutStat(filePath) {
56
+ return !!only && !only.has(filePath);
57
+ }
36
58
  function collectRuntimeImports(code) {
37
- const re = /^import\s*\{([^}]*)\}\s*from\s*['"]@vesk\/runtime['"];?\s*\n?/gm;
59
+ // matches bare '@vesk/runtime' and any of its subpaths
60
+ // ('@vesk/runtime/router', '@vesk/runtime/server', …)
61
+ const re = /^import\s*\{([^}]*)\}\s*from\s*['"]@vesk\/runtime(\/[a-zA-Z0-9-]+)*['"];?\s*\n?/gm;
38
62
  for (const m of code.matchAll(re)) {
39
63
  for (const name of m[1].split(',')) {
40
64
  const trimmed = name.trim().replace(/^(\w+)\s+as\s+.*$/, '$1');
@@ -45,7 +69,7 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
45
69
  }
46
70
  }
47
71
  function stripRuntimeImport(code) {
48
- return code.replace(/^import\s*\{[^}]*\}\s*from\s*['"]@vesk\/runtime['"];?\s*\n?/gm, '')
72
+ return code.replace(/^import\s*\{[^}]*\}\s*from\s*['"]@vesk\/runtime(\/[a-zA-Z0-9-]+)*['"];?\s*\n?/gm, '')
49
73
  .replace(/const\s+__components\s*=\s*\{\};\s*\n?/g, '')
50
74
  .replace(/^function __cleanup\(start, end\) \{[\s\S]*?\n\}\s*\n?/gm, '')
51
75
  .replace(/^function __place\(start, end, nodes, fallback\) \{[\s\S]*?\n\}\s*\n?/gm, '');
@@ -55,16 +79,22 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
55
79
  }
56
80
  function resolveVskImports(filePath, compile) {
57
81
  const src = readFileSync(filePath, 'utf-8');
82
+ const resolved = [];
58
83
  for (const importPath of collectVskImportPaths(vskImportLines(src), filePath)) {
59
- let importedName = null;
60
84
  try {
61
- importedName = resolveComponentName(readFileSync(importPath, 'utf-8'));
85
+ readFileSync(importPath);
62
86
  }
63
87
  catch {
64
88
  continue;
65
89
  }
66
- compile(importPath, importedName);
90
+ // resolvedName is intentionally null: compileFile derives the same
91
+ // name from its own IR pass, and the alias branch is unreachable
92
+ // when resolvedName matches — this avoids a second full parse of
93
+ // every imported file.
94
+ resolved.push(importPath);
95
+ compile(importPath, null);
67
96
  }
97
+ return resolved;
68
98
  }
69
99
  function stripExports(code) {
70
100
  return code
@@ -75,26 +105,88 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
75
105
  if (seen.has(filePath))
76
106
  return;
77
107
  seen.add(filePath);
78
- const src = readFileSync(filePath, 'utf-8');
79
- resolveVskImports(filePath, (p, n) => compileFile(p, n || '', output));
80
- const compCode = compileClient(src, null, { forceClient: true });
81
- if (compCode) {
82
- collectRuntimeImports(compCode);
83
- const stripped = stripExports(stripVskImports(stripRuntimeImport(compCode)));
84
- output.push(stripped.replace(/^\n+/, '').replace(/\n+$/, ''));
108
+ // Cache-hit fast path: replay the entry's recorded contribution with no
109
+ // file reads or parses. Deps recurse through the same path so each hit
110
+ // costs one stat call (zero with `only`).
111
+ const cachedEntry = cache?.files.get(filePath);
112
+ // If one of this file's .md dependencies was the edited target, its
113
+ // inlined content is stale by definition — force a recompile.
114
+ let dependsOnEdited = false;
115
+ if (cachedEntry && only && !only.has(filePath)) {
116
+ for (const p of only) {
117
+ if (cachedEntry.imports.includes(p)) {
118
+ dependsOnEdited = true;
119
+ break;
120
+ }
121
+ }
122
+ }
123
+ const cacheUsable = !!cachedEntry && !dependsOnEdited &&
124
+ (mustReuseWithoutStat(filePath) || fileUnchanged(filePath, cachedEntry));
125
+ const cached = cacheUsable ? cachedEntry : undefined;
126
+ if (cached && cacheUsable) {
127
+ cachedFileHits++;
128
+ for (const dep of cached.imports)
129
+ compileFile(dep, cache?.files.get(dep)?.actualName ?? '', output);
130
+ if (cached.compCode)
131
+ output.push(cached.compCode);
132
+ if (cached.hydCode)
133
+ output.push(cached.hydCode);
134
+ for (const n of cached.runtimeNames)
135
+ runtimeImportNames.add(n);
136
+ if (cached.actualName && resolvedName !== null && cached.actualName !== resolvedName) {
137
+ output.push(`Object.defineProperty(__components, ${JSON.stringify(resolvedName)}, { get: () => __components[${JSON.stringify(cached.actualName)}], configurable: true });`);
138
+ output.push(`Object.defineProperty(__hydrators, ${JSON.stringify(resolvedName)}, { get: () => __hydrators[${JSON.stringify(cached.actualName)}], configurable: true });`);
139
+ }
140
+ return;
141
+ }
142
+ compiledFiles++;
143
+ let src = readFileSync(filePath, 'utf-8');
144
+ if (/content=["'][^"']*\.md["']/i.test(src)) {
145
+ src = inlineMdContentAttrs(src, dirname(filePath), guessProjectRoots(appDir));
85
146
  }
86
- const hydCode = compileClient(src, null, { hydrate: true, forceClient: true, includeTopLevel: false });
87
- if (hydCode) {
88
- collectRuntimeImports(hydCode);
89
- const stripped = stripExports(stripVskImports(stripRuntimeImport(hydCode)))
90
- .replace(/__components/g, '__hydrators');
91
- output.push(stripped.replace(/^\n+/, '').replace(/\n+$/, ''));
147
+ const namesBefore = cache ? new Set(runtimeImportNames) : null;
148
+ const importedPaths = resolveVskImports(filePath, (p, n) => compileFile(p, n || '', output));
149
+ // One parse/IR pass feeds both client modes AND the component-name
150
+ // lookup the dev hot path pays the acorn+TS parse once per edit
151
+ // 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+$/, '') : '';
155
+ if (rawComp)
156
+ collectRuntimeImports(rawComp);
157
+ if (rawHyd)
158
+ collectRuntimeImports(rawHyd);
159
+ if (returnEdited && only.has(filePath)) {
160
+ // Mirrors the HMR fnSources preparation in the dev servers: drop every
161
+ // remaining top-level import so the snippet can be eval'd in the page
162
+ // context where runtime globals already exist.
163
+ const bare = rawComp
164
+ ? stripExports(stripVskImports(stripRuntimeImport(rawComp)))
165
+ .replace(/^import\s*[\s\S]*?from\s*['"][^'"]+['"];?\s*\n?/gm, '')
166
+ : '';
167
+ editedSources.set(filePath, bare);
168
+ editedNames.set(filePath, actualName);
92
169
  }
93
- const actualName = resolveComponentName(src);
94
- if (actualName && actualName !== resolvedName) {
170
+ if (compCode)
171
+ output.push(compCode);
172
+ if (hydCode)
173
+ output.push(hydCode);
174
+ if (actualName && resolvedName !== null && actualName !== resolvedName) {
95
175
  output.push(`Object.defineProperty(__components, ${JSON.stringify(resolvedName)}, { get: () => __components[${JSON.stringify(actualName)}], configurable: true });`);
96
176
  output.push(`Object.defineProperty(__hydrators, ${JSON.stringify(resolvedName)}, { get: () => __hydrators[${JSON.stringify(actualName)}], configurable: true });`);
97
177
  }
178
+ if (cache && namesBefore) {
179
+ const st = statSync(filePath);
180
+ cache.files.set(filePath, {
181
+ mtimeMs: st.mtimeMs,
182
+ size: st.size,
183
+ compCode,
184
+ hydCode,
185
+ actualName,
186
+ runtimeNames: [...runtimeImportNames].filter((n) => !namesBefore.has(n)),
187
+ imports: importedPaths,
188
+ });
189
+ }
98
190
  }
99
191
  function buildChunkName(node) {
100
192
  const dir = relative(appDir, node.sourceDir || '');
@@ -124,6 +216,14 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
124
216
  if (node.notFound && existsSync(notFoundPath)) {
125
217
  compileFile(notFoundPath, node.notFound, chunkCode);
126
218
  }
219
+ const offlinePath = resolve(appDir, node.sourceDir, 'offline.vsk');
220
+ if (node.offline && existsSync(offlinePath)) {
221
+ compileFile(offlinePath, node.offline, chunkCode);
222
+ }
223
+ const networkPath = resolve(appDir, node.sourceDir, 'network.vsk');
224
+ if (node.network && existsSync(networkPath)) {
225
+ compileFile(networkPath, node.network, chunkCode);
226
+ }
127
227
  const loadingPath = resolve(appDir, node.sourceDir, 'loading.vsk');
128
228
  if (node.loading && existsSync(loadingPath)) {
129
229
  compileFile(loadingPath, node.loading, chunkCode);
@@ -162,8 +262,18 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
162
262
  }
163
263
  }
164
264
  annotate(routeTree);
165
- const main = await buildMainBundle(routeTree, runtimeDir, true, {}, !!options?.hmr, !!options?.importRuntime, runtimeImportNames, options?.routeDataCache);
166
- return { main, chunks };
265
+ const mainKey = JSON.stringify(routeTree) + '|' + [...runtimeImportNames].sort().join(',') + `|${!!options?.hmr}|${!!options?.importRuntime}|${options?.routeDataCache ?? ''}`;
266
+ let main;
267
+ if (cache?.mainBundle && cache.mainBundle.key === mainKey) {
268
+ mainFromCache = true;
269
+ main = cache.mainBundle.code;
270
+ }
271
+ else {
272
+ main = await buildMainBundle(routeTree, runtimeDir, true, {}, !!options?.hmr, !!options?.importRuntime, runtimeImportNames, options?.routeDataCache);
273
+ if (cache)
274
+ cache.mainBundle = { key: mainKey, code: main };
275
+ }
276
+ return { main, chunks, cachedFileHits, compiledFiles, mainFromCache, editedSources, editedNames };
167
277
  }
168
278
  else {
169
279
  let componentLines = [];
@@ -209,6 +319,12 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
209
319
  const notFoundPath = resolve(appDir, node.sourceDir, 'not-found.vsk');
210
320
  if (node.notFound && existsSync(notFoundPath))
211
321
  compileFileMono(notFoundPath, node.notFound);
322
+ const offlinePath = resolve(appDir, node.sourceDir, 'offline.vsk');
323
+ if (node.offline && existsSync(offlinePath))
324
+ compileFileMono(offlinePath, node.offline);
325
+ const networkPath = resolve(appDir, node.sourceDir, 'network.vsk');
326
+ if (node.network && existsSync(networkPath))
327
+ compileFileMono(networkPath, node.network);
212
328
  const loadingPath = resolve(appDir, node.sourceDir, 'loading.vsk');
213
329
  if (node.loading && existsSync(loadingPath))
214
330
  compileFileMono(loadingPath, node.loading);
@@ -223,11 +339,12 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
223
339
  const main = await buildMainBundle(routeTree, runtimeDir, false, {
224
340
  componentLines, hydratorLines, aliasLines, hydratorAliasLines,
225
341
  }, !!options?.hmr, !!options?.importRuntime, runtimeImportNames, options?.routeDataCache);
342
+ // Mono (non-codeSplit) builds are the production path — no incremental cache.
226
343
  return { main, chunks: [] };
227
344
  }
228
345
  }
229
346
  function stripTypes(code) {
230
- return transformSync(code, { loader: 'ts' }).code;
347
+ return stripCodeTypes(code);
231
348
  }
232
349
  export function buildRuntimeCode(runtimeDir) {
233
350
  const runtimeFiles = [
@@ -324,9 +441,22 @@ export async function buildTreeShakenRuntime(runtimeDir, usedNames) {
324
441
  catch { /* ignore */ }
325
442
  }
326
443
  }
444
+ /**
445
+ * Appends the HMR eval hook to the dev client bundle.
446
+ *
447
+ * The gadget is gated by a per-server-session nonce: the dev server generates
448
+ * `globalThis.__vesk_hmr_nonce` at boot and broadcasts it inside every
449
+ * `update` message. The browser evaluates component sources only when the
450
+ * message nonce matches, so a cross-site WebSocket (or any other injection
451
+ * vector) cannot drive arbitrary eval without also reading the nonce from a
452
+ * same-origin page — which the WS origin check already blocks.
453
+ */
327
454
  function appendHmrGlobals(code) {
328
455
  return code +
329
- "globalThis.__vesk_hmr_eval = (code) => eval(code);\n";
456
+ "globalThis.__vesk_hmr_eval = (code, n) => {\n" +
457
+ " if (!n || n !== globalThis.__vesk_hmr_nonce) throw new Error('vesk hmr: invalid nonce');\n" +
458
+ " return eval(code);\n" +
459
+ "};\n";
330
460
  }
331
461
  async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, importRuntime, runtimeImportNames, routeDataCache) {
332
462
  const baseRuntimeImports = ['createFileRouter', 'get', 'set', 'effect', 'track', 'destroy_block', 'getActiveComponent', 'setActiveComponent', 'NavLink', 'Link', 'reactiveProps', 'matchRoute', 'ensureChunk'];
@@ -366,6 +496,8 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
366
496
  " if (n._layoutName && __components[n._layoutName]) n.layout = __components[n._layoutName];\n" +
367
497
  " if (n._errorName && __components[n._errorName]) n.error = __components[n._errorName];\n" +
368
498
  " if (n._notFoundName && __components[n._notFoundName]) n.notFound = __components[n._notFoundName];\n" +
499
+ " if (n._offlineName && __components[n._offlineName]) n.offline = __components[n._offlineName];\n" +
500
+ " if (n._networkName && __components[n._networkName]) n.network = __components[n._networkName];\n" +
369
501
  ' if (n.children) __updateComponents(n.children);\n' +
370
502
  ' }\n' +
371
503
  '}\n';
@@ -379,6 +511,8 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
379
511
  " if (typeof n.layout === 'string') n._layoutName = n.layout;\n" +
380
512
  " if (typeof n.error === 'string') n._errorName = n.error;\n" +
381
513
  " if (typeof n.notFound === 'string') n._notFoundName = n.notFound;\n" +
514
+ " if (typeof n.offline === 'string') n._offlineName = n.offline;\n" +
515
+ " if (typeof n.network === 'string') n._networkName = n.network;\n" +
382
516
  ' if (n.children) __resolveNames(n.children);\n' +
383
517
  ' }\n' +
384
518
  '}\n';
@@ -464,6 +598,10 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
464
598
  cleanupFn +
465
599
  placeFn +
466
600
  'globalThis.__components = __components;\n' +
601
+ 'globalThis.__runtime_comps = __runtime_comps;\n' +
602
+ 'globalThis.__cleanup = __cleanup;\n' +
603
+ 'globalThis.__place = __place;\n' +
604
+ allRuntimeImports.map(n => `globalThis.${n} = ${n};\n`).join('') +
467
605
  'function __resolveNames(nodes) {\n' +
468
606
  ' for (const n of nodes) {\n' +
469
607
  " if (typeof n.page === 'string') {\n" +
@@ -476,6 +614,8 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
476
614
  ' }\n' +
477
615
  " if (typeof n.error === 'string') n.error = __components[n.error];\n" +
478
616
  " if (typeof n.notFound === 'string') n.notFound = __components[n.notFound];\n" +
617
+ " if (typeof n.offline === 'string') n.offline = __components[n.offline];\n" +
618
+ " if (typeof n.network === 'string') n.network = __components[n.network];\n" +
479
619
  ' if (n.children) __resolveNames(n.children);\n' +
480
620
  ' }\n' +
481
621
  '}\n' +
@@ -1,4 +1,7 @@
1
1
  import { type Server } from 'node:http';
2
2
  import type { DevServerOptions } from '@vesk/adapter/src/types';
3
+ export declare function bodyTooLarge(maxBytes: number): Error & {
4
+ status: number;
5
+ };
3
6
  export declare function startDevServer(appDir: string, options?: DevServerOptions): Promise<Server | void>;
4
7
  //# sourceMappingURL=dev-server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AAMjG,OAAO,KAAK,EAAa,gBAAgB,EAAY,MAAM,yBAAyB,CAAC;AAuDrF,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiTvG"}
1
+ {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AAQjG,OAAO,KAAK,EAAa,gBAAgB,EAAY,MAAM,yBAAyB,CAAC;AAWrF,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AA6DD,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoTvG"}
@@ -2,18 +2,34 @@ import { readFileSync, existsSync, watch, statSync } from 'node:fs';
2
2
  import { resolve, extname, dirname } from 'node:path';
3
3
  import { createServer } from 'node:http';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { transformSync } from './esbuild-fallback.js';
5
+ import { stripCodeTypes } from '@vesk/compiler/src/strip-ts';
6
+ import { DEFAULT_MAX_BODY_BYTES } from '@vesk/compiler/src/server-codegen';
6
7
  import { build } from '@vesk/adapter/src/index';
7
8
  import { createHmrServer } from '@vesk/adapter/src/hmr';
8
9
  import { buildRuntimeCode } from '@vesk/adapter/src/client-bundle';
10
+ import { resolveWithin } from '@vesk/adapter/src/paths';
9
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
- async function readBody(req) {
12
+ export function bodyTooLarge(maxBytes) {
13
+ const err = new Error(`Request body exceeds limit (${maxBytes} bytes)`);
14
+ err.status = 413;
15
+ return err;
16
+ }
17
+ function errorStatus(e, fallback) {
18
+ const s = e?.status ?? e?.statusCode;
19
+ return typeof s === 'number' && s >= 400 && s < 600 ? s : fallback;
20
+ }
21
+ async function readBody(req, maxBytes = DEFAULT_MAX_BODY_BYTES) {
11
22
  const chunks = [];
12
- for await (const chunk of req)
23
+ let total = 0;
24
+ for await (const chunk of req) {
25
+ total += chunk.byteLength;
26
+ if (total > maxBytes)
27
+ throw bodyTooLarge(maxBytes);
13
28
  chunks.push(chunk);
29
+ }
14
30
  return Buffer.concat(chunks);
15
31
  }
16
- function makeWebRequest(nodeReq, url) {
32
+ function makeWebRequest(nodeReq, url, maxBodyBytes = DEFAULT_MAX_BODY_BYTES) {
17
33
  const parsedUrl = new URL(url, `http://${nodeReq.headers.host || 'localhost'}`);
18
34
  const method = nodeReq.method || 'GET';
19
35
  let _bodyBuffer = null;
@@ -21,8 +37,13 @@ function makeWebRequest(nodeReq, url) {
21
37
  if (_bodyBuffer)
22
38
  return _bodyBuffer;
23
39
  const chunks = [];
24
- for await (const chunk of nodeReq)
40
+ let total = 0;
41
+ for await (const chunk of nodeReq) {
42
+ total += chunk.byteLength;
43
+ if (total > maxBodyBytes)
44
+ throw bodyTooLarge(maxBodyBytes);
25
45
  chunks.push(Buffer.from(chunk));
46
+ }
26
47
  _bodyBuffer = Buffer.concat(chunks);
27
48
  return _bodyBuffer;
28
49
  }
@@ -59,6 +80,10 @@ const MIME = {
59
80
  };
60
81
  export async function startDevServer(appDir, options) {
61
82
  const port = options?.port || 3000;
83
+ const host = options?.host || '127.0.0.1';
84
+ const maxBodyBytes = options?.maxBodyBytes || DEFAULT_MAX_BODY_BYTES;
85
+ if (!process.env.NODE_ENV)
86
+ process.env.NODE_ENV = 'development';
62
87
  const devDir = resolve(appDir, '..', '.vesk', 'dev');
63
88
  const publicDir = options?.publicDir || resolve(appDir, '..', 'public');
64
89
  let componentMap = new Map();
@@ -110,7 +135,7 @@ export async function startDevServer(appDir, options) {
110
135
  if (hmrPath) {
111
136
  let hmrContent = readFileSync(hmrPath, 'utf-8');
112
137
  if (hmrPath.endsWith('.ts')) {
113
- hmrContent = transformSync(hmrContent, { loader: 'ts' }).code;
138
+ hmrContent = stripCodeTypes(hmrContent);
114
139
  }
115
140
  res.writeHead(200, { 'Content-Type': 'application/javascript' });
116
141
  res.end(hmrContent);
@@ -123,9 +148,9 @@ export async function startDevServer(appDir, options) {
123
148
  return;
124
149
  }
125
150
  if (url.pathname.startsWith('/_vesk/static/')) {
126
- const relPath = url.pathname.replace('/_vesk/static/', '').replace(/\.\./g, '');
127
- const staticPath = resolve(devDir, 'static', relPath);
128
- if (!staticPath.startsWith(resolve(devDir, 'static'))) {
151
+ const relPath = url.pathname.slice('/_vesk/static/'.length);
152
+ const staticPath = resolveWithin(resolve(devDir, 'static'), relPath);
153
+ if (!staticPath) {
129
154
  res.writeHead(403);
130
155
  res.end('Forbidden');
131
156
  return;
@@ -138,17 +163,16 @@ export async function startDevServer(appDir, options) {
138
163
  }
139
164
  }
140
165
  if (url.pathname !== '/') {
141
- const sanitized = url.pathname.replace(/\.\./g, '');
142
- const sourcePath = resolve(publicDir, sanitized.slice(1));
143
- if (sourcePath.startsWith(publicDir) && existsSync(sourcePath) && statSync(sourcePath).isFile()) {
166
+ const sourcePath = url.pathname.length > 1 ? resolveWithin(publicDir, url.pathname.slice(1)) : null;
167
+ if (sourcePath && existsSync(sourcePath) && statSync(sourcePath).isFile()) {
144
168
  const ext = extname(sourcePath);
145
169
  res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' });
146
170
  res.end(readFileSync(sourcePath));
147
171
  return;
148
172
  }
149
173
  const buildPublicDir = resolve(devDir, 'static', 'public');
150
- const buildPath = resolve(buildPublicDir, sanitized.slice(1));
151
- if (buildPath.startsWith(buildPublicDir) && existsSync(buildPath) && statSync(buildPath).isFile()) {
174
+ const buildPath = url.pathname.length > 1 ? resolveWithin(buildPublicDir, url.pathname.slice(1)) : null;
175
+ if (buildPath && existsSync(buildPath) && statSync(buildPath).isFile()) {
152
176
  const ext = extname(buildPath);
153
177
  res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' });
154
178
  res.end(readFileSync(buildPath));
@@ -164,7 +188,7 @@ export async function startDevServer(appDir, options) {
164
188
  try {
165
189
  const mod = await import(`${handlerPath}?t=${ssrVersion}`);
166
190
  if (mod.handleAction) {
167
- const webRequest = makeWebRequest(req, url.href);
191
+ const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
168
192
  const response = await mod.handleAction(webRequest, actionId);
169
193
  const body = await response.text();
170
194
  const headers = Object.fromEntries(response.headers);
@@ -179,7 +203,8 @@ export async function startDevServer(appDir, options) {
179
203
  }
180
204
  }
181
205
  catch (e) {
182
- res.writeHead(500, { 'Content-Type': 'application/json' });
206
+ const status = errorStatus(e, 500);
207
+ res.writeHead(status, { 'Content-Type': 'application/json' });
183
208
  res.end(JSON.stringify({ ok: false, error: e instanceof Error ? e.message : String(e) }));
184
209
  return;
185
210
  }
@@ -196,7 +221,7 @@ export async function startDevServer(appDir, options) {
196
221
  if (existsSync(handlerPath)) {
197
222
  try {
198
223
  const mod = await import(`${handlerPath}?t=${ssrVersion}`);
199
- const webRequest = makeWebRequest(req, url.href);
224
+ const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
200
225
  const response = await mod.handle(webRequest);
201
226
  const body = await response.text();
202
227
  res.writeHead(response.status, Object.fromEntries(response.headers));
@@ -218,7 +243,7 @@ export async function startDevServer(appDir, options) {
218
243
  if (existsSync(handlerPath)) {
219
244
  try {
220
245
  const mod = await import(`${handlerPath}?t=${ssrVersion}`);
221
- const webRequest = makeWebRequest(req, url.href);
246
+ const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
222
247
  const response = await mod.handle(webRequest);
223
248
  const body = await response.text();
224
249
  const headers = Object.fromEntries(response.headers);
@@ -245,7 +270,7 @@ export async function startDevServer(appDir, options) {
245
270
  if (existsSync(handlerPath)) {
246
271
  try {
247
272
  const mod = await import(`${handlerPath}?t=${ssrVersion}`);
248
- const webRequest = makeWebRequest(req, url.href);
273
+ const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
249
274
  const response = await mod.handle(webRequest);
250
275
  const body = await response.text();
251
276
  const headers = Object.fromEntries(response.headers);
@@ -320,15 +345,15 @@ export async function startDevServer(appDir, options) {
320
345
  else if (files.length > 0) {
321
346
  hmr.handleFileChange(files[0], doBuild, routeTree);
322
347
  }
323
- }, 200);
348
+ }, 40);
324
349
  });
325
350
  }
326
351
  catch (e) {
327
352
  console.error('vesk dev: file watching unavailable');
328
353
  }
329
354
  await new Promise(resolve => {
330
- server.listen(port, () => {
331
- console.error(`vesk dev server at http://localhost:${port}`);
355
+ server.listen(port, host, () => {
356
+ console.error(`vesk dev server at http://localhost:${port} (listening on ${host})`);
332
357
  resolve();
333
358
  });
334
359
  });
@@ -1,3 +1,2 @@
1
1
  export declare function build(options: any): Promise<any>;
2
- export declare function transformSync(code: string, options?: any): any;
3
2
  //# sourceMappingURL=esbuild-fallback.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"esbuild-fallback.d.ts","sourceRoot":"","sources":["../src/esbuild-fallback.ts"],"names":[],"mappings":"AA8BA,wBAAsB,KAAK,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAgBtD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,GAAG,CAa9D"}
1
+ {"version":3,"file":"esbuild-fallback.d.ts","sourceRoot":"","sources":["../src/esbuild-fallback.ts"],"names":[],"mappings":"AAkCA,wBAAsB,KAAK,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAiBtD"}
@@ -1,17 +1,21 @@
1
+ // esbuild access with a WASM fallback for machines where the native binary
2
+ // cannot run (older CPUs raise SIGILL, unsupported platforms skip install).
3
+ // Only `build()` is routed here: every synchronous TS-strip call site now uses
4
+ // the compiler's dependency-free `stripCodeTypes` (acorn-based), so no
5
+ // transformSync path exists anymore.
1
6
  let _nativeBuild = null;
2
- let _nativeTransform = null;
7
+ let _nativeDead = false;
3
8
  let _wasm = null;
4
9
  let _wasmReady = null;
5
10
  async function loadNative() {
6
- if (_nativeBuild && _nativeTransform)
11
+ if (_nativeBuild || _nativeDead)
7
12
  return;
8
13
  try {
9
14
  const m = await import('esbuild');
10
15
  _nativeBuild = m.build.bind(m);
11
- _nativeTransform = m.transformSync.bind(m);
12
16
  }
13
17
  catch {
14
- // native esbuild not installed
18
+ // native esbuild not installed — wasm handles it
15
19
  }
16
20
  }
17
21
  async function getWasm() {
@@ -23,7 +27,7 @@ async function getWasm() {
23
27
  return m;
24
28
  }).catch(() => {
25
29
  _wasmReady = null;
26
- throw new Error('esbuild-wasm not available');
30
+ throw new Error('esbuild-wasm not available — install it with: npm install esbuild-wasm');
27
31
  });
28
32
  }
29
33
  return _wasmReady;
@@ -38,6 +42,7 @@ export async function build(options) {
38
42
  const msg = e instanceof Error ? e.message : String(e);
39
43
  if (msg.includes('SIGILL') || msg.includes('illegal hardware instruction') || msg.includes('cannot execute binary file')) {
40
44
  console.warn('vesk: native esbuild failed, falling back to esbuild-wasm:', msg);
45
+ _nativeDead = true;
41
46
  }
42
47
  else {
43
48
  throw e;
@@ -47,18 +52,3 @@ export async function build(options) {
47
52
  const wasm = await getWasm();
48
53
  return wasm.build(options);
49
54
  }
50
- export function transformSync(code, options) {
51
- if (_nativeTransform) {
52
- try {
53
- return _nativeTransform(code, options);
54
- }
55
- catch (e) {
56
- const msg = e instanceof Error ? e.message : String(e);
57
- if (msg.includes('SIGILL') || msg.includes('illegal hardware instruction') || msg.includes('cannot execute binary file')) {
58
- throw new Error('esbuild-wasm fallback for transformSync not yet implemented — use native esbuild or convert to async transform');
59
- }
60
- throw e;
61
- }
62
- }
63
- throw new Error('esbuild not installed — run `npm install esbuild` or use `vesk build` which falls back to esbuild-wasm for bundling');
64
- }
package/dist/hmr.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../src/hmr.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAOxC,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,yBAAyB,CAAC;AA6RzE,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC;IAAE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAAC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAkH/L"}
1
+ {"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../src/hmr.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAQxC,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,yBAAyB,CAAC;AA6RzE,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC;IAAE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAAC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAmI/L"}