@jundle/cloudflare 0.0.1-15 → 0.0.1-16

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/package.json +1 -1
  2. package/src/harness.js +54 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jundle/cloudflare",
3
- "version": "0.0.1-15",
3
+ "version": "0.0.1-16",
4
4
  "description": "Cloudflare Workers runtime for the jundle dev server (workerd via Miniflare).",
5
5
  "license": "ISC",
6
6
  "type": "module",
package/src/harness.js CHANGED
@@ -98,6 +98,28 @@ const evaluated = new Map();
98
98
  const depText = new Map();
99
99
  // Evaluated dep bundles: key -> {exports, failed, error}.
100
100
  const depEvaluated = new Map();
101
+ // Serializes dep-text walks (FIFO promise queue):
102
+ // concurrent chunk loads walk the same shared bundles,
103
+ // and ensureDepText's "already fetched" early-return
104
+ // does not wait for an in-flight subtree walk — a
105
+ // second walk can see a bundle's text present, skip its
106
+ // subtree, and evaluate it while the first walk is
107
+ // still fetching its transitive deps ("was not
108
+ // pre-fetched", non-deterministic in which bundle). One
109
+ // walk at a time closes the race; the recursion inside
110
+ // a walk never re-enters the queue, so it cannot
111
+ // deadlock. A failed walk rejects to its caller but
112
+ // resolves the queue slot, so it never poisons later
113
+ // walks.
114
+ let depWalkQueue = Promise.resolve();
115
+ function runDepWalk(fn) {
116
+ const run = depWalkQueue.then(fn);
117
+ depWalkQueue = run.then(
118
+ () => {},
119
+ () => {},
120
+ );
121
+ return run;
122
+ }
101
123
  // Parsed JSON values: key `${env}\0${file}` -> value.
102
124
  const jsonCache = new Map();
103
125
  // Prefetched asset values: key `${env}\0${file}\0${type}` ->
@@ -298,7 +320,12 @@ async function ensureText(url, visiting = new Set()) {
298
320
  if (edge.kind === "dev" || edge.kind === "client-ref" || edge.kind === "server-action") {
299
321
  await ensureText(edge.url, visiting);
300
322
  } else if (edge.kind === "dep") {
301
- await ensureDepText(edge.url, new Set());
323
+ // Serialized through the dep walk queue: two
324
+ // concurrent dispatches walk the same shared
325
+ // bundles, and an unserialized walk can evaluate a
326
+ // bundle whose subtree another walk is still
327
+ // fetching (same race as loadChunk, below).
328
+ await runDepWalk(() => ensureDepText(edge.url, new Set()));
302
329
  } else if (edge.kind === "json") {
303
330
  const jk = devKey(edge.url);
304
331
  if (!jsonCache.has(jk)) {
@@ -563,6 +590,16 @@ function requireHook(spec, importerKey) {
563
590
  const key = `${importerEnv}\0${candidate}`;
564
591
  if (jsonCache.has(key)) return jsonCache.get(key);
565
592
  }
593
+ // Literal dynamic imports (import("./route")) compile
594
+ // to a require of the raw relative spec (the harness
595
+ // has no async __jundleImport), and the closure walk
596
+ // pre-fetches them via their dev edges — so resolve
597
+ // the spec the same way and evaluate the pre-fetched
598
+ // module. A spec with no pre-fetched text falls
599
+ // through to the loud error below (a truly-dynamic
600
+ // specifier has no static target to pre-fetch).
601
+ const devModuleKey = `${importerEnv}\0${normalized}`;
602
+ if (textCache.has(devModuleKey)) return evaluateDev(devModuleKey).exports;
566
603
  }
567
604
  throw new Error(
568
605
  `ModuleRunner: '${spec}' was not pre-loaded (the closure walk missed it); use a static, literal specifier for this import so the walk pre-loads it; dynamic specifiers are not pre-loaded.`,
@@ -588,6 +625,22 @@ const hostContext = {
588
625
  return (await ensureModuleByUrl(resolved.url)).exports;
589
626
  },
590
627
  loadChunk: async (chunkId) => {
628
+ // Dep chunk ids (the Flight client preloads dep
629
+ // client components by their bundle URL): fetch the
630
+ // dep text on demand — the closure walk does not
631
+ // pre-fetch dep client-ref bundles (their client-ref
632
+ // edges carry a prebundleSpec and are omitted) — then
633
+ // evaluate it, mirroring NodeRunner's hostContext.
634
+ // Serialized through the dep walk queue: concurrent
635
+ // chunk loads share transitive bundles, and an
636
+ // unserialized walk can evaluate a bundle whose
637
+ // subtree another walk is still fetching.
638
+ if (chunkId.startsWith("/@jundle/deps/")) {
639
+ return runDepWalk(async () => {
640
+ await ensureDepText(chunkId);
641
+ return evaluateDep(chunkId).exports;
642
+ });
643
+ }
591
644
  const u = new URL(chunkId, "http://dev");
592
645
  if (u.pathname !== "/@jundle/module")
593
646
  throw new Error(`ModuleRunner: refusing to load chunk (not a dev module URL): ${chunkId}`);