@vesk/vesk-cli 0.2.57 → 0.2.59

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.
Files changed (2) hide show
  1. package/dist/cli.js +386 -17
  2. package/package.json +5 -5
package/dist/cli.js CHANGED
@@ -3369,6 +3369,17 @@ ${String(this.root.outerHTML).slice(0, 800)}` : ""),
3369
3369
  // callee's first claim and resets the deposit so later claims stay
3370
3370
  // positional against the advancing cursor. Consumed synchronously on the
3371
3371
  // same walker reference the call was made through.
3372
+ //
3373
+ // The deposit is ALSO consumed implicitly by the next claim made through this
3374
+ // walker when the caller passed no explicit skipK. A component written in plain
3375
+ // JS (lucide-vesk icons are the common case) never calls `takeSkipK`, so
3376
+ // before this the deposit was silently dropped: the icon's `nextElement("svg")`
3377
+ // claimed the slot still holding the STATIC residue in front of it
3378
+ // (`<button><span>Apply Now</span><svg/></button>`), the descriptor missed on
3379
+ // `<span>`, and the walker detached server-rendered content that no client
3380
+ // program had any intention of replacing. Implicit consumption keeps static
3381
+ // residue out of the way for plain-JS components exactly as it is for
3382
+ // compiled ones.
3372
3383
  inheritOffset = 0;
3373
3384
  injectSkipK(n) {
3374
3385
  this.inheritOffset = n;
@@ -3382,7 +3393,11 @@ ${String(this.root.outerHTML).slice(0, 800)}` : ""),
3382
3393
  if (globalThis.__vesk_hydrate_debug) {
3383
3394
  console.error("[hyd-dbg] claimAt", tag, "skip=", skipK, "idx=", this.idx, "els=", this.els.length);
3384
3395
  }
3385
- let target = this.advancePastSkippable(this.idx + (skipK || 0));
3396
+ let offset2 = skipK || 0;
3397
+ const inherited = this.inheritOffset;
3398
+ this.inheritOffset = 0;
3399
+ if (!offset2) offset2 = inherited;
3400
+ let target = this.advancePastSkippable(this.idx + offset2);
3386
3401
  if (target > this.els.length) target = this.idx;
3387
3402
  while (target < this.els.length) {
3388
3403
  const el = this.els[target];
@@ -21909,6 +21924,7 @@ function warnStaleConstBindings(comp, file) {
21909
21924
  for (const d of st.declarations || []) {
21910
21925
  if (d.id?.type !== "Identifier" || !d.init) continue;
21911
21926
  if (reactive.has(d.id.name)) continue;
21927
+ if (d.init.type === "ArrowFunctionExpression" || d.init.type === "FunctionExpression") continue;
21912
21928
  if (d.init.type === "Identifier" && reactive.has(d.init.name)) continue;
21913
21929
  const seen = /* @__PURE__ */ new Set();
21914
21930
  collectIdentifiers(d.init, seen);
@@ -30704,6 +30720,9 @@ function isReactiveExpression(node, tracked2) {
30704
30720
  if (!ast) return false;
30705
30721
  let reactive = false;
30706
30722
  walk4(ast, tracked2, {
30723
+ CallExpression() {
30724
+ reactive = true;
30725
+ },
30707
30726
  Identifier(n, context) {
30708
30727
  if (reactive) return;
30709
30728
  const parent = context.path.at(-1);
@@ -30831,7 +30850,7 @@ function emitComponentCall(ctx2, node, tracked2, effectsVar, parentVar, compPref
30831
30850
  ctx2.push(`return $f; })();`);
30832
30851
  propsEntries.push(`children: ${frag}`);
30833
30852
  }
30834
- if (ctx2.markerless && promptExpr && !plainTarget) {
30853
+ if (ctx2.markerless && promptExpr) {
30835
30854
  ctx2.push(`${walkerArg}.injectSkipK(${promptExpr});`);
30836
30855
  }
30837
30856
  ctx2.push(`let ${v} = undefined;`);
@@ -31669,6 +31688,18 @@ function generateComponent(comp, importedNames = /* @__PURE__ */ new Set(), hydr
31669
31688
  `(typeof __hydrate.takeSkipK === 'function' ? __hydrate.takeSkipK() : 0)`
31670
31689
  );
31671
31690
  }
31691
+ if (ctx2.hydrate) {
31692
+ const bodyNodes = comp.body.filter((node) => !(node instanceof TrackDecl));
31693
+ const slotless = (node) => node instanceof StaticNode || node instanceof TextNode || node instanceof RuntimeStatement || node instanceof DynamicBinding && node.kind === "text";
31694
+ const claimless = bodyNodes.every((node) => slotless(node) && !claimsWalkerNode(ctx2, node));
31695
+ if (claimless) {
31696
+ const slots = bodyNodes.filter((node) => node instanceof StaticNode).length;
31697
+ for (let i = 0; i < slots; i++) {
31698
+ ctx2.push(indent2(`__hydrate.claimOnly(undefined, ${i === 0 && topBudget ? topBudget : "0"});`));
31699
+ if (i === 0 && topBudget) ctx2.push(indent2(`${topBudget} = 0;`));
31700
+ }
31701
+ }
31702
+ }
31672
31703
  for (const node of comp.body) {
31673
31704
  if (node instanceof TrackDecl) continue;
31674
31705
  const v = emitNode(ctx2, node, tracked2, null, void 0, "__components", topPendingSkip, topBudget || void 0);
@@ -31693,7 +31724,16 @@ function generateComponent(comp, importedNames = /* @__PURE__ */ new Set(), hydr
31693
31724
  ctx2.push(indent2(`if ($mount === null) $mount = document.createDocumentFragment();`));
31694
31725
  ctx2.push(indent2(`return __pendingChild || $mount;`));
31695
31726
  } else {
31696
- ctx2.push(indent2(`return __pendingChild || $root;`));
31727
+ const hasBareSlot = comp.body.some((node) => node instanceof SlotNode);
31728
+ if (hasBareSlot) {
31729
+ ctx2.push(indent2(`if (__pendingChild !== null && __pendingChild !== undefined) {`));
31730
+ ctx2.push(indent2(` if (typeof __pendingChild.then === 'function') return __pendingChild.then((__c) => { $root.appendChild(toDomNode(__c)); return $root; });`));
31731
+ ctx2.push(indent2(` const __c = toDomNode(__pendingChild);`));
31732
+ ctx2.push(indent2(` if ($root.childNodes.length === 0) return __c;`));
31733
+ ctx2.push(indent2(` $root.appendChild(__c);`));
31734
+ ctx2.push(indent2(`}`));
31735
+ }
31736
+ ctx2.push(indent2(hasBareSlot ? `return $root;` : `return __pendingChild || $root;`));
31697
31737
  }
31698
31738
  ctx2.push(indent2(`} finally {`));
31699
31739
  ctx2.push(indent2(` setActiveComponent(__prev);`));
@@ -49507,6 +49547,34 @@ function demoteExports(code) {
49507
49547
  function chunkHasImports(headLen, mergeAborted) {
49508
49548
  return headLen > 0 || mergeAborted;
49509
49549
  }
49550
+ function runtimeImportsOf(filePath) {
49551
+ let text;
49552
+ try {
49553
+ text = readFileSync11(filePath, "utf8");
49554
+ } catch {
49555
+ return [];
49556
+ }
49557
+ const names = [];
49558
+ const pattern2 = /import\s+(type\s+)?\{([^}]*)\}\s*from\s*['"]@vesk\/runtime[^'"]*['"]/g;
49559
+ for (const match of text.matchAll(pattern2)) {
49560
+ if (match[1]) continue;
49561
+ for (const part of match[2].split(",")) {
49562
+ const cleaned = part.trim().replace(/^type\s+/, "");
49563
+ if (!cleaned) continue;
49564
+ const name = cleaned.split(/\s+as\s+/)[0].trim();
49565
+ if (name) names.push(name);
49566
+ }
49567
+ }
49568
+ return names;
49569
+ }
49570
+ function sharedRuntimeResolver(shimPath) {
49571
+ return {
49572
+ name: "vesk-shared-runtime",
49573
+ setup(build4) {
49574
+ build4.onResolve({ filter: /^@vesk\/runtime(\/.*)?$/ }, () => ({ path: shimPath }));
49575
+ }
49576
+ };
49577
+ }
49510
49578
  var ChunkImports = class {
49511
49579
  bySource = /* @__PURE__ */ new Map();
49512
49580
  aborted = false;
@@ -49604,6 +49672,20 @@ var ChunkImports = class {
49604
49672
  get reason() {
49605
49673
  return this.abortReason;
49606
49674
  }
49675
+ /** Every import source this chunk's merged block pulls in (absolute paths). */
49676
+ sources() {
49677
+ return this.aborted ? [] : [...this.bySource.keys()];
49678
+ }
49679
+ /**
49680
+ * The named bindings this chunk imports from `source`, so a shared-module
49681
+ * shim can re-export exactly what consumers use (re-exporting `*` from the
49682
+ * real module would drag it back into the chunk being built).
49683
+ */
49684
+ bindingsFor(source) {
49685
+ const group = this.bySource.get(source);
49686
+ if (!group) return { named: [], hasDefault: false };
49687
+ return { named: [...group.named.keys()].sort(), hasDefault: !!group.default };
49688
+ }
49607
49689
  /** Renders the deduped import block (empty string when there are no imports). */
49608
49690
  render() {
49609
49691
  if (this.aborted || this.bySource.size === 0) return "";
@@ -49739,6 +49821,7 @@ async function generateClientBundle(routeTree, appDir, componentMap, options) {
49739
49821
  let cachedFileHits = 0;
49740
49822
  let compiledFiles = 0;
49741
49823
  let mainFromCache = false;
49824
+ let sharedModuleBundle = "";
49742
49825
  function mustReuseWithoutStat(filePath) {
49743
49826
  return !!only && !only.has(filePath);
49744
49827
  }
@@ -49924,7 +50007,7 @@ async function generateClientBundle(routeTree, appDir, componentMap, options) {
49924
50007
  if (!chunkImports.ok) console.error("[vesk] chunk import merge aborted for", chunkName + ":", chunkImports.reason);
49925
50008
  const head = chunkImports.render();
49926
50009
  const code = (head ? head + "\n\n" : "") + chunkCode.join("\n\n");
49927
- chunkEntries.push({ name: chunkName, code, node, imports: chunkHasImports(head.length, !chunkImports.ok) });
50010
+ chunkEntries.push({ name: chunkName, code, node, importsTable: chunkImports, imports: chunkHasImports(head.length, !chunkImports.ok) });
49928
50011
  }
49929
50012
  walkSplit2(node.children || [], [..._chain, node]);
49930
50013
  }
@@ -49953,7 +50036,7 @@ async function generateClientBundle(routeTree, appDir, componentMap, options) {
49953
50036
  if (!sharedImports.ok) console.error("[vesk] chunk import merge aborted for shared.js:", sharedImports.reason);
49954
50037
  const head = sharedImports.render();
49955
50038
  const code = (head ? head + "\n\n" : "") + sharedCode.join("\n\n");
49956
- chunkEntries.push({ name: "shared.js", code, node: null, imports: chunkHasImports(head.length, !sharedImports.ok) });
50039
+ chunkEntries.push({ name: "shared.js", code, node: null, importsTable: sharedImports, imports: chunkHasImports(head.length, !sharedImports.ok) });
49957
50040
  }
49958
50041
  const chunkIIFE = (body) => `(()=>{
49959
50042
  const __components = globalThis.__components || (globalThis.__components = {});
@@ -49961,6 +50044,98 @@ const __hydrators = globalThis.__hydrators || (globalThis.__hydrators = {});
49961
50044
  ${body}
49962
50045
  })();
49963
50046
  `;
50047
+ const sharedUsage = /* @__PURE__ */ new Map();
50048
+ for (const entry of chunkEntries) {
50049
+ for (const source of new Set(entry.importsTable.sources())) {
50050
+ sharedUsage.set(source, (sharedUsage.get(source) ?? 0) + 1);
50051
+ }
50052
+ }
50053
+ const isAppLocal = (source) => !source.includes("node_modules") && source.startsWith(resolve10(appDir, ".."));
50054
+ const sharedSources = [...sharedUsage.entries()].filter(([source, uses]) => uses > 1 && isAppLocal(source)).map(([source]) => source);
50055
+ if (process.env.VESK_DEBUG_SHARED) {
50056
+ console.error("[shared] candidates:", [...sharedUsage.entries()].map(([s, n]) => `${n}x ${s}`).join("\n"));
50057
+ console.error("[shared] selected:", sharedSources.join(", "));
50058
+ }
50059
+ const chunkAlias = {};
50060
+ let sharedBundleCode = "";
50061
+ const sharedRuntimeNames = /* @__PURE__ */ new Set();
50062
+ if (sharedSources.length > 0) {
50063
+ const tmpBase = mkdtempSync(join7(resolve10(appDir, ".."), "tmp-vesk-shared-"));
50064
+ try {
50065
+ const sharedEntry = join7(tmpBase, "shared-entry.js");
50066
+ const lines = [];
50067
+ const members = [];
50068
+ sharedSources.forEach((source, i) => {
50069
+ lines.push(`import * as m${i} from ${JSON.stringify(source)};`);
50070
+ members.push(` ${JSON.stringify(source)}: m${i},`);
50071
+ });
50072
+ lines.push(`globalThis.__veskShared = { ${members.join("\n")} };`);
50073
+ writeFileSync4(sharedEntry, lines.join("\n"));
50074
+ const appTsconfig = findTsconfigPath(appDir);
50075
+ const runtimeShim = join7(tmpBase, "runtime-shim.js");
50076
+ const runtimeLines = [
50077
+ "// ONE runtime instance: the entry bundle publishes these on globalThis,",
50078
+ "// and route chunks import the same functions. A second bundled copy",
50079
+ "// would mean a second Cell class and a second currentEffect.",
50080
+ "const __r = (n) => (...args) => globalThis[n](...args);"
50081
+ ];
50082
+ for (const source of sharedSources) {
50083
+ for (const name of runtimeImportsOf(source)) sharedRuntimeNames.add(name);
50084
+ }
50085
+ for (const name of [...sharedRuntimeNames].sort()) {
50086
+ runtimeLines.push(`export const ${name} = __r(${JSON.stringify(name)});`);
50087
+ }
50088
+ writeFileSync4(runtimeShim, runtimeLines.join("\n") + "\n");
50089
+ const result = await build({
50090
+ entryPoints: [sharedEntry],
50091
+ bundle: true,
50092
+ format: "iife",
50093
+ platform: "browser",
50094
+ write: false,
50095
+ logLevel: "silent",
50096
+ loader: { ".js": "tsx" },
50097
+ legalComments: "none",
50098
+ // `@vesk/runtime` MUST stay the entry bundle's instance. Bundling a
50099
+ // second copy in here gives the app a second `Cell` class and a
50100
+ // second `currentEffect` variable: a store cell written from a click
50101
+ // notifies subscribers registered against the OTHER copy, so nothing
50102
+ // the store drives ever updates — writes land, no error, no repaint.
50103
+ plugins: [sharedRuntimeResolver(runtimeShim)],
50104
+ ...appTsconfig ? { tsconfig: appTsconfig } : {}
50105
+ });
50106
+ sharedBundleCode = result.outputFiles[0].text;
50107
+ sharedSources.forEach((source, i) => {
50108
+ const named = /* @__PURE__ */ new Set();
50109
+ let hasDefault = false;
50110
+ for (const entry of chunkEntries) {
50111
+ const bindings = entry.importsTable.bindingsFor(source);
50112
+ bindings.named.forEach((n) => named.add(n));
50113
+ hasDefault = hasDefault || bindings.hasDefault;
50114
+ }
50115
+ const shim = join7(tmpBase, `shim-${i}.js`);
50116
+ const shimLines = [`const m = globalThis.__veskShared[${JSON.stringify(source)}];`];
50117
+ if (hasDefault) shimLines.push(`export default m;`);
50118
+ for (const name of [...named].sort()) {
50119
+ shimLines.push(`export const ${name} = m[${JSON.stringify(name)}];`);
50120
+ }
50121
+ writeFileSync4(shim, shimLines.join("\n") + "\n");
50122
+ chunkAlias[source] = shim;
50123
+ });
50124
+ } catch (e) {
50125
+ console.error("[vesk] shared-module pass failed; falling back to per-chunk inlining:", e.message);
50126
+ sharedBundleCode = "";
50127
+ for (const key of Object.keys(chunkAlias)) delete chunkAlias[key];
50128
+ }
50129
+ }
50130
+ const sharedRedirectPlugin = Object.keys(chunkAlias).length > 0 ? {
50131
+ name: "vesk-shared-modules",
50132
+ setup(build4) {
50133
+ build4.onResolve({ filter: /.*/ }, (args2) => {
50134
+ const mapped = chunkAlias[args2.path];
50135
+ return mapped ? { path: mapped } : null;
50136
+ });
50137
+ }
50138
+ } : null;
49964
50139
  for (const entry of chunkEntries) {
49965
50140
  if (!entry.code.trim()) continue;
49966
50141
  let finalCode;
@@ -49989,6 +50164,13 @@ ${entry.code}
49989
50164
  // for a dependency stays in its own package; shipping every
49990
50165
  // module's header inside our own chunk adds nothing.
49991
50166
  legalComments: "none",
50167
+ // Shared app modules are NOT inlined here: the resolver below
50168
+ // redirects them to a generated shim reading
50169
+ // `globalThis.__veskShared`, so the single copy in
50170
+ // shared-modules.js serves every route chunk. (esbuild's `alias`
50171
+ // option only accepts bare package names, so this is a resolver
50172
+ // rather than a map.)
50173
+ ...sharedRedirectPlugin ? { plugins: [sharedRedirectPlugin] } : {},
49992
50174
  ...appTsconfig ? { tsconfig: appTsconfig } : {}
49993
50175
  });
49994
50176
  finalCode = result.outputFiles[0].text;
@@ -50016,13 +50198,15 @@ ${entry.code}
50016
50198
  chunks.push({ name: entry.name, code: finalCode });
50017
50199
  }
50018
50200
  annotate2(routeTree);
50019
- const mainKey = JSON.stringify(routeTree) + "|" + [...runtimeImportNames].sort().join(",") + `|${!!options?.hmr}|${!!options?.importRuntime}|${options?.routeDataCache ?? ""}`;
50201
+ sharedModuleBundle = sharedBundleCode;
50202
+ for (const name of sharedRuntimeNames) runtimeImportNames.add(name);
50203
+ const mainKey = JSON.stringify(routeTree) + "|" + [...runtimeImportNames].sort().join(",") + `|${!!options?.hmr}|${!!options?.importRuntime}|${options?.routeDataCache ?? ""}|${sharedModuleBundle.length}`;
50020
50204
  let main;
50021
50205
  if (cache2?.mainBundle && cache2.mainBundle.key === mainKey) {
50022
50206
  mainFromCache = true;
50023
50207
  main = cache2.mainBundle.code;
50024
50208
  } else {
50025
- main = await buildMainBundle(routeTree, runtimeDir, true, {}, !!options?.hmr, !!options?.importRuntime, runtimeImportNames, options?.routeDataCache);
50209
+ main = await buildMainBundle(routeTree, runtimeDir, true, {}, !!options?.hmr, !!options?.importRuntime, runtimeImportNames, options?.routeDataCache, sharedModuleBundle);
50026
50210
  if (cache2) cache2.mainBundle = { key: mainKey, code: main };
50027
50211
  }
50028
50212
  const transformedChunks = [];
@@ -50223,7 +50407,7 @@ export { ${unique.join(", ")} };
50223
50407
  function appendHmrGlobals(code) {
50224
50408
  return code + "globalThis.__vesk_hmr_eval = (code, n) => {\n if (!n || n !== globalThis.__vesk_hmr_nonce) throw new Error('vesk hmr: invalid nonce');\n return eval(code);\n};\n";
50225
50409
  }
50226
- async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, importRuntime, runtimeImportNames, routeDataCache) {
50410
+ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, importRuntime, runtimeImportNames, routeDataCache, sharedModuleBundle = "") {
50227
50411
  const baseRuntimeImports = ["createFileRouter", "get", "set", "effect", "track", "destroy_block", "getActiveComponent", "setActiveComponent", "NavLink", "Link", "reactiveProps", "matchRoute", "ensureChunk", "toDomNode", "rerenderNode"];
50228
50412
  const allRuntimeImports = runtimeImportNames && runtimeImportNames.size > 0 ? [.../* @__PURE__ */ new Set([...baseRuntimeImports, ...runtimeImportNames])] : baseRuntimeImports;
50229
50413
  const runtimeGlobals = [
@@ -50292,7 +50476,11 @@ if (__pendChunks.length > 0 && typeof ensureChunk === 'function') {
50292
50476
  `).join("") + "globalThis.__runtime_comps = __runtime_comps;\nglobalThis.__cleanup = __cleanup;\nglobalThis.__place = __place;\n\n";
50293
50477
  const extraGlobals = [...runtimeImportNames || []].filter((n) => n && n !== "default").map((n) => `globalThis.${n} = ${n};
50294
50478
  `).join("");
50295
- const code2 = preamble + "const __components = globalThis.__components || (globalThis.__components = {});\nconst __hydrators = globalThis.__hydrators || (globalThis.__hydrators = {});\nconst __runtime_comps = __components;\n\n" + runtimeGlobals2 + extraGlobals + cleanupFn + placeFn + "globalThis.__components = __components;\n" + resolveNamesFn + updateComponentsFn + "const __routeTree = " + routeTreeJson + ";\n__resolveNames(__routeTree);\n" + pendCode + startRouterCode;
50479
+ const code2 = preamble + "const __components = globalThis.__components || (globalThis.__components = {});\nconst __hydrators = globalThis.__hydrators || (globalThis.__hydrators = {});\nconst __runtime_comps = __components;\n\n" + runtimeGlobals2 + extraGlobals + // Shared app modules run HERE: after the runtime bindings are published,
50480
+ // before anything can request a route chunk. Earlier (at the top of the
50481
+ // bundle) `globalThis.track` was still undefined, so every module-scope
50482
+ // cell in a store was created against a missing runtime.
50483
+ (sharedModuleBundle ? sharedModuleBundle + "\n" : "") + cleanupFn + placeFn + "globalThis.__components = __components;\n" + resolveNamesFn + updateComponentsFn + "const __routeTree = " + routeTreeJson + ";\n__resolveNames(__routeTree);\n" + pendCode + startRouterCode;
50296
50484
  return hmr ? appendHmrGlobals(code2) : code2;
50297
50485
  }
50298
50486
  const componentLines = mono?.componentLines || [];
@@ -52547,6 +52735,9 @@ function isReactiveExpression2(node, tracked2) {
52547
52735
  return false;
52548
52736
  let reactive = false;
52549
52737
  walk7(ast, tracked2, {
52738
+ CallExpression() {
52739
+ reactive = true;
52740
+ },
52550
52741
  Identifier(n, context) {
52551
52742
  if (reactive)
52552
52743
  return;
@@ -52692,7 +52883,7 @@ function emitComponentCall2(ctx2, node, tracked2, effectsVar, parentVar, compPre
52692
52883
  ctx2.push(`return $f; })();`);
52693
52884
  propsEntries.push(`children: ${frag}`);
52694
52885
  }
52695
- if (ctx2.markerless && promptExpr && !plainTarget) {
52886
+ if (ctx2.markerless && promptExpr) {
52696
52887
  ctx2.push(`${walkerArg}.injectSkipK(${promptExpr});`);
52697
52888
  }
52698
52889
  ctx2.push(`let ${v} = undefined;`);
@@ -53616,6 +53807,19 @@ function generateComponent2(comp, importedNames = /* @__PURE__ */ new Set(), hyd
53616
53807
  if (ctx2.markerless && ctx2.hydrate) {
53617
53808
  topBudget = regionBudgetVar2(ctx2, 0, void 0, `(typeof __hydrate.takeSkipK === 'function' ? __hydrate.takeSkipK() : 0)`);
53618
53809
  }
53810
+ if (ctx2.hydrate) {
53811
+ const bodyNodes = comp.body.filter((node) => !(node instanceof TrackDecl));
53812
+ const slotless = (node) => node instanceof StaticNode || node instanceof TextNode || node instanceof RuntimeStatement || node instanceof DynamicBinding && node.kind === "text";
53813
+ const claimless = bodyNodes.every((node) => slotless(node) && !claimsWalkerNode2(ctx2, node));
53814
+ if (claimless) {
53815
+ const slots = bodyNodes.filter((node) => node instanceof StaticNode).length;
53816
+ for (let i = 0; i < slots; i++) {
53817
+ ctx2.push(indent3(`__hydrate.claimOnly(undefined, ${i === 0 && topBudget ? topBudget : "0"});`));
53818
+ if (i === 0 && topBudget)
53819
+ ctx2.push(indent3(`${topBudget} = 0;`));
53820
+ }
53821
+ }
53822
+ }
53619
53823
  for (const node of comp.body) {
53620
53824
  if (node instanceof TrackDecl)
53621
53825
  continue;
@@ -53644,7 +53848,16 @@ function generateComponent2(comp, importedNames = /* @__PURE__ */ new Set(), hyd
53644
53848
  ctx2.push(indent3(`if ($mount === null) $mount = document.createDocumentFragment();`));
53645
53849
  ctx2.push(indent3(`return __pendingChild || $mount;`));
53646
53850
  } else {
53647
- ctx2.push(indent3(`return __pendingChild || $root;`));
53851
+ const hasBareSlot = comp.body.some((node) => node instanceof SlotNode);
53852
+ if (hasBareSlot) {
53853
+ ctx2.push(indent3(`if (__pendingChild !== null && __pendingChild !== undefined) {`));
53854
+ ctx2.push(indent3(` if (typeof __pendingChild.then === 'function') return __pendingChild.then((__c) => { $root.appendChild(toDomNode(__c)); return $root; });`));
53855
+ ctx2.push(indent3(` const __c = toDomNode(__pendingChild);`));
53856
+ ctx2.push(indent3(` if ($root.childNodes.length === 0) return __c;`));
53857
+ ctx2.push(indent3(` $root.appendChild(__c);`));
53858
+ ctx2.push(indent3(`}`));
53859
+ }
53860
+ ctx2.push(indent3(hasBareSlot ? `return $root;` : `return __pendingChild || $root;`));
53648
53861
  }
53649
53862
  ctx2.push(indent3(`} finally {`));
53650
53863
  ctx2.push(indent3(` setActiveComponent(__prev);`));
@@ -55446,6 +55659,37 @@ function demoteExports2(code) {
55446
55659
  function chunkHasImports2(headLen, mergeAborted) {
55447
55660
  return headLen > 0 || mergeAborted;
55448
55661
  }
55662
+ function runtimeImportsOf2(filePath) {
55663
+ let text;
55664
+ try {
55665
+ text = readFileSync24(filePath, "utf8");
55666
+ } catch {
55667
+ return [];
55668
+ }
55669
+ const names = [];
55670
+ const pattern2 = /import\s+(type\s+)?\{([^}]*)\}\s*from\s*['"]@vesk\/runtime[^'"]*['"]/g;
55671
+ for (const match of text.matchAll(pattern2)) {
55672
+ if (match[1])
55673
+ continue;
55674
+ for (const part of match[2].split(",")) {
55675
+ const cleaned = part.trim().replace(/^type\s+/, "");
55676
+ if (!cleaned)
55677
+ continue;
55678
+ const name = cleaned.split(/\s+as\s+/)[0].trim();
55679
+ if (name)
55680
+ names.push(name);
55681
+ }
55682
+ }
55683
+ return names;
55684
+ }
55685
+ function sharedRuntimeResolver2(shimPath) {
55686
+ return {
55687
+ name: "vesk-shared-runtime",
55688
+ setup(build4) {
55689
+ build4.onResolve({ filter: /^@vesk\/runtime(\/.*)?$/ }, () => ({ path: shimPath }));
55690
+ }
55691
+ };
55692
+ }
55449
55693
  var ChunkImports2 = class {
55450
55694
  bySource = /* @__PURE__ */ new Map();
55451
55695
  aborted = false;
@@ -55553,6 +55797,21 @@ var ChunkImports2 = class {
55553
55797
  get reason() {
55554
55798
  return this.abortReason;
55555
55799
  }
55800
+ /** Every import source this chunk's merged block pulls in (absolute paths). */
55801
+ sources() {
55802
+ return this.aborted ? [] : [...this.bySource.keys()];
55803
+ }
55804
+ /**
55805
+ * The named bindings this chunk imports from `source`, so a shared-module
55806
+ * shim can re-export exactly what consumers use (re-exporting `*` from the
55807
+ * real module would drag it back into the chunk being built).
55808
+ */
55809
+ bindingsFor(source) {
55810
+ const group = this.bySource.get(source);
55811
+ if (!group)
55812
+ return { named: [], hasDefault: false };
55813
+ return { named: [...group.named.keys()].sort(), hasDefault: !!group.default };
55814
+ }
55556
55815
  /** Renders the deduped import block (empty string when there are no imports). */
55557
55816
  render() {
55558
55817
  if (this.aborted || this.bySource.size === 0)
@@ -55707,6 +55966,7 @@ async function generateClientBundle2(routeTree, appDir, componentMap, options) {
55707
55966
  let cachedFileHits = 0;
55708
55967
  let compiledFiles = 0;
55709
55968
  let mainFromCache = false;
55969
+ let sharedModuleBundle = "";
55710
55970
  function mustReuseWithoutStat(filePath) {
55711
55971
  return !!only && !only.has(filePath);
55712
55972
  }
@@ -55905,7 +56165,7 @@ async function generateClientBundle2(routeTree, appDir, componentMap, options) {
55905
56165
  console.error("[vesk] chunk import merge aborted for", chunkName + ":", chunkImports.reason);
55906
56166
  const head = chunkImports.render();
55907
56167
  const code = (head ? head + "\n\n" : "") + chunkCode.join("\n\n");
55908
- chunkEntries.push({ name: chunkName, code, node, imports: chunkHasImports2(head.length, !chunkImports.ok) });
56168
+ chunkEntries.push({ name: chunkName, code, node, importsTable: chunkImports, imports: chunkHasImports2(head.length, !chunkImports.ok) });
55909
56169
  }
55910
56170
  walkSplit2(node.children || [], [..._chain, node]);
55911
56171
  }
@@ -55935,7 +56195,7 @@ async function generateClientBundle2(routeTree, appDir, componentMap, options) {
55935
56195
  console.error("[vesk] chunk import merge aborted for shared.js:", sharedImports.reason);
55936
56196
  const head = sharedImports.render();
55937
56197
  const code = (head ? head + "\n\n" : "") + sharedCode.join("\n\n");
55938
- chunkEntries.push({ name: "shared.js", code, node: null, imports: chunkHasImports2(head.length, !sharedImports.ok) });
56198
+ chunkEntries.push({ name: "shared.js", code, node: null, importsTable: sharedImports, imports: chunkHasImports2(head.length, !sharedImports.ok) });
55939
56199
  }
55940
56200
  const chunkIIFE = (body) => `(()=>{
55941
56201
  const __components = globalThis.__components || (globalThis.__components = {});
@@ -55943,6 +56203,101 @@ const __hydrators = globalThis.__hydrators || (globalThis.__hydrators = {});
55943
56203
  ${body}
55944
56204
  })();
55945
56205
  `;
56206
+ const sharedUsage = /* @__PURE__ */ new Map();
56207
+ for (const entry of chunkEntries) {
56208
+ for (const source of new Set(entry.importsTable.sources())) {
56209
+ sharedUsage.set(source, (sharedUsage.get(source) ?? 0) + 1);
56210
+ }
56211
+ }
56212
+ const isAppLocal = (source) => !source.includes("node_modules") && source.startsWith(resolve24(appDir, ".."));
56213
+ const sharedSources = [...sharedUsage.entries()].filter(([source, uses]) => uses > 1 && isAppLocal(source)).map(([source]) => source);
56214
+ if (process.env.VESK_DEBUG_SHARED) {
56215
+ console.error("[shared] candidates:", [...sharedUsage.entries()].map(([s, n]) => `${n}x ${s}`).join("\n"));
56216
+ console.error("[shared] selected:", sharedSources.join(", "));
56217
+ }
56218
+ const chunkAlias = {};
56219
+ let sharedBundleCode = "";
56220
+ const sharedRuntimeNames = /* @__PURE__ */ new Set();
56221
+ if (sharedSources.length > 0) {
56222
+ const tmpBase = mkdtempSync2(join20(resolve24(appDir, ".."), "tmp-vesk-shared-"));
56223
+ try {
56224
+ const sharedEntry = join20(tmpBase, "shared-entry.js");
56225
+ const lines = [];
56226
+ const members = [];
56227
+ sharedSources.forEach((source, i) => {
56228
+ lines.push(`import * as m${i} from ${JSON.stringify(source)};`);
56229
+ members.push(` ${JSON.stringify(source)}: m${i},`);
56230
+ });
56231
+ lines.push(`globalThis.__veskShared = { ${members.join("\n")} };`);
56232
+ writeFileSync13(sharedEntry, lines.join("\n"));
56233
+ const appTsconfig = findTsconfigPath(appDir);
56234
+ const runtimeShim = join20(tmpBase, "runtime-shim.js");
56235
+ const runtimeLines = [
56236
+ "// ONE runtime instance: the entry bundle publishes these on globalThis,",
56237
+ "// and route chunks import the same functions. A second bundled copy",
56238
+ "// would mean a second Cell class and a second currentEffect.",
56239
+ "const __r = (n) => (...args) => globalThis[n](...args);"
56240
+ ];
56241
+ for (const source of sharedSources) {
56242
+ for (const name of runtimeImportsOf2(source))
56243
+ sharedRuntimeNames.add(name);
56244
+ }
56245
+ for (const name of [...sharedRuntimeNames].sort()) {
56246
+ runtimeLines.push(`export const ${name} = __r(${JSON.stringify(name)});`);
56247
+ }
56248
+ writeFileSync13(runtimeShim, runtimeLines.join("\n") + "\n");
56249
+ const result = await build3({
56250
+ entryPoints: [sharedEntry],
56251
+ bundle: true,
56252
+ format: "iife",
56253
+ platform: "browser",
56254
+ write: false,
56255
+ logLevel: "silent",
56256
+ loader: { ".js": "tsx" },
56257
+ legalComments: "none",
56258
+ // `@vesk/runtime` MUST stay the entry bundle's instance. Bundling a
56259
+ // second copy in here gives the app a second `Cell` class and a
56260
+ // second `currentEffect` variable: a store cell written from a click
56261
+ // notifies subscribers registered against the OTHER copy, so nothing
56262
+ // the store drives ever updates — writes land, no error, no repaint.
56263
+ plugins: [sharedRuntimeResolver2(runtimeShim)],
56264
+ ...appTsconfig ? { tsconfig: appTsconfig } : {}
56265
+ });
56266
+ sharedBundleCode = result.outputFiles[0].text;
56267
+ sharedSources.forEach((source, i) => {
56268
+ const named = /* @__PURE__ */ new Set();
56269
+ let hasDefault = false;
56270
+ for (const entry of chunkEntries) {
56271
+ const bindings = entry.importsTable.bindingsFor(source);
56272
+ bindings.named.forEach((n) => named.add(n));
56273
+ hasDefault = hasDefault || bindings.hasDefault;
56274
+ }
56275
+ const shim = join20(tmpBase, `shim-${i}.js`);
56276
+ const shimLines = [`const m = globalThis.__veskShared[${JSON.stringify(source)}];`];
56277
+ if (hasDefault)
56278
+ shimLines.push(`export default m;`);
56279
+ for (const name of [...named].sort()) {
56280
+ shimLines.push(`export const ${name} = m[${JSON.stringify(name)}];`);
56281
+ }
56282
+ writeFileSync13(shim, shimLines.join("\n") + "\n");
56283
+ chunkAlias[source] = shim;
56284
+ });
56285
+ } catch (e) {
56286
+ console.error("[vesk] shared-module pass failed; falling back to per-chunk inlining:", e.message);
56287
+ sharedBundleCode = "";
56288
+ for (const key of Object.keys(chunkAlias))
56289
+ delete chunkAlias[key];
56290
+ }
56291
+ }
56292
+ const sharedRedirectPlugin = Object.keys(chunkAlias).length > 0 ? {
56293
+ name: "vesk-shared-modules",
56294
+ setup(build4) {
56295
+ build4.onResolve({ filter: /.*/ }, (args2) => {
56296
+ const mapped = chunkAlias[args2.path];
56297
+ return mapped ? { path: mapped } : null;
56298
+ });
56299
+ }
56300
+ } : null;
55946
56301
  for (const entry of chunkEntries) {
55947
56302
  if (!entry.code.trim())
55948
56303
  continue;
@@ -55972,6 +56327,13 @@ ${entry.code}
55972
56327
  // for a dependency stays in its own package; shipping every
55973
56328
  // module's header inside our own chunk adds nothing.
55974
56329
  legalComments: "none",
56330
+ // Shared app modules are NOT inlined here: the resolver below
56331
+ // redirects them to a generated shim reading
56332
+ // `globalThis.__veskShared`, so the single copy in
56333
+ // shared-modules.js serves every route chunk. (esbuild's `alias`
56334
+ // option only accepts bare package names, so this is a resolver
56335
+ // rather than a map.)
56336
+ ...sharedRedirectPlugin ? { plugins: [sharedRedirectPlugin] } : {},
55975
56337
  ...appTsconfig ? { tsconfig: appTsconfig } : {}
55976
56338
  });
55977
56339
  finalCode = result.outputFiles[0].text;
@@ -55999,13 +56361,16 @@ ${entry.code}
55999
56361
  chunks.push({ name: entry.name, code: finalCode });
56000
56362
  }
56001
56363
  annotate2(routeTree);
56002
- const mainKey = JSON.stringify(routeTree) + "|" + [...runtimeImportNames].sort().join(",") + `|${!!options?.hmr}|${!!options?.importRuntime}|${options?.routeDataCache ?? ""}`;
56364
+ sharedModuleBundle = sharedBundleCode;
56365
+ for (const name of sharedRuntimeNames)
56366
+ runtimeImportNames.add(name);
56367
+ const mainKey = JSON.stringify(routeTree) + "|" + [...runtimeImportNames].sort().join(",") + `|${!!options?.hmr}|${!!options?.importRuntime}|${options?.routeDataCache ?? ""}|${sharedModuleBundle.length}`;
56003
56368
  let main;
56004
56369
  if (cache2?.mainBundle && cache2.mainBundle.key === mainKey) {
56005
56370
  mainFromCache = true;
56006
56371
  main = cache2.mainBundle.code;
56007
56372
  } else {
56008
- main = await buildMainBundle2(routeTree, runtimeDir, true, {}, !!options?.hmr, !!options?.importRuntime, runtimeImportNames, options?.routeDataCache);
56373
+ main = await buildMainBundle2(routeTree, runtimeDir, true, {}, !!options?.hmr, !!options?.importRuntime, runtimeImportNames, options?.routeDataCache, sharedModuleBundle);
56009
56374
  if (cache2)
56010
56375
  cache2.mainBundle = { key: mainKey, code: main };
56011
56376
  }
@@ -56222,7 +56587,7 @@ export { ${unique.join(", ")} };
56222
56587
  function appendHmrGlobals2(code) {
56223
56588
  return code + "globalThis.__vesk_hmr_eval = (code, n) => {\n if (!n || n !== globalThis.__vesk_hmr_nonce) throw new Error('vesk hmr: invalid nonce');\n return eval(code);\n};\n";
56224
56589
  }
56225
- async function buildMainBundle2(routeTree, runtimeDir, codeSplit, mono, hmr, importRuntime, runtimeImportNames, routeDataCache) {
56590
+ async function buildMainBundle2(routeTree, runtimeDir, codeSplit, mono, hmr, importRuntime, runtimeImportNames, routeDataCache, sharedModuleBundle = "") {
56226
56591
  const baseRuntimeImports = ["createFileRouter", "get", "set", "effect", "track", "destroy_block", "getActiveComponent", "setActiveComponent", "NavLink", "Link", "reactiveProps", "matchRoute", "ensureChunk", "toDomNode", "rerenderNode"];
56227
56592
  const allRuntimeImports = runtimeImportNames && runtimeImportNames.size > 0 ? [.../* @__PURE__ */ new Set([...baseRuntimeImports, ...runtimeImportNames])] : baseRuntimeImports;
56228
56593
  const runtimeGlobals = [
@@ -56291,7 +56656,11 @@ if (__pendChunks.length > 0 && typeof ensureChunk === 'function') {
56291
56656
  `).join("") + "globalThis.__runtime_comps = __runtime_comps;\nglobalThis.__cleanup = __cleanup;\nglobalThis.__place = __place;\n\n";
56292
56657
  const extraGlobals = [...runtimeImportNames || []].filter((n) => n && n !== "default").map((n) => `globalThis.${n} = ${n};
56293
56658
  `).join("");
56294
- const code2 = preamble + "const __components = globalThis.__components || (globalThis.__components = {});\nconst __hydrators = globalThis.__hydrators || (globalThis.__hydrators = {});\nconst __runtime_comps = __components;\n\n" + runtimeGlobals2 + extraGlobals + cleanupFn + placeFn + "globalThis.__components = __components;\n" + resolveNamesFn + updateComponentsFn + "const __routeTree = " + routeTreeJson + ";\n__resolveNames(__routeTree);\n" + pendCode + startRouterCode;
56659
+ const code2 = preamble + "const __components = globalThis.__components || (globalThis.__components = {});\nconst __hydrators = globalThis.__hydrators || (globalThis.__hydrators = {});\nconst __runtime_comps = __components;\n\n" + runtimeGlobals2 + extraGlobals + // Shared app modules run HERE: after the runtime bindings are published,
56660
+ // before anything can request a route chunk. Earlier (at the top of the
56661
+ // bundle) `globalThis.track` was still undefined, so every module-scope
56662
+ // cell in a store was created against a missing runtime.
56663
+ (sharedModuleBundle ? sharedModuleBundle + "\n" : "") + cleanupFn + placeFn + "globalThis.__components = __components;\n" + resolveNamesFn + updateComponentsFn + "const __routeTree = " + routeTreeJson + ";\n__resolveNames(__routeTree);\n" + pendCode + startRouterCode;
56295
56664
  return hmr ? appendHmrGlobals2(code2) : code2;
56296
56665
  }
56297
56666
  const componentLines = mono?.componentLines || [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/vesk-cli",
3
- "version": "0.2.57",
3
+ "version": "0.2.59",
4
4
  "type": "module",
5
5
  "description": "Vesk — compiler-first framework for the post-VDOM web. vesk dev, vesk build.",
6
6
  "bin": {
@@ -8,10 +8,10 @@
8
8
  },
9
9
  "main": "./dist/cli.js",
10
10
  "dependencies": {
11
- "@vesk/adapter": "^0.2.57",
12
- "@vesk/compiler": "^0.2.57",
13
- "@vesk/plugin-tailwind": "^0.2.57",
14
- "@vesk/runtime": "^0.2.57",
11
+ "@vesk/adapter": "^0.2.59",
12
+ "@vesk/compiler": "^0.2.59",
13
+ "@vesk/plugin-tailwind": "^0.2.59",
14
+ "@vesk/runtime": "^0.2.59",
15
15
  "esrap": "^2.3.0",
16
16
  "typescript": "^5.8.0",
17
17
  "ws": "^8.18.0",