@vesk/adapter 0.2.45 → 0.2.46

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.
@@ -117,13 +117,6 @@ export declare function stripAndDemoteInOnePass(code: string): string;
117
117
  * second full acorn parse on every file save.
118
118
  */
119
119
  export declare function buildHmrEvalSnippet(code: string): string;
120
- /**
121
- * Removes matched top-level statements from a compiled client module using the
122
- * parser's exact `start`/`end` offsets — never a text scan, so a doc sample
123
- * stored inside a template literal can never be mistaken for a real statement.
124
- * If the module cannot be parsed it is returned untouched (the compiler's own
125
- * output is always valid ESM, so that branch is unreachable in practice).
126
- */
127
120
  export declare function removeCompiledNodes(code: string, isTarget: (node: CompiledNode) => boolean): string;
128
121
  export declare function generateClientBundle(routeTree: RouteNode[], appDir: string, componentMap?: Map<string, string>, options?: ClientBundleOptions): Promise<ClientBundleResult>;
129
122
  export declare function buildRuntimeCode(runtimeDir: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"client-bundle.d.ts","sourceRoot":"","sources":["../src/client-bundle.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAmF,MAAM,yBAAyB,CAAC;AAoCnL;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA8BhE;AAED;;;;;GAKG;AACH,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7B,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvB,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,IAAI,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE;gBAAE,IAAI,CAAC,EAAE,MAAM,CAAC;gBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;IACtH,WAAW,CAAC,EAAE;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC1C,YAAY,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,CAAC,EAAE;gBAAE,IAAI,CAAC,EAAE,MAAM,CAAC;gBAAC,MAAM,CAAC,EAAE;oBAAE,IAAI,CAAC,EAAE,MAAM,CAAC;oBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;iBAAE,CAAA;aAAE,CAAA;SAAE,CAAC,CAAC;KAC/F,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAmDD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAKzD;AAkCD;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA+B7D;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAyBlD;AA4MD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAmC5D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYxD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,GAAG,MAAM,CAwBnG;AAED,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,CA+b7B;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;AAoCD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAI/F"}
1
+ {"version":3,"file":"client-bundle.d.ts","sourceRoot":"","sources":["../src/client-bundle.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAmF,MAAM,yBAAyB,CAAC;AAoCnL;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA4BhE;AAED;;;;;GAKG;AACH,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7B,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvB,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,IAAI,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE;gBAAE,IAAI,CAAC,EAAE,MAAM,CAAC;gBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;IACtH,WAAW,CAAC,EAAE;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC1C,YAAY,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,CAAC,EAAE;gBAAE,IAAI,CAAC,EAAE,MAAM,CAAC;gBAAC,MAAM,CAAC,EAAE;oBAAE,IAAI,CAAC,EAAE,MAAM,CAAC;oBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;iBAAE,CAAA;aAAE,CAAA;SAAE,CAAC,CAAC;KAC/F,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAmDD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAKzD;AAkCD;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA+B7D;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAyBlD;AA4MD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA+B5D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASxD;AA4CD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,GAAG,MAAM,CAoBnG;AAED,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,CAoc7B;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;AAoCD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAI/F"}
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { build } from './esbuild-fallback.js';
6
6
  import { stripCodeTypes } from '@vesk/compiler/src/strip-ts';
7
- import { parse } from '@vesk/compiler/src/parser';
7
+ import { parse, parseGeneratedJs } from '@vesk/compiler/src/parser';
8
8
  import { compileClient, compileClientBoth, nameAllocFor, vskComponentNames } from '@vesk/compiler/src/client-codegen';
9
9
  import { VskComponentOwners } from '@vesk/compiler/src/vsk-collision';
10
10
  import { resolveComponentName } from '@vesk/compiler/src/server-codegen';
@@ -59,13 +59,11 @@ function findRuntimeSrc(appDir) {
59
59
  */
60
60
  export function extractRuntimeImportNames(code) {
61
61
  const names = [];
62
- let ast;
63
- try {
64
- ast = parse(code);
65
- }
66
- catch {
62
+ // Shares the parse with the strip passes (same generated string, read-only
63
+ // AST) — see parseForStrip.
64
+ const ast = parseForStrip(code);
65
+ if (!ast)
67
66
  return names;
68
- }
69
67
  const body = ast.body ?? [];
70
68
  for (const raw of body) {
71
69
  const node = raw;
@@ -482,13 +480,9 @@ function scopeFileContribution(code) {
482
480
  * hot path used to run on every edit.
483
481
  */
484
482
  export function stripAndDemoteInOnePass(code) {
485
- let ast;
486
- try {
487
- ast = parse(code);
488
- }
489
- catch {
483
+ const ast = parseForStrip(code);
484
+ if (!ast)
490
485
  return code;
491
- }
492
486
  const body = ast.body ?? [];
493
487
  const edits = [];
494
488
  for (const raw of body) {
@@ -548,14 +542,10 @@ export function buildHmrEvalSnippet(code) {
548
542
  return '';
549
543
  const scoped = `{\n${body}\n}`;
550
544
  // Never trade a potential duplicate-binding error for a certain syntax
551
- // error: if the scoped form does not parse, keep the unscoped body.
552
- try {
553
- parse(scoped);
554
- return scoped;
555
- }
556
- catch {
557
- return body;
558
- }
545
+ // error: if the scoped form does not parse, keep the unscoped body. The body
546
+ // is generated JS, so plain acorn is the right tool (parseGeneratedJs) and
547
+ // it must not go through the cache: this string is unique per call.
548
+ return parseGeneratedJs(scoped) !== null ? scoped : body;
559
549
  }
560
550
  /**
561
551
  * Removes matched top-level statements from a compiled client module using the
@@ -564,14 +554,46 @@ export function buildHmrEvalSnippet(code) {
564
554
  * If the module cannot be parsed it is returned untouched (the compiler's own
565
555
  * output is always valid ESM, so that branch is unreachable in practice).
566
556
  */
567
- export function removeCompiledNodes(code, isTarget) {
568
- let ast;
569
- try {
570
- ast = parse(code);
557
+ /**
558
+ * Parsed-AST cache for the strip passes over GENERATED code.
559
+ *
560
+ * Every compiled file goes through several `removeCompiledNodes` passes with
561
+ * different predicates (`isVskImport`, `isRuntimeImport`, `isComponentExport`,
562
+ * ...) and each one used to re-parse the same string with the full `.vsk`
563
+ * pipeline. On a CPU profile of a 28-route build, parsing was ~70% of the
564
+ * whole build and the generated-code strips were the bulk of it. The ASTs are
565
+ * only read here (node.start/end plus the caller's predicate), so one parse can
566
+ * serve every pass over the same string.
567
+ *
568
+ * Bounded: cleared wholesale past the cap, because a watch session keeps
569
+ * calling this with new strings forever.
570
+ */
571
+ const stripAstCache = new Map();
572
+ const STRIP_AST_CACHE_MAX = 512;
573
+ function parseForStrip(code) {
574
+ const hit = stripAstCache.get(code);
575
+ if (hit !== undefined)
576
+ return hit;
577
+ // Plain acorn for generated JS (see parseGeneratedJs); the `.vsk` pipeline
578
+ // only for the rare module it cannot read.
579
+ let ast = parseGeneratedJs(code);
580
+ if (!ast) {
581
+ try {
582
+ ast = parse(code);
583
+ }
584
+ catch {
585
+ ast = null;
586
+ }
571
587
  }
572
- catch {
588
+ if (stripAstCache.size >= STRIP_AST_CACHE_MAX)
589
+ stripAstCache.clear();
590
+ stripAstCache.set(code, ast);
591
+ return ast;
592
+ }
593
+ export function removeCompiledNodes(code, isTarget) {
594
+ const ast = parseForStrip(code);
595
+ if (!ast)
573
596
  return code;
574
- }
575
597
  const ranges = [];
576
598
  for (const raw of ast.body ?? []) {
577
599
  const node = raw;
@@ -597,6 +619,7 @@ export function removeCompiledNodes(code, isTarget) {
597
619
  export async function generateClientBundle(routeTree, appDir, componentMap, options) {
598
620
  const runtimeDir = findRuntimeSrc(appDir);
599
621
  let seen = new Set();
622
+ const __stats = { chunks: 0, compiled: 0, distinct: new Set(), parseMs: 0 };
600
623
  const chunks = [];
601
624
  const runtimeImportNames = new Set();
602
625
  const cache = options?.cache;
@@ -720,6 +743,8 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
720
743
  return;
721
744
  }
722
745
  compiledFiles++;
746
+ __stats.compiled++;
747
+ __stats.distinct.add(filePath);
723
748
  let src = readFileSync(filePath, 'utf-8');
724
749
  if (/content=["'][^"']*\.md["']/i.test(src)) {
725
750
  src = inlineMdContentAttrs(src, dirname(filePath), guessProjectRoots(appDir));
@@ -729,7 +754,9 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
729
754
  // One parse/IR pass feeds both client modes AND the component-name
730
755
  // lookup — the dev hot path pays the acorn+TS parse once per edit
731
756
  // instead of three times.
757
+ const __p0 = performance.now();
732
758
  const { comp: rawComp, hyd: rawHyd, name: actualName, aliases, reexportPaths, componentNames } = compileClientBoth(src, null, filePath);
759
+ __stats.parseMs += performance.now() - __p0;
733
760
  for (const cn of componentNames || [])
734
761
  componentOwners.claim(cn, filePath);
735
762
  // Cache keeps the import-carrying codes so a warm build can re-fold them
@@ -978,6 +1005,8 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
978
1005
  transformedChunks.push({ ...chunk, code: await applyTransformPlugins(chunk.code, chunk.name) });
979
1006
  }
980
1007
  componentOwners.report('client bundle');
1008
+ if (process.env.VESK_BUILD_PROFILE)
1009
+ console.error(` [client] chunks=${chunks.length} compiled=${__stats.compiled} distinct=${__stats.distinct.size} parseMs=${__stats.parseMs.toFixed(0)}`);
981
1010
  return { main: await applyTransformPlugins(main, 'client.js'), chunks: transformedChunks, cachedFileHits, compiledFiles, mainFromCache, editedSources, editedNames };
982
1011
  }
983
1012
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"image-pipeline.d.ts","sourceRoot":"","sources":["../src/image-pipeline.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsGrE,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAgD3F"}
1
+ {"version":3,"file":"image-pipeline.d.ts","sourceRoot":"","sources":["../src/image-pipeline.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAyKrE,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAwD3F"}
@@ -1,29 +1,100 @@
1
1
  import { mkdirSync, writeFileSync, readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
2
2
  import { spawnSync } from 'node:child_process';
3
- import { resolve, extname, dirname } from 'node:path';
3
+ import { resolve, extname, dirname, join } from 'node:path';
4
+ import { createRequire } from 'node:module';
5
+ import { tmpdir } from 'node:os';
4
6
  const SUPPORTED = new Set(['.jpg', '.jpeg', '.png', '.webp', '.avif', '.tiff']);
5
7
  const OUTPUT_WIDTHS = [640, 768, 1024, 1280, 1536];
6
8
  const FORMATS = ['webp', 'avif'];
7
9
  let sharpFn = null;
8
- try {
9
- // Probe in a child before importing sharp in this process. On older CPUs,
10
- // importing the native module itself can terminate Node with SIGILL; an
11
- // in-process try/catch cannot catch that signal.
12
- const probe = spawnSync(process.execPath, [
13
- '--input-type=module',
14
- '-e',
15
- "import sharp from 'sharp'; await sharp({ create: { width: 2, height: 2, channels: 3, background: 'red' } }).png().toBuffer();",
16
- ], { stdio: 'ignore' });
17
- if (probe.status === 0) {
18
- const mod = await import('sharp');
19
- sharpFn = mod.default;
10
+ let sharpProbed = false;
11
+ /**
12
+ * Remember a failed probe on disk.
13
+ *
14
+ * The probe's answer is a property of the MACHINE (CPU instruction set, the
15
+ * installed sharp binary) and not of the project, but it costs a child process
16
+ * that dies with SIGILL — and the kernel writes a core dump on the way out, so
17
+ * the "no" case is the expensive one: measured at 25-100s per build on a
18
+ * loaded 2-core box. Without this, every build of an app that has images pays
19
+ * it again. The key folds in the resolved sharp entry's mtime, so installing or
20
+ * upgrading sharp invalidates the memo and the next build probes for real.
21
+ */
22
+ function probeMemoPath() {
23
+ try {
24
+ const req = createRequire(import.meta.url);
25
+ // `sharp/package.json` is NOT an exported subpath (sharp's exports map
26
+ // rejects it with ERR_PACKAGE_PATH_NOT_EXPORTED), so key on the resolved
27
+ // ENTRY file: its mtime changes on install/upgrade, which is exactly when
28
+ // the answer has to be re-measured.
29
+ const entry = req.resolve('sharp');
30
+ const stamp = Math.round(statSync(entry).mtimeMs);
31
+ return join(tmpdir(), `vesk-sharp-probe-${process.platform}-${process.arch}-${stamp}.json`);
20
32
  }
21
- else {
22
- console.warn('vesk images: sharp native probe failed; using copy-only image pipeline');
33
+ catch {
34
+ // sharp not installed: the answer is "unavailable" and cannot change
35
+ // without an install.
36
+ return null;
37
+ }
38
+ }
39
+ function readProbeMemo() {
40
+ const path = probeMemoPath();
41
+ if (path === null)
42
+ return null;
43
+ try {
44
+ return JSON.parse(readFileSync(path, 'utf-8'))?.usable === true;
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ }
50
+ function writeProbeMemo(usable) {
51
+ const path = probeMemoPath();
52
+ if (path === null)
53
+ return;
54
+ try {
55
+ writeFileSync(path, JSON.stringify({ usable, at: Date.now() }), 'utf-8');
23
56
  }
57
+ catch { /* a read-only tmpdir just means we probe again next build */ }
24
58
  }
25
- catch {
26
- // sharp not available — fall through to copy-only
59
+ /**
60
+ * Load sharp, or decide we cannot. LAZY on purpose: the probe spawns a child
61
+ * Node that imports the native module, and on a CPU without the instructions
62
+ * sharp's binary needs that child dies with SIGILL after tens of seconds. Doing
63
+ * it at module scope charged every build — including builds of apps with no
64
+ * `<Image>` at all — for a capability the build may never use. Callers reach
65
+ * this only after finding image refs, and the answer is memoized per machine.
66
+ */
67
+ async function ensureSharp() {
68
+ if (sharpProbed)
69
+ return;
70
+ sharpProbed = true;
71
+ const memo = readProbeMemo();
72
+ if (memo === false) {
73
+ console.warn('vesk images: sharp unusable on this machine (cached); using copy-only image pipeline');
74
+ return;
75
+ }
76
+ try {
77
+ // Probe in a child before importing sharp in this process. On older CPUs,
78
+ // importing the native module itself can terminate Node with SIGILL; an
79
+ // in-process try/catch cannot catch that signal.
80
+ const probe = spawnSync(process.execPath, [
81
+ '--input-type=module',
82
+ '-e',
83
+ "import sharp from 'sharp'; await sharp({ create: { width: 2, height: 2, channels: 3, background: 'red' } }).png().toBuffer();",
84
+ ], { stdio: 'ignore' });
85
+ if (probe.status === 0) {
86
+ const mod = await import('sharp');
87
+ sharpFn = mod.default;
88
+ writeProbeMemo(true);
89
+ }
90
+ else {
91
+ console.warn('vesk images: sharp native probe failed; using copy-only image pipeline');
92
+ writeProbeMemo(false);
93
+ }
94
+ }
95
+ catch {
96
+ // sharp not available — fall through to copy-only
97
+ }
27
98
  }
28
99
  async function processImage(srcPath, outDir, baseName) {
29
100
  const image = sharpFn ? sharpFn(srcPath) : null;
@@ -95,13 +166,20 @@ export async function optimizeImages(appDir, outDir) {
95
166
  console.error('vesk images: no <Image> refs found');
96
167
  return [];
97
168
  }
169
+ await ensureSharp();
98
170
  const results = [];
99
171
  for (const ref of refs) {
172
+ // `src` is site-root-relative in every documented form (`<Image
173
+ // src="/hero.png" />`), and `path.resolve` treats a leading-slash argument
174
+ // as an ABSOLUTE path — so `resolve(appDir, '/hero.png')` asks the filesystem
175
+ // for `/hero.png` and every image reference in the app is reported missing.
176
+ // Strip the leading slash for every candidate base.
177
+ const rel = ref.src.replace(/^\//, '');
100
178
  const possiblePaths = [
101
- resolve(appDir, ref.src),
102
- resolve(appDir, '..', 'public', ref.src.replace(/^\//, '')),
103
- resolve(appDir, '..', 'src', ref.src.replace(/^\//, '')),
104
- resolve(outDir, 'static', 'public', ref.src.replace(/^\//, '')),
179
+ resolve(appDir, rel),
180
+ resolve(appDir, '..', 'public', rel),
181
+ resolve(appDir, '..', 'src', rel),
182
+ resolve(outDir, 'static', 'public', rel),
105
183
  ];
106
184
  let srcPath = null;
107
185
  for (const p of possiblePaths) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACe,YAAY,EAAE,WAAW,EAC7B,UAAU,EAChC,MAAM,yBAAyB,CAAC;AAiBjC;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CACjB;AACD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,UAAU,EAAE,EACrB,OAAO,EAAE,iBAAiB,EAAE,GAAG,IAAI,GAAG,SAAS,GAC9C,UAAU,EAAE,CAQd;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,UAAU,EAAE,EACrB,KAAK,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,GACxC,UAAU,EAAE,CAGd;AA+BD,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CA8TpG;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAIhE,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,yBAAyB,EACzB,eAAe,GAChB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,aAAa,GACd,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACe,YAAY,EAAE,WAAW,EAC7B,UAAU,EAChC,MAAM,yBAAyB,CAAC;AAiBjC;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CACjB;AACD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,UAAU,EAAE,EACrB,OAAO,EAAE,iBAAiB,EAAE,GAAG,IAAI,GAAG,SAAS,GAC9C,UAAU,EAAE,CAQd;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,UAAU,EAAE,EACrB,KAAK,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,GACxC,UAAU,EAAE,CAGd;AAkDD,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CA6UpG;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAIhE,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,yBAAyB,EACzB,eAAe,GAChB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,aAAa,GACd,MAAM,2BAA2B,CAAC"}
package/dist/index.js CHANGED
@@ -84,7 +84,32 @@ async function collectPluginHeadExtra(pluginsPipelines) {
84
84
  }
85
85
  return parts.join('\n');
86
86
  }
87
+ /**
88
+ * `VESK_BUILD_PROFILE=1 vesk build` prints a per-phase millisecond breakdown.
89
+ *
90
+ * Builds are parse-bound and used to be hard to reason about: a 28-route app
91
+ * spent its time in a sharp capability probe, in per-route recompiles of the
92
+ * same shared files, and in re-parsing generated JS through the `.vsk` parser,
93
+ * and none of that is visible in the phase log lines. This makes it visible.
94
+ */
95
+ const profileEnabled = () => process.env.VESK_BUILD_PROFILE === '1';
96
+ let phaseMarks = [];
97
+ let phaseStarted = 0;
98
+ let buildStarted = 0;
99
+ function markPhase(name) {
100
+ if (!profileEnabled())
101
+ return;
102
+ const now = performance.now();
103
+ if (phaseStarted > 0)
104
+ phaseMarks.push([name, now - phaseStarted]);
105
+ phaseStarted = now;
106
+ }
87
107
  export async function build(appDir, options) {
108
+ if (profileEnabled()) {
109
+ buildStarted = Date.now();
110
+ phaseMarks = [];
111
+ phaseStarted = performance.now();
112
+ }
88
113
  appDir = resolve(appDir);
89
114
  const outDir = resolve(options?.outDir || resolve(appDir, '..', '.vesk'));
90
115
  const publicDir = options?.publicDir || resolve(appDir, '..', 'public');
@@ -150,6 +175,7 @@ export async function build(appDir, options) {
150
175
  ];
151
176
  for (const d of dirs)
152
177
  mkdirSync(d, { recursive: true });
178
+ markPhase('config+dirs');
153
179
  const cssSourcePath = resolveUserCssPath(appDir);
154
180
  if (cssSourcePath !== null) {
155
181
  const cssContent = readFileSync(cssSourcePath, 'utf-8');
@@ -174,6 +200,7 @@ export async function build(appDir, options) {
174
200
  console.error(`vesk build: css → static/global.css (${finalCss.length} bytes, tailwind plugin inactive)`);
175
201
  }
176
202
  }
203
+ markPhase('css/tailwind');
177
204
  const { scanRoutes, scanComponents } = await resolveCompilerApi('router.js');
178
205
  const { scanApiRoutes } = await resolveCompilerApi('api-routes.js');
179
206
  const { collectMiddlewareChain } = await resolveCompilerApi('middleware.js');
@@ -191,6 +218,7 @@ export async function build(appDir, options) {
191
218
  const apiDir = resolve(appDir, 'api');
192
219
  const apiTree = existsSync(apiDir) ? scanApiRoutes(apiDir) : [];
193
220
  console.error(`vesk build: ${routeTree.length} root routes, ${apiTree.length} API routes`);
221
+ markPhase('scan routes');
194
222
  console.error('vesk build: bundling server runtime...');
195
223
  await bundleRuntime(appDir, outDir);
196
224
  const ssrRoutes = [];
@@ -247,6 +275,7 @@ export async function build(appDir, options) {
247
275
  }
248
276
  }
249
277
  walk(routeTree);
278
+ markPhase('server runtime + ssr functions');
250
279
  const apiRoutes = [];
251
280
  function walkApi(nodes) {
252
281
  for (const node of nodes) {
@@ -273,6 +302,7 @@ export async function build(appDir, options) {
273
302
  const hasEvents = await compileEvents(appDir, outDir);
274
303
  if (hasEvents)
275
304
  console.error('vesk build: evt → server/events.js (onStart/onRequest/onStop)');
305
+ markPhase('api functions');
276
306
  console.error('vesk build: bundling client runtime...');
277
307
  const bundleOpts = {};
278
308
  if (options?.codeSplit)
@@ -295,6 +325,7 @@ export async function build(appDir, options) {
295
325
  }
296
326
  }
297
327
  copyStaticAssets(publicDir, outDir);
328
+ markPhase('client runtime + chunks');
298
329
  console.error('vesk build: static → static/public/');
299
330
  let prerenderedRoutes = [];
300
331
  if (options?.ssg) {
@@ -316,10 +347,6 @@ export async function build(appDir, options) {
316
347
  throw new Error(`SEO audit failed with ${audit.errors} error(s) — fix them before deploying`);
317
348
  }
318
349
  }
319
- {
320
- const { optimizeImages } = await import('./image-pipeline.js');
321
- await optimizeImages(appDir, outDir);
322
- }
323
350
  {
324
351
  const { generateSitemap, generateRobotsTxt } = await import('./static.js');
325
352
  const publicDirResolved = resolve(outDir, 'static', 'public');
@@ -337,6 +364,7 @@ export async function build(appDir, options) {
337
364
  console.error(`vesk build: seo → static/public/robots.txt (${robots.length} bytes)`);
338
365
  }
339
366
  }
367
+ markPhase('ssg+images+seo');
340
368
  const manifest = generateManifest(routeTree, ssrRoutes, apiRoutes, prerenderedRoutes, middlewareEnabled, actionMap, pluginHeadExtra, hasEvents);
341
369
  writeFileSync(resolve(outDir, 'config.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
342
370
  console.error('vesk build: config → config.json');
@@ -369,6 +397,14 @@ export async function build(appDir, options) {
369
397
  await plugin.onBuildEnd();
370
398
  }
371
399
  }
400
+ markPhase('manifest+platform');
401
+ markPhase('tail');
402
+ if (profileEnabled()) {
403
+ for (const [name, ms] of phaseMarks) {
404
+ console.error(` phase ${name.padEnd(30)} ${ms.toFixed(0).padStart(6)}ms`);
405
+ }
406
+ console.error(` TOTAL ${(Date.now() - buildStarted).toFixed(0)}ms`);
407
+ }
372
408
  console.error(`\nvesk build: done (${outDir})`);
373
409
  return { routeTree, apiTree, ssrRoutes, apiRoutes, manifest };
374
410
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/adapter",
3
- "version": "0.2.45",
3
+ "version": "0.2.46",
4
4
  "type": "module",
5
5
  "description": "Vesk adapter — builds deployable .vesk/ output for Deno and Node serverless",
6
6
  "main": "./dist/index.js",
@@ -23,9 +23,9 @@
23
23
  "sharp": "^0.35.3"
24
24
  },
25
25
  "dependencies": {
26
- "@vesk/compiler": "^0.2.45",
27
- "@vesk/runtime": "^0.2.45",
28
- "@vesk/types": "^0.2.45"
26
+ "@vesk/compiler": "^0.2.46",
27
+ "@vesk/runtime": "^0.2.46",
28
+ "@vesk/types": "^0.2.46"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/ws": "^8.18.1",