diff --git a/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs b/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs
index 40be6b3de0f06..fde609a3a913f 100644
--- a/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs
+++ b/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs
@@ -147,6 +147,7 @@ impl EcmascriptDevEvaluateChunk {
let runtime_code = turbopack_ecmascript_runtime::get_browser_runtime_code(
environment,
chunking_context.chunk_base_path(),
+ Value::new(chunking_context.runtime_type()),
Vc::cell(output_root.to_string().into()),
);
code.push_code(&*runtime_code.await?);
@@ -155,6 +156,7 @@ impl EcmascriptDevEvaluateChunk {
let runtime_code = turbopack_ecmascript_runtime::get_browser_runtime_code(
environment,
chunking_context.chunk_base_path(),
+ Value::new(chunking_context.runtime_type()),
Vc::cell(output_root.to_string().into()),
);
code.push_code(&*runtime_code.await?);
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/package.json b/turbopack/crates/turbopack-ecmascript-runtime/js/package.json
index e9d1421f77411..b5ff2686751c3 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/package.json
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/package.json
@@ -8,9 +8,9 @@
"check": "run-p check:*",
"check:nodejs": "tsc -p src/nodejs",
"check:browser-dev-client": "tsc -p src/browser/dev/hmr-client",
- "check:browser-dev-runtime-base": "tsc -p src/browser/dev/runtime/base",
- "check:browser-dev-runtime-dom": "tsc -p src/browser/dev/runtime/dom",
- "check:browser-dev-runtime-edge": "tsc -p src/browser/dev/runtime/edge"
+ "check:browser-runtime-base": "tsc -p src/browser/runtime/base",
+ "check:browser-runtime-dom": "tsc -p src/browser/runtime/dom",
+ "check:browser-runtime-edge": "tsc -p src/browser/runtime/edge"
},
"exports": {
".": "./src/main.js",
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/hmr-client/hmr-client.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/hmr-client/hmr-client.ts
index fc011843a752c..a8423a2d78fb1 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/hmr-client/hmr-client.ts
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/hmr-client/hmr-client.ts
@@ -1,7 +1,7 @@
///
-///
-///
-///
+///
+///
+///
import {
addMessageListener as turboSocketAddMessageListener,
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/build-base.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/build-base.ts
new file mode 100644
index 0000000000000..a3968238aadc7
--- /dev/null
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/build-base.ts
@@ -0,0 +1,144 @@
+///
+///
+
+declare var augmentContext: ((context: unknown) => unknown);
+
+const moduleCache: ModuleCache = {};
+
+/**
+ * Gets or instantiates a runtime module.
+ */
+// @ts-ignore
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function getOrInstantiateRuntimeModule(
+ moduleId: ModuleId,
+ chunkPath: ChunkPath,
+): Module {
+ const module = moduleCache[moduleId];
+ if (module) {
+ if (module.error) {
+ throw module.error;
+ }
+ return module;
+ }
+
+ return instantiateModule(moduleId, { type: SourceType.Runtime, chunkPath });
+}
+
+/**
+ * Retrieves a module from the cache, or instantiate it if it is not cached.
+ */
+// Used by the backend
+// @ts-ignore
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const getOrInstantiateModuleFromParent: GetOrInstantiateModuleFromParent = (
+ id,
+ sourceModule
+) => {
+ const module = moduleCache[id];
+
+ if (module) {
+ return module;
+ }
+
+ return instantiateModule(id, {
+ type: SourceType.Parent,
+ parentId: sourceModule.id,
+ });
+};
+
+function instantiateModule(id: ModuleId, source: SourceInfo): Module {
+ const moduleFactory = moduleFactories[id];
+ if (typeof moduleFactory !== "function") {
+ // This can happen if modules incorrectly handle HMR disposes/updates,
+ // e.g. when they keep a `setTimeout` around which still executes old code
+ // and contains e.g. a `require("something")` call.
+ let instantiationReason;
+ switch (source.type) {
+ case SourceType.Runtime:
+ instantiationReason = `as a runtime entry of chunk ${source.chunkPath}`;
+ break;
+ case SourceType.Parent:
+ instantiationReason = `because it was required from module ${source.parentId}`;
+ break;
+ case SourceType.Update:
+ instantiationReason = "because of an HMR update";
+ break;
+ default:
+ invariant(source, (source) => `Unknown source type: ${source?.type}`);
+ }
+ throw new Error(
+ `Module ${id} was instantiated ${instantiationReason}, but the module factory is not available. It might have been deleted in an HMR update.`
+ );
+ }
+
+ switch (source.type) {
+ case SourceType.Runtime:
+ runtimeModules.add(id);
+ break;
+ case SourceType.Parent:
+ // No need to add this module as a child of the parent module here, this
+ // has already been taken care of in `getOrInstantiateModuleFromParent`.
+ break;
+ case SourceType.Update:
+ throw new Error('Unexpected')
+ default:
+ invariant(source, (source) => `Unknown source type: ${source?.type}`);
+ }
+
+ const module: Module = {
+ exports: {},
+ error: undefined,
+ loaded: false,
+ id,
+ namespaceObject: undefined,
+ };
+
+ moduleCache[id] = module;
+
+ // NOTE(alexkirsz) This can fail when the module encounters a runtime error.
+ try {
+ const sourceInfo: SourceInfo = { type: SourceType.Parent, parentId: id };
+
+ const r = commonJsRequire.bind(null, module);
+ moduleFactory.call(
+ module.exports,
+ augmentContext({
+ a: asyncModule.bind(null, module),
+ e: module.exports,
+ r: commonJsRequire.bind(null, module),
+ t: runtimeRequire,
+ f: moduleContext,
+ i: esmImport.bind(null, module),
+ s: esmExport.bind(null, module, module.exports),
+ j: dynamicExport.bind(null, module, module.exports),
+ v: exportValue.bind(null, module),
+ n: exportNamespace.bind(null, module),
+ m: module,
+ c: moduleCache,
+ M: moduleFactories,
+ l: loadChunk.bind(null, sourceInfo),
+ w: loadWebAssembly.bind(null, sourceInfo),
+ u: loadWebAssemblyModule.bind(null, sourceInfo),
+ g: globalThis,
+ P: resolveAbsolutePath,
+ U: relativeURL,
+ R: createResolvePathFromModule(r),
+ b: getWorkerBlobURL,
+ __dirname: typeof module.id === "string" ? module.id.replace(/(^|\/)\/+$/, "") : module.id
+ })
+ );
+ } catch (error) {
+ module.error = error as any;
+ throw error;
+ }
+
+ module.loaded = true;
+ if (module.namespaceObject && module.exports !== module.namespaceObject) {
+ // in case of a circular dependency: cjs1 -> esm2 -> cjs1
+ interopEsm(module.exports, module.namespaceObject);
+ }
+
+ return module;
+}
+
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/runtime-base.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-base.ts
similarity index 75%
rename from turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/runtime-base.ts
rename to turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-base.ts
index 69c8019a4915b..ea88ef16ed1b8 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/runtime-base.ts
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-base.ts
@@ -1,3 +1,7 @@
+///
+///
+///
+
/**
* This file contains runtime types and functions that are shared between all
* Turbopack *development* ECMAScript runtimes.
@@ -8,10 +12,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-///
-///
-///
-///
+const devModuleCache: ModuleCache = Object.create(null);
// This file must not use `import` and `export` statements. Otherwise, it
// becomes impossible to augment interfaces declared in ``d files
@@ -21,8 +22,8 @@ type RefreshRuntimeGlobals =
// Workers are loaded via blob object urls and aren't relative to the main context, this gets
// prefixed to chunk urls in the worker.
-declare var TURBOPACK_WORKER_LOCATION: string;
-declare var CHUNK_BASE_PATH: string;
+// declare var TURBOPACK_WORKER_LOCATION: string;
+// declare var CHUNK_BASE_PATH: string;
declare var $RefreshHelpers$: RefreshRuntimeGlobals["$RefreshHelpers$"];
declare var $RefreshReg$: RefreshRuntimeGlobals["$RefreshReg$"];
declare var $RefreshSig$: RefreshRuntimeGlobals["$RefreshSig$"];
@@ -37,74 +38,21 @@ type RefreshContext = {
type RefreshHelpers = RefreshRuntimeGlobals["$RefreshHelpers$"];
-interface TurbopackDevBaseContext extends TurbopackBaseContext {
+interface TurbopackDevBaseContext extends TurbopackBaseContext {
k: RefreshContext;
R: ResolvePathFromModule;
}
interface TurbopackDevContext extends TurbopackDevBaseContext {}
-// string encoding of a module factory (used in hmr updates)
-type ModuleFactoryString = string;
-
type ModuleFactory = (
this: Module["exports"],
- context: TurbopackDevContext
-) => undefined;
-
-type DevRuntimeParams = {
- otherChunks: ChunkData[];
- runtimeModuleIds: ModuleId[];
-};
-
-type ChunkRegistration = [
- chunkPath: ChunkPath,
- chunkModules: ModuleFactories,
- params: DevRuntimeParams | undefined
-];
-type ChunkList = {
- path: ChunkPath;
- chunks: ChunkData[];
- source: "entry" | "dynamic";
-};
-
-enum SourceType {
- /**
- * The module was instantiated because it was included in an evaluated chunk's
- * runtime.
- */
- Runtime = 0,
- /**
- * The module was instantiated because a parent module imported it.
- */
- Parent = 1,
- /**
- * The module was instantiated because it was included in a chunk's hot module
- * update.
- */
- Update = 2,
-}
-
-type SourceInfo =
- | {
- type: SourceType.Runtime;
- chunkPath: ChunkPath;
- }
- | {
- type: SourceType.Parent;
- parentId: ModuleId;
- }
- | {
- type: SourceType.Update;
- parents?: ModuleId[];
- };
+ context: TurbopackDevBaseContext
+) => undefined
-interface RuntimeBackend {
- registerChunk: (chunkPath: ChunkPath, params?: DevRuntimeParams) => void;
- loadChunk: (chunkPath: ChunkPath, source: SourceInfo) => Promise;
+interface DevRuntimeBackend {
reloadChunk?: (chunkPath: ChunkPath) => Promise;
unloadChunk?: (chunkPath: ChunkPath) => void;
-
restart: () => void;
}
@@ -119,8 +67,6 @@ class UpdateApplyError extends Error {
}
}
-const moduleFactories: ModuleFactories = Object.create(null);
-const moduleCache: ModuleCache = Object.create(null);
/**
* Maps module IDs to persisted data between executions of their hot module
* implementation (`hot.data`).
@@ -134,161 +80,62 @@ const moduleHotState: Map = new Map();
* Modules that call `module.hot.invalidate()` (while being updated).
*/
const queuedInvalidatedModules: Set = new Set();
+
/**
- * Module IDs that are instantiated as part of the runtime of a chunk.
- */
-const runtimeModules: Set = new Set();
-/**
- * Map from module ID to the chunks that contain this module.
- *
- * In HMR, we need to keep track of which modules are contained in which so
- * chunks. This is so we don't eagerly dispose of a module when it is removed
- * from chunk A, but still exists in chunk B.
- */
-const moduleChunksMap: Map> = new Map();
-/**
- * Map from a chunk path to all modules it contains.
- */
-const chunkModulesMap: Map> = new Map();
-/**
- * Chunk lists that contain a runtime. When these chunk lists receive an update
- * that can't be reconciled with the current state of the page, we need to
- * reload the runtime entirely.
- */
-const runtimeChunkLists: Set = new Set();
-/**
- * Map from a chunk list to the chunk paths it contains.
- */
-const chunkListChunksMap: Map> = new Map();
-/**
- * Map from a chunk path to the chunk lists it belongs to.
+ * Gets or instantiates a runtime module.
*/
-const chunkChunkListsMap: Map> = new Map();
-
-const availableModules: Map | true> = new Map();
-
-const availableModuleChunks: Map | true> = new Map();
-
-async function loadChunk(
- source: SourceInfo,
- chunkData: ChunkData
-): Promise {
- if (typeof chunkData === "string") {
- return loadChunkPath(source, chunkData);
- }
-
- const includedList = chunkData.included || [];
- const modulesPromises = includedList.map((included) => {
- if (moduleFactories[included]) return true;
- return availableModules.get(included);
- });
- if (modulesPromises.length > 0 && modulesPromises.every((p) => p)) {
- // When all included items are already loaded or loading, we can skip loading ourselves
- return Promise.all(modulesPromises);
- }
-
- const includedModuleChunksList = chunkData.moduleChunks || [];
- const moduleChunksPromises = includedModuleChunksList
- .map((included) => {
- // TODO(alexkirsz) Do we need this check?
- // if (moduleFactories[included]) return true;
- return availableModuleChunks.get(included);
- })
- .filter((p) => p);
-
- let promise;
- if (moduleChunksPromises.length > 0) {
- // Some module chunks are already loaded or loading.
-
- if (moduleChunksPromises.length === includedModuleChunksList.length) {
- // When all included module chunks are already loaded or loading, we can skip loading ourselves
- return Promise.all(moduleChunksPromises);
- }
-
- const moduleChunksToLoad: Set = new Set();
- for (const moduleChunk of includedModuleChunksList) {
- if (!availableModuleChunks.has(moduleChunk)) {
- moduleChunksToLoad.add(moduleChunk);
- }
+// @ts-ignore
+function getOrInstantiateRuntimeModule(
+ moduleId: ModuleId,
+ chunkPath: ChunkPath,
+): Module {
+ const module = devModuleCache[moduleId];
+ if (module) {
+ if (module.error) {
+ throw module.error;
}
+ return module;
+ }
- for (const moduleChunkToLoad of moduleChunksToLoad) {
- const promise = loadChunkPath(source, moduleChunkToLoad);
-
- availableModuleChunks.set(moduleChunkToLoad, promise);
+ // @ts-ignore
+ return instantiateModule(moduleId, { type: SourceType.Runtime, chunkPath });
+}
- moduleChunksPromises.push(promise);
- }
+/**
+ * Retrieves a module from the cache, or instantiate it if it is not cached.
+ */
+// @ts-ignore Defined in `runtime-utils.ts`
+const getOrInstantiateModuleFromParent: GetOrInstantiateModuleFromParent = (
+ id,
+ sourceModule,
+) => {
+ if (!sourceModule.hot.active) {
+ console.warn(
+ `Unexpected import of module ${id} from module ${sourceModule.id}, which was deleted by an HMR update`
+ );
+ }
- promise = Promise.all(moduleChunksPromises);
- } else {
- promise = loadChunkPath(source, chunkData.path);
+ const module = devModuleCache[id];
- // Mark all included module chunks as loading if they are not already loaded or loading.
- for (const includedModuleChunk of includedModuleChunksList) {
- if (!availableModuleChunks.has(includedModuleChunk)) {
- availableModuleChunks.set(includedModuleChunk, promise);
- }
- }
+ if (sourceModule.children.indexOf(id) === -1) {
+ sourceModule.children.push(id);
}
- for (const included of includedList) {
- if (!availableModules.has(included)) {
- // It might be better to race old and new promises, but it's rare that the new promise will be faster than a request started earlier.
- // In production it's even more rare, because the chunk optimization tries to deduplicate modules anyway.
- availableModules.set(included, promise);
+ if (module) {
+ if (module.parents.indexOf(sourceModule.id) === -1) {
+ module.parents.push(sourceModule.id);
}
- }
-
- return promise;
-}
-async function loadChunkPath(
- source: SourceInfo,
- chunkPath: ChunkPath
-): Promise {
- try {
- await BACKEND.loadChunk(chunkPath, source);
- } catch (error) {
- let loadReason;
- switch (source.type) {
- case SourceType.Runtime:
- loadReason = `as a runtime dependency of chunk ${source.chunkPath}`;
- break;
- case SourceType.Parent:
- loadReason = `from module ${source.parentId}`;
- break;
- case SourceType.Update:
- loadReason = "from an HMR update";
- break;
- default:
- invariant(source, (source) => `Unknown source type: ${source?.type}`);
- }
- throw new Error(
- `Failed to load chunk ${chunkPath} ${loadReason}${
- error ? `: ${error}` : ""
- }`,
- error
- ? {
- cause: error,
- }
- : undefined
- );
+ return module;
}
-}
-/**
- * Returns an absolute url to an asset.
- */
-function createResolvePathFromModule(
- resolver: (moduleId: string) => Exports
-): (moduleId: string) => string {
- return function resolvePathFromModule(moduleId: string): string {
- const exported = resolver(moduleId);
- return exported?.default ?? exported;
- };
-}
+ return instantiateModule(id, {
+ type: SourceType.Parent,
+ parentId: sourceModule.id,
+ });
+};
+// @ts-ignore Defined in `runtime-base.ts`
function instantiateModule(id: ModuleId, source: SourceInfo): Module {
const moduleFactory = moduleFactories[id];
if (typeof moduleFactory !== "function") {
@@ -335,7 +182,7 @@ function instantiateModule(id: ModuleId, source: SourceInfo): Module {
invariant(source, (source) => `Unknown source type: ${source?.type}`);
}
- const module: Module = {
+ const module: HotModule = {
exports: {},
error: undefined,
loaded: false,
@@ -346,7 +193,7 @@ function instantiateModule(id: ModuleId, source: SourceInfo): Module {
hot,
};
- moduleCache[id] = module;
+ devModuleCache[id] = module;
moduleHotState.set(module, hotState);
// NOTE(alexkirsz) This can fail when the module encounters a runtime error.
@@ -369,7 +216,7 @@ function instantiateModule(id: ModuleId, source: SourceInfo): Module {
v: exportValue.bind(null, module),
n: exportNamespace.bind(null, module),
m: module,
- c: moduleCache,
+ c: devModuleCache,
M: moduleFactories,
l: loadChunk.bind(null, sourceInfo),
w: loadWebAssembly.bind(null, sourceInfo),
@@ -399,20 +246,6 @@ function instantiateModule(id: ModuleId, source: SourceInfo): Module {
return module;
}
-/**
- * no-op for browser
- * @param modulePath
- */
-function resolveAbsolutePath(modulePath?: string): string {
- return `/ROOT/${modulePath ?? ""}`;
-}
-
-function getWorkerBlobURL(chunks: ChunkPath[]): string {
- let bootstrap = `TURBOPACK_WORKER_LOCATION = ${JSON.stringify(location.origin)};importScripts(${chunks.map(c => (`TURBOPACK_WORKER_LOCATION + ${JSON.stringify(getChunkRelativeUrl(c))}`)).join(", ")});`;
- let blob = new Blob([bootstrap], { type: "text/javascript" });
- return URL.createObjectURL(blob);
-}
-
/**
* NOTE(alexkirsz) Webpack has a "module execution" interception hook that
* Next.js' React Refresh runtime hooks into to add module context to the
@@ -441,44 +274,11 @@ function runModuleExecutionHooks(
}
}
-/**
- * Retrieves a module from the cache, or instantiate it if it is not cached.
- */
-const getOrInstantiateModuleFromParent: GetOrInstantiateModuleFromParent = (
- id,
- sourceModule
-) => {
- if (!sourceModule.hot.active) {
- console.warn(
- `Unexpected import of module ${id} from module ${sourceModule.id}, which was deleted by an HMR update`
- );
- }
-
- const module = moduleCache[id];
-
- if (sourceModule.children.indexOf(id) === -1) {
- sourceModule.children.push(id);
- }
-
- if (module) {
- if (module.parents.indexOf(sourceModule.id) === -1) {
- module.parents.push(sourceModule.id);
- }
-
- return module;
- }
-
- return instantiateModule(id, {
- type: SourceType.Parent,
- parentId: sourceModule.id,
- });
-};
-
/**
* This is adapted from https://github.com/vercel/next.js/blob/3466862d9dc9c8bb3131712134d38757b918d1c0/packages/react-refresh-utils/internal/ReactRefreshModule.runtime.ts
*/
function registerExportsAndSetupBoundaryForReactRefresh(
- module: Module,
+ module: HotModule,
helpers: RefreshHelpers
) {
const currentExports = module.exports;
@@ -600,9 +400,9 @@ function computedInvalidatedModules(
function computeOutdatedSelfAcceptedModules(
outdatedModules: Iterable
): { moduleId: ModuleId; errorHandler: true | Function }[] {
- const outdatedSelfAcceptedModules = [];
+ const outdatedSelfAcceptedModules: { moduleId: ModuleId; errorHandler: true | Function }[] = [];
for (const moduleId of outdatedModules) {
- const module = moduleCache[moduleId];
+ const module = devModuleCache[moduleId];
const hotState = moduleHotState.get(module)!;
if (module && hotState.selfAccepted && !hotState.selfInvalidated) {
outdatedSelfAcceptedModules.push({
@@ -657,9 +457,9 @@ function disposePhase(
// We also want to keep track of previous parents of the outdated modules.
const outdatedModuleParents = new Map();
for (const moduleId of outdatedModules) {
- const oldModule = moduleCache[moduleId];
+ const oldModule = devModuleCache[moduleId];
outdatedModuleParents.set(moduleId, oldModule?.parents);
- delete moduleCache[moduleId];
+ delete devModuleCache[moduleId];
}
// TODO(alexkirsz) Dependencies: remove outdated dependency from module
@@ -674,15 +474,15 @@ function disposePhase(
* Returns the persistent hot data that should be kept for the next module
* instance.
*
- * NOTE: mode = "replace" will not remove modules from the moduleCache.
+ * NOTE: mode = "replace" will not remove modules from the devModuleCache
* This must be done in a separate step afterwards.
* This is important because all modules need to be disposed to update the
- * parent/child relationships before they are actually removed from the moduleCache.
+ * parent/child relationships before they are actually removed from the devModuleCache.
* If this was done in this method, the following disposeModule calls won't find
* the module from the module id in the cache.
*/
function disposeModule(moduleId: ModuleId, mode: "clear" | "replace") {
- const module = moduleCache[moduleId];
+ const module = devModuleCache[moduleId];
if (!module) {
return;
}
@@ -708,7 +508,7 @@ function disposeModule(moduleId: ModuleId, mode: "clear" | "replace") {
// It will be added back once the module re-instantiates and imports its
// children again.
for (const childId of module.children) {
- const child = moduleCache[childId];
+ const child = devModuleCache[childId];
if (!child) {
continue;
}
@@ -721,7 +521,7 @@ function disposeModule(moduleId: ModuleId, mode: "clear" | "replace") {
switch (mode) {
case "clear":
- delete moduleCache[module.id];
+ delete devModuleCache[module.id];
moduleHotData.delete(module.id);
break;
case "replace":
@@ -760,7 +560,7 @@ function applyPhase(
} catch (err) {
if (typeof errorHandler === "function") {
try {
- errorHandler(err, { moduleId, module: moduleCache[moduleId] });
+ errorHandler(err, { moduleId, module: devModuleCache[moduleId] });
} catch (err2) {
reportError(err2);
reportError(err);
@@ -802,10 +602,10 @@ function applyChunkListUpdate(update: ChunkListUpdate) {
BACKEND.loadChunk(chunkPath, { type: SourceType.Update });
break;
case "total":
- BACKEND.reloadChunk?.(chunkPath);
+ DEV_BACKEND.reloadChunk?.(chunkPath);
break;
case "deleted":
- BACKEND.unloadChunk?.(chunkPath);
+ DEV_BACKEND.unloadChunk?.(chunkPath);
break;
case "partial":
invariant(
@@ -1019,7 +819,7 @@ function getAffectedModuleEffects(moduleId: ModuleId): ModuleEffect {
};
}
- const module = moduleCache[moduleId];
+ const module = devModuleCache[moduleId];
const hotState = moduleHotState.get(module)!;
if (
@@ -1049,7 +849,7 @@ function getAffectedModuleEffects(moduleId: ModuleId): ModuleEffect {
}
for (const parentId of module.parents) {
- const parent = moduleCache[parentId];
+ const parent = devModuleCache[parentId];
if (!parent) {
// TODO(alexkirsz) Is this even possible?
@@ -1084,7 +884,7 @@ function handleApply(chunkListPath: ChunkPath, update: ServerMessage) {
// This indicates that there is no way to apply the update to the
// current state of the application, and that the application must be
// restarted.
- BACKEND.restart();
+ DEV_BACKEND.restart();
break;
}
case "notFound": {
@@ -1093,7 +893,7 @@ function handleApply(chunkListPath: ChunkPath, update: ServerMessage) {
// If it is a dynamic import, we simply discard all modules that the chunk has exclusive access to.
// If it is a runtime chunk list, we restart the application.
if (runtimeChunkLists.has(chunkListPath)) {
- BACKEND.restart();
+ DEV_BACKEND.restart();
} else {
disposeChunkList(chunkListPath);
}
@@ -1184,41 +984,6 @@ function createModuleHot(
return { hot, hotState };
}
-/**
- * Adds a module to a chunk.
- */
-function addModuleToChunk(moduleId: ModuleId, chunkPath: ChunkPath) {
- let moduleChunks = moduleChunksMap.get(moduleId);
- if (!moduleChunks) {
- moduleChunks = new Set([chunkPath]);
- moduleChunksMap.set(moduleId, moduleChunks);
- } else {
- moduleChunks.add(chunkPath);
- }
-
- let chunkModules = chunkModulesMap.get(chunkPath);
- if (!chunkModules) {
- chunkModules = new Set([moduleId]);
- chunkModulesMap.set(chunkPath, chunkModules);
- } else {
- chunkModules.add(moduleId);
- }
-}
-
-/**
- * Returns the first chunk that included a module.
- * This is used by the Node.js backend, hence why it's marked as unused in this
- * file.
- */
-function getFirstModuleChunk(moduleId: ModuleId) {
- const moduleChunkPaths = moduleChunksMap.get(moduleId);
- if (moduleChunkPaths == null) {
- return null;
- }
-
- return moduleChunkPaths.values().next().value;
-}
-
/**
* Removes a module from a chunk.
* Returns `true` if there are no remaining chunks including this module.
@@ -1268,7 +1033,7 @@ function disposeChunkList(chunkListPath: ChunkPath): boolean {
// We must also dispose of the chunk list's chunk itself to ensure it may
// be reloaded properly in the future.
- BACKEND.unloadChunk?.(chunkListPath);
+ DEV_BACKEND.unloadChunk?.(chunkListPath);
return true;
}
@@ -1281,7 +1046,7 @@ function disposeChunkList(chunkListPath: ChunkPath): boolean {
function disposeChunk(chunkPath: ChunkPath): boolean {
// This should happen whether the chunk has any modules in it or not.
// For instance, CSS chunks have no modules in them, but they still need to be unloaded.
- BACKEND.unloadChunk?.(chunkPath);
+ DEV_BACKEND.unloadChunk?.(chunkPath);
const chunkModules = chunkModulesMap.get(chunkPath);
if (chunkModules == null) {
@@ -1304,43 +1069,6 @@ function disposeChunk(chunkPath: ChunkPath): boolean {
return true;
}
-/**
- * Instantiates a runtime module.
- */
-function instantiateRuntimeModule(
- moduleId: ModuleId,
- chunkPath: ChunkPath
-): Module {
- return instantiateModule(moduleId, { type: SourceType.Runtime, chunkPath });
-}
-
-/**
- * Gets or instantiates a runtime module.
- */
-function getOrInstantiateRuntimeModule(
- moduleId: ModuleId,
- chunkPath: ChunkPath
-): Module {
- const module = moduleCache[moduleId];
- if (module) {
- if (module.error) {
- throw module.error;
- }
- return module;
- }
-
- return instantiateModule(moduleId, { type: SourceType.Runtime, chunkPath });
-}
-
-/**
- * Returns the URL relative to the origin where a chunk can be fetched from.
- */
-function getChunkRelativeUrl(chunkPath: ChunkPath): string {
- return `${CHUNK_BASE_PATH}${chunkPath
- .split("/")
- .map((p) => encodeURIComponent(p))
- .join("/")}`;
-}
/**
* Subscribes to chunk list updates from the update server and applies them.
@@ -1372,30 +1100,6 @@ function registerChunkList(
}
}
-/**
- * Marks a chunk list as a runtime chunk list. There can be more than one
- * runtime chunk list. For instance, integration tests can have multiple chunk
- * groups loaded at runtime, each with its own chunk list.
- */
-function markChunkListAsRuntime(chunkListPath: ChunkPath) {
- runtimeChunkLists.add(chunkListPath);
-}
-
-function registerChunk([
- chunkPath,
- chunkModules,
- runtimeParams,
-]: ChunkRegistration) {
- for (const [moduleId, moduleFactory] of Object.entries(chunkModules)) {
- if (!moduleFactories[moduleId]) {
- moduleFactories[moduleId] = moduleFactory;
- }
- addModuleToChunk(moduleId, chunkPath);
- }
-
- return BACKEND.registerChunk(chunkPath, runtimeParams);
-}
-
globalThis.TURBOPACK_CHUNK_UPDATE_LISTENERS ??= [];
const chunkListsToRegister = globalThis.TURBOPACK_CHUNK_LISTS;
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-dummy.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-dummy.ts
new file mode 100644
index 0000000000000..0f88f5c15b3a4
--- /dev/null
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-dummy.ts
@@ -0,0 +1,17 @@
+/**
+ * This file acts as a dummy implementor for the interface that
+ * `runtime-base.ts` expects to be available in the global scope.
+ *
+ * This interface will be implemented by runtime backends.
+ */
+
+/* eslint-disable @typescript-eslint/no-unused-vars */
+
+declare var DEV_BACKEND: DevRuntimeBackend;
+declare var _eval: (code: EcmascriptModuleEntry) => any;
+/**
+ * Adds additional properties to the `TurbopackDevBaseContext` interface.
+ */
+declare var augmentContext: (
+ context: TurbopackDevBaseContext
+) => TurbopackDevContext;
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/extensions.d.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-extensions.ts
similarity index 83%
rename from turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/extensions.d.ts
rename to turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-extensions.ts
index a6f011eb863b2..5ad1ee7b9b096 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/extensions.d.ts
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-extensions.ts
@@ -1,3 +1,5 @@
+///
+
/**
* Extensions to the shared runtime types that are specific to the development
* runtime (e.g. `module.hot`).
@@ -13,6 +15,8 @@ interface HotData {
prevExports?: Exports;
}
+// Used through reference comments
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface HotState {
selfAccepted: boolean | Function;
selfDeclined: boolean;
@@ -60,6 +64,8 @@ interface Hot {
check: (autoApply: boolean) => Promise;
}
-interface Module {
+// Used through reference comments
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+interface HotModule extends ModuleWithDirection {
hot: Hot;
}
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/globals.d.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-globals.d.ts
similarity index 67%
rename from turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/globals.d.ts
rename to turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-globals.d.ts
index eae4e2323ee6c..97a17fee8b8aa 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/globals.d.ts
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-globals.d.ts
@@ -7,20 +7,10 @@
type UpdateCallback = (update: ServerMessage) => void;
-type ChunkRegistry = {
- push: (registration: ChunkRegistration) => void;
-};
-
-type ChunkListProvider = {
- push: (registration: ChunkList) => void;
-};
-
type ChunkUpdateProvider = {
push: (registration: [ChunkPath, UpdateCallback]) => void;
};
-declare var TURBOPACK: ChunkRegistry | ChunkRegistration[] | undefined;
-declare var TURBOPACK_CHUNK_LISTS: ChunkListProvider | ChunkList[] | undefined;
declare var TURBOPACK_CHUNK_UPDATE_LISTENERS:
| ChunkUpdateProvider
| [ChunkPath, UpdateCallback][]
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/protocol.d.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-protocol.d.ts
similarity index 96%
rename from turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/protocol.d.ts
rename to turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-protocol.d.ts
index 2123ac8e8e888..b683e3cf73c2f 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/protocol.d.ts
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-protocol.d.ts
@@ -9,6 +9,9 @@ type PartialServerMessage = {
instruction: PartialUpdate;
};
+// string encoding of a module factory (used in hmr updates)
+type ModuleFactoryString = string;
+
type ServerMessage = {
resource: ResourceIdentifier;
issues: Issue[];
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/dummy.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dummy.ts
similarity index 66%
rename from turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/dummy.ts
rename to turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dummy.ts
index f839299329602..fd5e03937a0f3 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/dummy.ts
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dummy.ts
@@ -7,16 +7,10 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-///
+///
+///
declare var BACKEND: RuntimeBackend;
-declare var _eval: (code: EcmascriptModuleEntry) => any;
-/**
- * Adds additional properties to the `TurbopackDevBaseContext` interface.
- */
-declare var augmentContext: (
- context: TurbopackDevBaseContext
-) => TurbopackDevContext;
declare var loadWebAssembly: (
source: SourceInfo,
wasmChunkPath: ChunkPath,
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/globals.d.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/globals.d.ts
new file mode 100644
index 0000000000000..7699194a40e35
--- /dev/null
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/globals.d.ts
@@ -0,0 +1,11 @@
+type ChunkRegistry = {
+ push: (registration: ChunkRegistration) => void;
+};
+
+type ChunkListProvider = {
+ push: (registration: ChunkList) => void;
+};
+
+
+declare var TURBOPACK: ChunkRegistry | ChunkRegistration[] | undefined;
+declare var TURBOPACK_CHUNK_LISTS: ChunkListProvider | ChunkList[] | undefined;
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/runtime-base.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/runtime-base.ts
new file mode 100644
index 0000000000000..36adb88621566
--- /dev/null
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/runtime-base.ts
@@ -0,0 +1,324 @@
+/**
+ * This file contains runtime types and functions that are shared between all
+ * Turbopack *development* ECMAScript runtimes.
+ *
+ * It will be appended to the runtime code of each runtime right after the
+ * shared runtime utils.
+ */
+
+/* eslint-disable @typescript-eslint/no-unused-vars */
+
+///
+///
+
+declare var TURBOPACK_WORKER_LOCATION: string;
+declare var CHUNK_BASE_PATH: string;
+declare function instantiateModule(id: ModuleId, source: SourceInfo): Module;
+
+type RuntimeParams = {
+ otherChunks: ChunkData[];
+ runtimeModuleIds: ModuleId[];
+};
+
+type ChunkRegistration = [
+ chunkPath: ChunkPath,
+ chunkModules: ModuleFactories,
+ params: RuntimeParams | undefined
+];
+
+type ChunkList = {
+ path: ChunkPath;
+ chunks: ChunkData[];
+ source: "entry" | "dynamic";
+};
+
+enum SourceType {
+ /**
+ * The module was instantiated because it was included in an evaluated chunk's
+ * runtime.
+ */
+ Runtime = 0,
+ /**
+ * The module was instantiated because a parent module imported it.
+ */
+ Parent = 1,
+ /**
+ * The module was instantiated because it was included in a chunk's hot module
+ * update.
+ */
+ Update = 2,
+}
+
+type SourceInfo =
+ | {
+ type: SourceType.Runtime;
+ chunkPath: ChunkPath;
+ }
+ | {
+ type: SourceType.Parent;
+ parentId: ModuleId;
+ }
+ | {
+ type: SourceType.Update;
+ parents?: ModuleId[];
+ };
+
+interface RuntimeBackend {
+ registerChunk: (chunkPath: ChunkPath, params?: RuntimeParams) => void;
+ loadChunk: (chunkPath: ChunkPath, source: SourceInfo) => Promise;
+}
+
+interface DevRuntimeBackend {
+ reloadChunk?: (chunkPath: ChunkPath) => Promise;
+ unloadChunk?: (chunkPath: ChunkPath) => void;
+ restart: () => void;
+}
+
+const moduleFactories: ModuleFactories = Object.create(null);
+/**
+ * Module IDs that are instantiated as part of the runtime of a chunk.
+ */
+const runtimeModules: Set = new Set();
+/**
+ * Map from module ID to the chunks that contain this module.
+ *
+ * In HMR, we need to keep track of which modules are contained in which so
+ * chunks. This is so we don't eagerly dispose of a module when it is removed
+ * from chunk A, but still exists in chunk B.
+ */
+const moduleChunksMap: Map> = new Map();
+/**
+ * Map from a chunk path to all modules it contains.
+ */
+const chunkModulesMap: Map> = new Map();
+/**
+ * Chunk lists that contain a runtime. When these chunk lists receive an update
+ * that can't be reconciled with the current state of the page, we need to
+ * reload the runtime entirely.
+ */
+const runtimeChunkLists: Set = new Set();
+/**
+ * Map from a chunk list to the chunk paths it contains.
+ */
+const chunkListChunksMap: Map> = new Map();
+/**
+ * Map from a chunk path to the chunk lists it belongs to.
+ */
+const chunkChunkListsMap: Map> = new Map();
+
+const availableModules: Map | true> = new Map();
+
+const availableModuleChunks: Map | true> = new Map();
+
+async function loadChunk(
+ source: SourceInfo,
+ chunkData: ChunkData
+): Promise {
+ if (typeof chunkData === "string") {
+ return loadChunkPath(source, chunkData);
+ }
+
+ const includedList = chunkData.included || [];
+ const modulesPromises = includedList.map((included) => {
+ if (moduleFactories[included]) return true;
+ return availableModules.get(included);
+ });
+ if (modulesPromises.length > 0 && modulesPromises.every((p) => p)) {
+ // When all included items are already loaded or loading, we can skip loading ourselves
+ return Promise.all(modulesPromises);
+ }
+
+ const includedModuleChunksList = chunkData.moduleChunks || [];
+ const moduleChunksPromises = includedModuleChunksList
+ .map((included) => {
+ // TODO(alexkirsz) Do we need this check?
+ // if (moduleFactories[included]) return true;
+ return availableModuleChunks.get(included);
+ })
+ .filter((p) => p);
+
+ let promise;
+ if (moduleChunksPromises.length > 0) {
+ // Some module chunks are already loaded or loading.
+
+ if (moduleChunksPromises.length === includedModuleChunksList.length) {
+ // When all included module chunks are already loaded or loading, we can skip loading ourselves
+ return Promise.all(moduleChunksPromises);
+ }
+
+ const moduleChunksToLoad: Set = new Set();
+ for (const moduleChunk of includedModuleChunksList) {
+ if (!availableModuleChunks.has(moduleChunk)) {
+ moduleChunksToLoad.add(moduleChunk);
+ }
+ }
+
+ for (const moduleChunkToLoad of moduleChunksToLoad) {
+ const promise = loadChunkPath(source, moduleChunkToLoad);
+
+ availableModuleChunks.set(moduleChunkToLoad, promise);
+
+ moduleChunksPromises.push(promise);
+ }
+
+ promise = Promise.all(moduleChunksPromises);
+ } else {
+ promise = loadChunkPath(source, chunkData.path);
+
+ // Mark all included module chunks as loading if they are not already loaded or loading.
+ for (const includedModuleChunk of includedModuleChunksList) {
+ if (!availableModuleChunks.has(includedModuleChunk)) {
+ availableModuleChunks.set(includedModuleChunk, promise);
+ }
+ }
+ }
+
+ for (const included of includedList) {
+ if (!availableModules.has(included)) {
+ // It might be better to race old and new promises, but it's rare that the new promise will be faster than a request started earlier.
+ // In production it's even more rare, because the chunk optimization tries to deduplicate modules anyway.
+ availableModules.set(included, promise);
+ }
+ }
+
+ return promise;
+}
+
+async function loadChunkPath(
+ source: SourceInfo,
+ chunkPath: ChunkPath
+): Promise {
+ try {
+ await BACKEND.loadChunk(chunkPath, source);
+ } catch (error) {
+ let loadReason;
+ switch (source.type) {
+ case SourceType.Runtime:
+ loadReason = `as a runtime dependency of chunk ${source.chunkPath}`;
+ break;
+ case SourceType.Parent:
+ loadReason = `from module ${source.parentId}`;
+ break;
+ case SourceType.Update:
+ loadReason = "from an HMR update";
+ break;
+ default:
+ invariant(source, (source) => `Unknown source type: ${source?.type}`);
+ }
+ throw new Error(
+ `Failed to load chunk ${chunkPath} ${loadReason}${
+ error ? `: ${error}` : ""
+ }`,
+ error
+ ? {
+ cause: error,
+ }
+ : undefined
+ );
+ }
+}
+
+/**
+ * Returns an absolute url to an asset.
+ */
+function createResolvePathFromModule(
+ resolver: (moduleId: string) => Exports
+): (moduleId: string) => string {
+ return function resolvePathFromModule(moduleId: string): string {
+ const exported = resolver(moduleId);
+ return exported?.default ?? exported;
+ };
+}
+
+/**
+ * no-op for browser
+ * @param modulePath
+ */
+function resolveAbsolutePath(modulePath?: string): string {
+ return `/ROOT/${modulePath ?? ""}`;
+}
+
+function getWorkerBlobURL(chunks: ChunkPath[]): string {
+ let bootstrap = `TURBOPACK_WORKER_LOCATION = ${JSON.stringify(location.origin)};importScripts(${chunks.map(c => (`TURBOPACK_WORKER_LOCATION + ${JSON.stringify(getChunkRelativeUrl(c))}`)).join(", ")});`;
+ let blob = new Blob([bootstrap], { type: "text/javascript" });
+ return URL.createObjectURL(blob);
+}
+
+/**
+ * Adds a module to a chunk.
+ */
+function addModuleToChunk(moduleId: ModuleId, chunkPath: ChunkPath) {
+ let moduleChunks = moduleChunksMap.get(moduleId);
+ if (!moduleChunks) {
+ moduleChunks = new Set([chunkPath]);
+ moduleChunksMap.set(moduleId, moduleChunks);
+ } else {
+ moduleChunks.add(chunkPath);
+ }
+
+ let chunkModules = chunkModulesMap.get(chunkPath);
+ if (!chunkModules) {
+ chunkModules = new Set([moduleId]);
+ chunkModulesMap.set(chunkPath, chunkModules);
+ } else {
+ chunkModules.add(moduleId);
+ }
+}
+
+/**
+ * Returns the first chunk that included a module.
+ * This is used by the Node.js backend, hence why it's marked as unused in this
+ * file.
+ */
+function getFirstModuleChunk(moduleId: ModuleId) {
+ const moduleChunkPaths = moduleChunksMap.get(moduleId);
+ if (moduleChunkPaths == null) {
+ return null;
+ }
+
+ return moduleChunkPaths.values().next().value;
+}
+
+/**
+ * Instantiates a runtime module.
+ */
+function instantiateRuntimeModule(
+ moduleId: ModuleId,
+ chunkPath: ChunkPath
+): Module {
+ return instantiateModule(moduleId, { type: SourceType.Runtime, chunkPath });
+}
+
+/**
+ * Returns the URL relative to the origin where a chunk can be fetched from.
+ */
+function getChunkRelativeUrl(chunkPath: ChunkPath): string {
+ return `${CHUNK_BASE_PATH}${chunkPath
+ .split("/")
+ .map((p) => encodeURIComponent(p))
+ .join("/")}`;
+}
+
+/**
+ * Marks a chunk list as a runtime chunk list. There can be more than one
+ * runtime chunk list. For instance, integration tests can have multiple chunk
+ * groups loaded at runtime, each with its own chunk list.
+ */
+function markChunkListAsRuntime(chunkListPath: ChunkPath) {
+ runtimeChunkLists.add(chunkListPath);
+}
+
+function registerChunk([
+ chunkPath,
+ chunkModules,
+ runtimeParams,
+]: ChunkRegistration) {
+ for (const [moduleId, moduleFactory] of Object.entries(chunkModules)) {
+ if (!moduleFactories[moduleId]) {
+ moduleFactories[moduleId] = moduleFactory;
+ }
+ addModuleToChunk(moduleId, chunkPath);
+ }
+
+ return BACKEND.registerChunk(chunkPath, runtimeParams);
+}
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/tsconfig.json b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/tsconfig.json
similarity index 78%
rename from turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/tsconfig.json
rename to turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/tsconfig.json
index cc70058649bb6..5aa7968522993 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/base/tsconfig.json
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/tsconfig.json
@@ -1,5 +1,5 @@
{
- "extends": "../../../../tsconfig.base.json",
+ "extends": "../../../tsconfig.base.json",
"compilerOptions": {
// environment, we need WebWorker for WebAssembly types
"lib": ["ESNext", "WebWorker"]
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/dev-backend-dom.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/dev-backend-dom.ts
new file mode 100644
index 0000000000000..7ce31930e4ab1
--- /dev/null
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/dev-backend-dom.ts
@@ -0,0 +1,130 @@
+/**
+ * This file contains the runtime code specific to the Turbopack development
+ * ECMAScript DOM runtime.
+ *
+ * It will be appended to the base development runtime code.
+ */
+
+/* eslint-disable @typescript-eslint/no-unused-vars */
+
+///
+///
+///
+///
+
+let DEV_BACKEND: DevRuntimeBackend;
+
+(() => {
+ DEV_BACKEND = {
+ unloadChunk(chunkPath) {
+ deleteResolver(chunkPath);
+
+ const chunkUrl = getChunkRelativeUrl(chunkPath);
+ // TODO(PACK-2140): remove this once all filenames are guaranteed to be escaped.
+ const decodedChunkUrl = decodeURI(chunkUrl);
+
+ if (chunkPath.endsWith(".css")) {
+ const links = document.querySelectorAll(
+ `link[href="${chunkUrl}"],link[href^="${chunkUrl}?"],link[href="${decodedChunkUrl}"],link[href^="${decodedChunkUrl}?"]`
+ );
+ for (const link of Array.from(links)) {
+ link.remove();
+ }
+ } else if (chunkPath.endsWith(".js")) {
+ // Unloading a JS chunk would have no effect, as it lives in the JS
+ // runtime once evaluated.
+ // However, we still want to remove the script tag from the DOM to keep
+ // the HTML somewhat consistent from the user's perspective.
+ const scripts = document.querySelectorAll(
+ `script[src="${chunkUrl}"],script[src^="${chunkUrl}?"],script[src="${decodedChunkUrl}"],script[src^="${decodedChunkUrl}?"]`
+ );
+ for (const script of Array.from(scripts)) {
+ script.remove();
+ }
+ } else {
+ throw new Error(`can't infer type of chunk from path ${chunkPath}`);
+ }
+ },
+
+ reloadChunk(chunkPath) {
+ return new Promise((resolve, reject) => {
+ if (!chunkPath.endsWith(".css")) {
+ reject(new Error("The DOM backend can only reload CSS chunks"));
+ return;
+ }
+
+ const chunkUrl = getChunkRelativeUrl(chunkPath);
+ const decodedChunkUrl = decodeURI(chunkUrl);
+
+ const previousLinks = document.querySelectorAll(
+ `link[rel=stylesheet][href="${chunkUrl}"],link[rel=stylesheet][href^="${chunkUrl}?"],link[rel=stylesheet][href="${decodedChunkUrl}"],link[rel=stylesheet][href^="${decodedChunkUrl}?"]`
+ );
+
+ if (previousLinks.length === 0) {
+ reject(new Error(`No link element found for chunk ${chunkPath}`));
+ return;
+ }
+
+ const link = document.createElement("link");
+ link.rel = "stylesheet";
+
+ if (navigator.userAgent.includes("Firefox")) {
+ // Firefox won't reload CSS files that were previously loaded on the current page,
+ // we need to add a query param to make sure CSS is actually reloaded from the server.
+ //
+ // I believe this is this issue: https://bugzilla.mozilla.org/show_bug.cgi?id=1037506
+ //
+ // Safari has a similar issue, but only if you have a `` tag
+ // pointing to the same URL as the stylesheet: https://bugs.webkit.org/show_bug.cgi?id=187726
+ link.href = `${chunkUrl}?ts=${Date.now()}`;
+ } else {
+ link.href = chunkUrl;
+ }
+
+ link.onerror = () => {
+ reject();
+ };
+ link.onload = () => {
+ // First load the new CSS, then remove the old ones. This prevents visible
+ // flickering that would happen in-between removing the previous CSS and
+ // loading the new one.
+ for (const previousLink of Array.from(previousLinks))
+ previousLink.remove();
+
+ // CSS chunks do not register themselves, and as such must be marked as
+ // loaded instantly.
+ resolve();
+ };
+
+ // Make sure to insert the new CSS right after the previous one, so that
+ // its precedence is higher.
+ previousLinks[0].parentElement!.insertBefore(
+ link,
+ previousLinks[0].nextSibling
+ );
+ });
+ },
+
+ restart: () => self.location.reload(),
+ };
+
+ function deleteResolver(chunkPath: ChunkPath) {
+ chunkResolvers.delete(chunkPath);
+ }
+})();
+
+function _eval({ code, url, map }: EcmascriptModuleEntry): ModuleFactory {
+ code += `\n\n//# sourceURL=${encodeURI(
+ location.origin + CHUNK_BASE_PATH + url
+ )}`;
+ if (map) {
+ code += `\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${btoa(
+ // btoa doesn't handle nonlatin characters, so escape them as \x sequences
+ // See https://stackoverflow.com/a/26603875
+ unescape(encodeURIComponent(map))
+ )}`;
+ }
+
+ // eslint-disable-next-line no-eval
+ return eval(code);
+}
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/dom/runtime-backend-dom.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/runtime-backend-dom.ts
similarity index 57%
rename from turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/dom/runtime-backend-dom.ts
rename to turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/runtime-backend-dom.ts
index 21dfa240f74de..9e59a8fb71050 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/dom/runtime-backend-dom.ts
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/runtime-backend-dom.ts
@@ -7,8 +7,8 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-///
-///
+///
+///
type ChunkResolver = {
resolved: boolean;
@@ -19,7 +19,7 @@ type ChunkResolver = {
let BACKEND: RuntimeBackend;
-function augmentContext(context: TurbopackDevBaseContext): TurbopackDevContext {
+function augmentContext(context: unknown): unknown {
return context;
}
@@ -28,7 +28,7 @@ function fetchWebAssembly(wasmChunkPath: ChunkPath) {
}
async function loadWebAssembly(
- _source: SourceInfo,
+ _source: unknown,
wasmChunkPath: ChunkPath,
importsObj: WebAssembly.Imports
): Promise {
@@ -40,7 +40,7 @@ async function loadWebAssembly(
}
async function loadWebAssemblyModule(
- _source: SourceInfo,
+ _source: unknown,
wasmChunkPath: ChunkPath
): Promise {
const req = fetchWebAssembly(wasmChunkPath);
@@ -48,6 +48,11 @@ async function loadWebAssemblyModule(
return await WebAssembly.compileStreaming(req);
}
+/**
+ * Maps chunk paths to the corresponding resolver.
+ */
+const chunkResolvers: Map = new Map();
+
(() => {
BACKEND = {
async registerChunk(chunkPath, params) {
@@ -81,104 +86,8 @@ async function loadWebAssemblyModule(
loadChunk(chunkPath, source) {
return doLoadChunk(chunkPath, source);
},
-
- unloadChunk(chunkPath) {
- deleteResolver(chunkPath);
-
- const chunkUrl = getChunkRelativeUrl(chunkPath);
- // TODO(PACK-2140): remove this once all filenames are guaranteed to be escaped.
- const decodedChunkUrl = decodeURI(chunkUrl);
-
- if (chunkPath.endsWith(".css")) {
- const links = document.querySelectorAll(
- `link[href="${chunkUrl}"],link[href^="${chunkUrl}?"],link[href="${decodedChunkUrl}"],link[href^="${decodedChunkUrl}?"]`
- );
- for (const link of Array.from(links)) {
- link.remove();
- }
- } else if (chunkPath.endsWith(".js")) {
- // Unloading a JS chunk would have no effect, as it lives in the JS
- // runtime once evaluated.
- // However, we still want to remove the script tag from the DOM to keep
- // the HTML somewhat consistent from the user's perspective.
- const scripts = document.querySelectorAll(
- `script[src="${chunkUrl}"],script[src^="${chunkUrl}?"],script[src="${decodedChunkUrl}"],script[src^="${decodedChunkUrl}?"]`
- );
- for (const script of Array.from(scripts)) {
- script.remove();
- }
- } else {
- throw new Error(`can't infer type of chunk from path ${chunkPath}`);
- }
- },
-
- reloadChunk(chunkPath) {
- return new Promise((resolve, reject) => {
- if (!chunkPath.endsWith(".css")) {
- reject(new Error("The DOM backend can only reload CSS chunks"));
- return;
- }
-
- const chunkUrl = getChunkRelativeUrl(chunkPath);
- const decodedChunkUrl = decodeURI(chunkUrl);
-
- const previousLinks = document.querySelectorAll(
- `link[rel=stylesheet][href="${chunkUrl}"],link[rel=stylesheet][href^="${chunkUrl}?"],link[rel=stylesheet][href="${decodedChunkUrl}"],link[rel=stylesheet][href^="${decodedChunkUrl}?"]`
- );
-
- if (previousLinks.length === 0) {
- reject(new Error(`No link element found for chunk ${chunkPath}`));
- return;
- }
-
- const link = document.createElement("link");
- link.rel = "stylesheet";
-
- if (navigator.userAgent.includes("Firefox")) {
- // Firefox won't reload CSS files that were previously loaded on the current page,
- // we need to add a query param to make sure CSS is actually reloaded from the server.
- //
- // I believe this is this issue: https://bugzilla.mozilla.org/show_bug.cgi?id=1037506
- //
- // Safari has a similar issue, but only if you have a `` tag
- // pointing to the same URL as the stylesheet: https://bugs.webkit.org/show_bug.cgi?id=187726
- link.href = `${chunkUrl}?ts=${Date.now()}`;
- } else {
- link.href = chunkUrl;
- }
-
- link.onerror = () => {
- reject();
- };
- link.onload = () => {
- // First load the new CSS, then remove the old ones. This prevents visible
- // flickering that would happen in-between removing the previous CSS and
- // loading the new one.
- for (const previousLink of Array.from(previousLinks))
- previousLink.remove();
-
- // CSS chunks do not register themselves, and as such must be marked as
- // loaded instantly.
- resolve();
- };
-
- // Make sure to insert the new CSS right after the previous one, so that
- // its precedence is higher.
- previousLinks[0].parentElement!.insertBefore(
- link,
- previousLinks[0].nextSibling
- );
- });
- },
-
- restart: () => self.location.reload(),
};
- /**
- * Maps chunk paths to the corresponding resolver.
- */
- const chunkResolvers: Map = new Map();
-
function getOrCreateResolver(chunkPath: ChunkPath): ChunkResolver {
let resolver = chunkResolvers.get(chunkPath);
if (!resolver) {
@@ -202,10 +111,6 @@ async function loadWebAssemblyModule(
return resolver;
}
- function deleteResolver(chunkPath: ChunkPath) {
- chunkResolvers.delete(chunkPath);
- }
-
/**
* Loads the given chunk, and returns a promise that resolves once the chunk
* has been loaded.
@@ -299,19 +204,3 @@ async function loadWebAssemblyModule(
return resolver.promise;
}
})();
-
-function _eval({ code, url, map }: EcmascriptModuleEntry): ModuleFactory {
- code += `\n\n//# sourceURL=${encodeURI(
- location.origin + CHUNK_BASE_PATH + url
- )}`;
- if (map) {
- code += `\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${btoa(
- // btoa doesn't handle nonlatin characters, so escape them as \x sequences
- // See https://stackoverflow.com/a/26603875
- unescape(encodeURIComponent(map))
- )}`;
- }
-
- // eslint-disable-next-line no-eval
- return eval(code);
-}
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/dom/tsconfig.json b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/tsconfig.json
similarity index 73%
rename from turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/dom/tsconfig.json
rename to turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/tsconfig.json
index 2f29983db98d0..4908beba8e3ea 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/dom/tsconfig.json
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/tsconfig.json
@@ -1,5 +1,5 @@
{
- "extends": "../../../../tsconfig.base.json",
+ "extends": "../../../tsconfig.base.json",
"compilerOptions": {
// environment
"lib": ["ESNext", "DOM", "WebWorker.ImportScripts"]
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/edge/dev-backend-edge.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/edge/dev-backend-edge.ts
new file mode 100644
index 0000000000000..4f0c15e6598dd
--- /dev/null
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/edge/dev-backend-edge.ts
@@ -0,0 +1,24 @@
+/**
+ * This file contains the runtime code specific to the Turbopack development
+ * ECMAScript "None" runtime (e.g. for Edge).
+ *
+ * It will be appended to the base development runtime code.
+ */
+
+/* eslint-disable @typescript-eslint/no-unused-vars */
+
+///
+
+let DEV_BACKEND: DevRuntimeBackend;
+
+(() => {
+ DEV_BACKEND = {
+ restart: () => {
+ throw new Error("restart is not supported");
+ },
+ };
+})();
+
+function _eval(_: EcmascriptModuleEntry) {
+ throw new Error("HMR evaluation is not implemented on this backend");
+}
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/edge/runtime-backend-edge.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/edge/runtime-backend-edge.ts
similarity index 90%
rename from turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/edge/runtime-backend-edge.ts
rename to turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/edge/runtime-backend-edge.ts
index 232078a3bb90b..e38695fc3d488 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/edge/runtime-backend-edge.ts
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/edge/runtime-backend-edge.ts
@@ -8,8 +8,8 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
///
-///
-///
+///
+///
type ChunkRunner = {
requiredChunks: Set;
@@ -23,15 +23,16 @@ type ExternalRequire = (
id: ModuleId,
esm?: boolean
) => Exports | EsmNamespaceObject;
+
type ExternalImport = (id: ModuleId) => Promise;
-interface TurbopackDevContext extends TurbopackDevBaseContext {
+interface TurbopackEdgeContext extends TurbopackBaseContext {
x: ExternalRequire;
y: ExternalImport;
}
-function augmentContext(context: TurbopackDevBaseContext): TurbopackDevContext {
- const nodejsContext = context as TurbopackDevContext;
+function augmentContext(context: TurbopackBaseContext): TurbopackEdgeContext {
+ const nodejsContext = context as TurbopackEdgeContext;
nodejsContext.x = externalRequire;
nodejsContext.y = externalImport;
return nodejsContext;
@@ -121,10 +122,6 @@ async function loadWebAssemblyModule(
loadChunk(_chunkPath, _fromChunkPath) {
throw new Error("chunk loading is not supported");
},
-
- restart: () => {
- throw new Error("restart is not supported");
- },
};
const registeredChunks: Set = new Set();
@@ -198,7 +195,3 @@ async function loadWebAssemblyModule(
}
}
})();
-
-function _eval(_: EcmascriptModuleEntry): ModuleFactory {
- throw new Error("HMR evaluation is not implemented on this backend");
-}
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/edge/tsconfig.json b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/edge/tsconfig.json
similarity index 76%
rename from turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/edge/tsconfig.json
rename to turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/edge/tsconfig.json
index fabb2ed16dbbb..5722142375561 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/runtime/edge/tsconfig.json
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/edge/tsconfig.json
@@ -1,5 +1,5 @@
{
- "extends": "../../../../tsconfig.base.json",
+ "extends": "../../../tsconfig.base.json",
"compilerOptions": {
// environment, we need WebWorker for WebAssembly types
"lib": ["ESNext", "WebWorker"]
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/nodejs/runtime.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/nodejs/runtime.ts
index 051b9dd30916f..15ca270206cc4 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/nodejs/runtime.ts
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/nodejs/runtime.ts
@@ -41,7 +41,7 @@ function stringifySourceInfo(source: SourceInfo): string {
type ExternalRequire = (id: ModuleId) => Exports | EsmNamespaceObject;
type ExternalImport = (id: ModuleId) => Promise;
-interface TurbopackNodeBuildContext extends TurbopackBaseContext {
+interface TurbopackNodeBuildContext extends TurbopackBaseContext {
R: ResolvePathFromModule;
x: ExternalRequire;
y: ExternalImport;
@@ -57,7 +57,7 @@ const fs = require("fs/promises");
const vm = require("vm");
const moduleFactories: ModuleFactories = Object.create(null);
-const moduleCache: ModuleCache = Object.create(null);
+const moduleCache: ModuleCache = Object.create(null);
/**
* Returns an absolute path to the given module's id.
@@ -177,11 +177,11 @@ function loadWebAssemblyModule(chunkPath: ChunkPath) {
return compileWebAssemblyFromPath(resolved);
}
-function getWorkerBlobURL(_chunks: ChunkPath[]): never {
+function getWorkerBlobURL(_chunks: ChunkPath[]): string {
throw new Error("Worker blobs are not implemented yet for Node.js");
}
-function instantiateModule(id: ModuleId, source: SourceInfo): Module {
+function instantiateModule(id: ModuleId, source: SourceInfo): ModuleWithDirection {
const moduleFactory = moduleFactories[id];
if (typeof moduleFactory !== "function") {
// This can happen if modules incorrectly handle HMR disposes/updates,
@@ -217,7 +217,7 @@ function instantiateModule(id: ModuleId, source: SourceInfo): Module {
invariant(source, (source) => `Unknown source type: ${source?.type}`);
}
- const module: Module = {
+ const module: ModuleWithDirection = {
exports: {},
error: undefined,
loaded: false,
@@ -275,10 +275,11 @@ function instantiateModule(id: ModuleId, source: SourceInfo): Module {
/**
* Retrieves a module from the cache, or instantiate it if it is not cached.
*/
+// @ts-ignore
function getOrInstantiateModuleFromParent(
id: ModuleId,
- sourceModule: Module
-): Module {
+ sourceModule: ModuleWithDirection
+): ModuleWithDirection {
const module = moduleCache[id];
if (sourceModule.children.indexOf(id) === -1) {
@@ -312,6 +313,7 @@ function instantiateRuntimeModule(
/**
* Retrieves a module from the cache, or instantiate it as a runtime module if it is not cached.
*/
+// @ts-ignore TypeScript doesn't separate this module space from the browser runtime
function getOrInstantiateRuntimeModule(
moduleId: ModuleId,
chunkPath: ChunkPath
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/dummy.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/dummy.ts
index 7a6071d133166..f60125232b9a4 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/dummy.ts
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/dummy.ts
@@ -7,4 +7,7 @@
* This interface will be implemented by runtimes.
*/
-declare var getOrInstantiateModuleFromParent: GetOrInstantiateModuleFromParent;
+declare function getOrInstantiateModuleFromParent(
+ id: ModuleId,
+ sourceModule: M
+): M;
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/runtime-types.d.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/runtime-types.d.ts
index f0232ff633cd0..e4cb9900931de 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/runtime-types.d.ts
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/runtime-types.d.ts
@@ -10,6 +10,12 @@
type ChunkPath = string;
type ModuleId = string;
+interface Exports {
+ __esModule?: boolean;
+
+ [key: string]: any;
+}
+
type ChunkData =
| ChunkPath
| {
@@ -37,8 +43,8 @@ type LoadWebAssembly = (
) => Exports;
type LoadWebAssemblyModule = (wasmChunkPath: ChunkPath) => WebAssembly.Module;
-type ModuleCache = Record;
-type ModuleFactories = Record;
+type ModuleCache = Record;
+type ModuleFactories = Record;
type RelativeURL = (inputUrl: string) => void;
type ResolvePathFromModule = (moduleId: string) => string;
@@ -56,7 +62,25 @@ type AsyncModule = (
type ResolveAbsolutePath = (modulePath?: string) => string;
type GetWorkerBlobURL = (chunks: ChunkPath[]) => string;
-interface TurbopackBaseContext {
+interface Module {
+ exports: Function | Exports | Promise | AsyncModulePromise;
+ error: Error | undefined;
+ loaded: boolean;
+ id: ModuleId;
+ namespaceObject?:
+ | EsmNamespaceObject
+ | Promise
+ | AsyncModulePromise;
+ [REEXPORTED_OBJECTS]?: any[];
+}
+
+interface ModuleWithDirection extends Module {
+ children: ModuleId[];
+ parents: ModuleId[];
+}
+
+
+interface TurbopackBaseContext {
a: AsyncModule;
e: Module["exports"];
r: CommonJsRequire;
@@ -68,7 +92,7 @@ interface TurbopackBaseContext {
v: ExportValue;
n: ExportNamespace;
m: Module;
- c: ModuleCache;
+ c: ModuleCache;
M: ModuleFactories;
l: LoadChunk;
w: LoadWebAssembly;
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/runtime-utils.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/runtime-utils.ts
index f6a1ac2d744c3..61c0ceb80fd84 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/runtime-utils.ts
+++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/runtime-utils.ts
@@ -9,31 +9,15 @@
///
-interface Exports {
- __esModule?: boolean;
-
- [key: string]: any;
-}
-
type EsmNamespaceObject = Record;
-const REEXPORTED_OBJECTS = Symbol("reexported objects");
-
-interface BaseModule {
- exports: Function | Exports | Promise | AsyncModulePromise;
- error: Error | undefined;
- loaded: boolean;
- id: ModuleId;
- children: ModuleId[];
- parents: ModuleId[];
- namespaceObject?:
- | EsmNamespaceObject
- | Promise
- | AsyncModulePromise;
- [REEXPORTED_OBJECTS]?: any[];
-}
+// @ts-ignore Defined in `dev-base.ts`
+declare function getOrInstantiateModuleFromParent(
+ id: ModuleId,
+ sourceModule: M
+): M;
-interface Module extends BaseModule {}
+const REEXPORTED_OBJECTS = Symbol("reexported objects");
type ModuleContextMap = Record;
@@ -54,10 +38,12 @@ interface ModuleContext {
resolve(moduleId: ModuleId): ModuleId;
}
-type GetOrInstantiateModuleFromParent = (
+type GetOrInstantiateModuleFromParent = (
moduleId: ModuleId,
- parentModule: Module
-) => Module;
+ parentModule: M
+) => M;
+
+declare function getOrInstantiateRuntimeModule(moduleId: ModuleId, chunkPath: ChunkPath): Module;
const hasOwnProperty = Object.prototype.hasOwnProperty;
const toStringTag = typeof Symbol !== "undefined" && Symbol.toStringTag;
@@ -211,7 +197,7 @@ function interopEsm(
return ns;
}
-function createNS(raw: BaseModule["exports"]): EsmNamespaceObject {
+function createNS(raw: Module["exports"]): EsmNamespaceObject {
if (typeof raw === "function") {
return function (this: any, ...args: any[]) {
return raw.apply(this, args);
@@ -243,7 +229,9 @@ function esmImport(
// Add a simple runtime require so that environments without one can still pass
// `typeof require` CommonJS checks so that exports are correctly registered.
const runtimeRequire =
+ // @ts-ignore
typeof require === "function"
+ // @ts-ignore
? require
: function require() {
throw new Error("Unexpected use of runtime require");
diff --git a/turbopack/crates/turbopack-ecmascript-runtime/src/browser_runtime.rs b/turbopack/crates/turbopack-ecmascript-runtime/src/browser_runtime.rs
index 3ef6e5f8f01e5..4ffbb5f7f2ba8 100644
--- a/turbopack/crates/turbopack-ecmascript-runtime/src/browser_runtime.rs
+++ b/turbopack/crates/turbopack-ecmascript-runtime/src/browser_runtime.rs
@@ -2,7 +2,7 @@ use std::io::Write;
use anyhow::Result;
use indoc::writedoc;
-use turbo_tasks::{RcStr, Vc};
+use turbo_tasks::{RcStr, Value, Vc};
use turbopack_core::{
code_builder::{Code, CodeBuilder},
context::AssetContext,
@@ -10,23 +10,32 @@ use turbopack_core::{
};
use turbopack_ecmascript::utils::StringifyJs;
-use crate::{asset_context::get_runtime_asset_context, embed_js::embed_static_code};
+use crate::{asset_context::get_runtime_asset_context, embed_js::embed_static_code, RuntimeType};
-/// Returns the code for the development ECMAScript runtime.
+/// Returns the code for the ECMAScript runtime.
#[turbo_tasks::function]
pub async fn get_browser_runtime_code(
environment: Vc,
chunk_base_path: Vc