@jundle/cloudflare 0.0.1-14 → 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.
- package/package.json +1 -1
- package/src/harness.js +90 -3
package/package.json
CHANGED
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}` ->
|
|
@@ -220,16 +242,50 @@ async function syncVersion() {
|
|
|
220
242
|
// document in a worker), and surfacing it would trip the
|
|
221
243
|
// hot client's first-update overlay rule and force a full
|
|
222
244
|
// reload on the next update instead of HMR.
|
|
245
|
+
//
|
|
246
|
+
// The re-import can install a second (browser) shim over
|
|
247
|
+
// the dev shim's globals — client.browser.js imports
|
|
248
|
+
// ./shim.js first, so any module importing the client
|
|
249
|
+
// runtime installs it as a side effect — and the
|
|
250
|
+
// client's chunkCache skips re-loading chunks, so a
|
|
251
|
+
// poisoned global makes the next render require through
|
|
252
|
+
// an empty shim ("has not been loaded yet"). Snapshot the
|
|
253
|
+
// four shim globals before the re-import and restore
|
|
254
|
+
// them before the push lands (finally, so a failed
|
|
255
|
+
// import cannot leave them poisoned). A snapshot of
|
|
256
|
+
// undefined restores by deleting the global, not by
|
|
257
|
+
// assigning undefined (the shim was not installed yet).
|
|
258
|
+
const SHIM_GLOBALS = [
|
|
259
|
+
"__webpack_require__",
|
|
260
|
+
"__webpack_chunk_load__",
|
|
261
|
+
"__webpack_get_script_filename__",
|
|
262
|
+
"__jundleFlight",
|
|
263
|
+
];
|
|
264
|
+
function snapshotShimGlobals() {
|
|
265
|
+
return SHIM_GLOBALS.map((name) => [name, globalThis[name]]);
|
|
266
|
+
}
|
|
267
|
+
function restoreShimGlobals(saved) {
|
|
268
|
+
for (const [name, value] of saved) {
|
|
269
|
+
if (value === undefined) delete globalThis[name];
|
|
270
|
+
else globalThis[name] = value;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
223
273
|
async function pushSsrUpdates(clientKeys) {
|
|
224
274
|
for (const key of clientKeys) {
|
|
225
275
|
const [env, file] = key.split("\0");
|
|
226
276
|
const url = `/@jundle/module?env=${encodeURIComponent(env)}&file=${encodeURIComponent(file)}`;
|
|
277
|
+
const saved = snapshotShimGlobals();
|
|
278
|
+
let rec;
|
|
279
|
+
let ok = false;
|
|
227
280
|
try {
|
|
228
|
-
|
|
229
|
-
|
|
281
|
+
rec = await ensureModuleByUrl(url);
|
|
282
|
+
ok = true;
|
|
230
283
|
} catch (e) {
|
|
231
284
|
console.error(`[jundle runner] SSR push ${url}: ${e && e.message ? e.message : String(e)}`);
|
|
285
|
+
} finally {
|
|
286
|
+
restoreShimGlobals(saved);
|
|
232
287
|
}
|
|
288
|
+
if (ok) globalThis.__jundleFlight?.updateModule?.(url, rec.exports);
|
|
233
289
|
}
|
|
234
290
|
}
|
|
235
291
|
|
|
@@ -264,7 +320,12 @@ async function ensureText(url, visiting = new Set()) {
|
|
|
264
320
|
if (edge.kind === "dev" || edge.kind === "client-ref" || edge.kind === "server-action") {
|
|
265
321
|
await ensureText(edge.url, visiting);
|
|
266
322
|
} else if (edge.kind === "dep") {
|
|
267
|
-
|
|
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()));
|
|
268
329
|
} else if (edge.kind === "json") {
|
|
269
330
|
const jk = devKey(edge.url);
|
|
270
331
|
if (!jsonCache.has(jk)) {
|
|
@@ -529,6 +590,16 @@ function requireHook(spec, importerKey) {
|
|
|
529
590
|
const key = `${importerEnv}\0${candidate}`;
|
|
530
591
|
if (jsonCache.has(key)) return jsonCache.get(key);
|
|
531
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;
|
|
532
603
|
}
|
|
533
604
|
throw new Error(
|
|
534
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.`,
|
|
@@ -554,6 +625,22 @@ const hostContext = {
|
|
|
554
625
|
return (await ensureModuleByUrl(resolved.url)).exports;
|
|
555
626
|
},
|
|
556
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
|
+
}
|
|
557
644
|
const u = new URL(chunkId, "http://dev");
|
|
558
645
|
if (u.pathname !== "/@jundle/module")
|
|
559
646
|
throw new Error(`ModuleRunner: refusing to load chunk (not a dev module URL): ${chunkId}`);
|