diff --git a/crates/next-core/src/mode.rs b/crates/next-core/src/mode.rs index c5a29ef486b1d8..50aa941c18c9ae 100644 --- a/crates/next-core/src/mode.rs +++ b/crates/next-core/src/mode.rs @@ -54,7 +54,7 @@ impl NextMode { pub fn minify_type(&self) -> MinifyType { match self { NextMode::Development => MinifyType::NoMinify, - NextMode::Build => MinifyType::Minify, + NextMode::Build => MinifyType::NoMinify, } } diff --git a/packages/next/src/build/swc/generated-native.d.ts b/packages/next/src/build/swc/generated-native.d.ts index 0f7888462ef24e..0c011f00a584e4 100644 --- a/packages/next/src/build/swc/generated-native.d.ts +++ b/packages/next/src/build/swc/generated-native.d.ts @@ -32,11 +32,6 @@ export class ExternalObject { [K: symbol]: T } } -export interface TransformOutput { - code: string - map?: string - output?: string -} export function mdxCompile( value: string, option: Buffer, diff --git a/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs b/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs index 57b5bc6390f8a3..313e75aa028c53 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-cli/src/build/mod.rs b/turbopack/crates/turbopack-cli/src/build/mod.rs index 635fa6f6472fd0..a81840c38a9db3 100644 --- a/turbopack/crates/turbopack-cli/src/build/mod.rs +++ b/turbopack/crates/turbopack-cli/src/build/mod.rs @@ -75,7 +75,7 @@ impl TurbopackBuildBuilder { log_level: IssueSeverity::Warning, show_all: false, log_detail: false, - minify_type: MinifyType::Minify, + minify_type: MinifyType::NoMinify, } } diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/package.json b/turbopack/crates/turbopack-ecmascript-runtime/js/package.json index e9d1421f77411b..b5ff2686751c38 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 fc011843a752ca..a8423a2d78fb10 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 00000000000000..06ff65b5a5204e --- /dev/null +++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/build-base.ts @@ -0,0 +1,155 @@ +/// + +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, +): BaseModule { + 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 (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, + }); +}; + +function instantiateModule(id: ModuleId, source: SourceInfo): BaseModule { + 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.` + ); + } + + let parents: ModuleId[]; + switch (source.type) { + case SourceType.Runtime: + runtimeModules.add(id); + parents = []; + 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`. + parents = [source.parentId]; + break; + case SourceType.Update: + parents = source.parents || []; + break; + default: + invariant(source, (source) => `Unknown source type: ${source?.type}`); + } + + const module: BaseModule = { + exports: {}, + error: undefined, + loaded: false, + id, + parents, + children: [], + 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, + { + 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 69c8019a4915b8..2b1f9f1fa2bc3f 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") { @@ -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,39 +274,6 @@ 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 */ @@ -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 @@ -682,7 +482,7 @@ function disposePhase( * 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 00000000000000..0f88f5c15b3a43 --- /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 88% 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 a6f011eb863b21..8ada7238371f06 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 @@ -13,6 +13,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 +62,8 @@ interface Hot { check: (autoApply: boolean) => Promise; } +// Used through reference comments +// eslint-disable-next-line @typescript-eslint/no-unused-vars interface Module { 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 eae4e2323ee6c5..97a17fee8b8aaf 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 2123ac8e8e8888..b683e3cf73c2f3 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 f839299329602b..fd5e03937a0f3a 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 00000000000000..7699194a40e35f --- /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 00000000000000..f73ba347073acd --- /dev/null +++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/runtime-base.ts @@ -0,0 +1,351 @@ +/** + * 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): BaseModule; + +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); +} + +// /** +// * Retrieves a module from the cache, or instantiate it if it is not cached. +// */ +// const getOrInstantiateModuleFromParent: GetOrInstantiateModuleFromParent = ( +// id, +// sourceModule +// ) => { +// 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, +// }); +// }; + +/** + * 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 cc70058649bb66..5aa79685229932 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 00000000000000..d12c1d19662de5 --- /dev/null +++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/dev-backend-dom.ts @@ -0,0 +1,134 @@ +/** + * 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; + +function augmentContext(context: TurbopackDevBaseContext): TurbopackDevContext { + return context; +} + +(() => { + 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 21dfa240f74dec..5fe58ebf5e98dc 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,16 +19,12 @@ type ChunkResolver = { let BACKEND: RuntimeBackend; -function augmentContext(context: TurbopackDevBaseContext): TurbopackDevContext { - return context; -} - function fetchWebAssembly(wasmChunkPath: ChunkPath) { return fetch(getChunkRelativeUrl(wasmChunkPath)); } async function loadWebAssembly( - _source: SourceInfo, + _source: unknown, wasmChunkPath: ChunkPath, importsObj: WebAssembly.Imports ): Promise { @@ -40,7 +36,7 @@ async function loadWebAssembly( } async function loadWebAssemblyModule( - _source: SourceInfo, + _source: unknown, wasmChunkPath: ChunkPath ): Promise { const req = fetchWebAssembly(wasmChunkPath); @@ -48,6 +44,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 +82,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 +107,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 +200,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 2f29983db98d08..4908beba8e3ea1 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 00000000000000..385601bfaa6bef --- /dev/null +++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/edge/dev-backend-edge.ts @@ -0,0 +1,46 @@ +/** + * 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; + +type ExternalRequire = ( + id: ModuleId, + esm?: boolean +) => Exports | EsmNamespaceObject; + +type ExternalImport = (id: ModuleId) => Promise; + +interface TurbopackDevContext extends TurbopackBaseContext { + x: ExternalRequire; + y: ExternalImport; +} + +function augmentContext(context: TurbopackBaseContext): TurbopackDevContext { + const nodejsContext = context as TurbopackDevContext; + nodejsContext.x = externalRequire; + nodejsContext.y = externalImport; + return nodejsContext; +} + +(() => { + 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 85% 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 232078a3bb90b8..293be35636e1df 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,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /// -/// -/// type ChunkRunner = { requiredChunks: Set; @@ -19,24 +17,6 @@ type ChunkRunner = { let BACKEND: RuntimeBackend; -type ExternalRequire = ( - id: ModuleId, - esm?: boolean -) => Exports | EsmNamespaceObject; -type ExternalImport = (id: ModuleId) => Promise; - -interface TurbopackDevContext extends TurbopackDevBaseContext { - x: ExternalRequire; - y: ExternalImport; -} - -function augmentContext(context: TurbopackDevBaseContext): TurbopackDevContext { - const nodejsContext = context as TurbopackDevContext; - nodejsContext.x = externalRequire; - nodejsContext.y = externalImport; - return nodejsContext; -} - async function loadWebAssembly( source: SourceInfo, chunkPath: ChunkPath, @@ -121,10 +101,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 +174,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 fabb2ed16dbbb9..57221423755613 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 051b9dd30916f4..61a7a387e6cffc 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,7 +177,7 @@ 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"); } @@ -275,6 +275,7 @@ 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 @@ -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 7a6071d1331668..f60125232b9a41 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 f0232ff633cd0f..f89b8a64ab0eeb 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,7 @@ type AsyncModule = ( type ResolveAbsolutePath = (modulePath?: string) => string; type GetWorkerBlobURL = (chunks: ChunkPath[]) => string; -interface TurbopackBaseContext { +interface TurbopackBaseContext { a: AsyncModule; e: Module["exports"]; r: CommonJsRequire; @@ -68,7 +74,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 f6a1ac2d744c33..5603b5494a3d1a 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,14 +9,14 @@ /// -interface Exports { - __esModule?: boolean; - - [key: string]: any; -} - type EsmNamespaceObject = Record; +// @ts-ignore Defined in `dev-base.ts` +declare function getOrInstantiateModuleFromParent( + id: ModuleId, + sourceModule: M +): M; + const REEXPORTED_OBJECTS = Symbol("reexported objects"); interface BaseModule { @@ -54,10 +54,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; @@ -243,7 +245,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 3ef6e5f8f01e5c..e54bee65c651b0 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>, + runtime_type: Value, output_root: Vc, ) -> Result> { let asset_context = get_runtime_asset_context(environment); let shared_runtime_utils_code = embed_static_code(asset_context, "shared/runtime-utils.ts".into()); - let runtime_base_code = embed_static_code( - asset_context, - "browser/dev/runtime/base/runtime-base.ts".into(), - ); + + let mut runtime_base_code = vec!["browser/runtime/base/runtime-base.ts"]; + match *runtime_type { + RuntimeType::Production => runtime_base_code.push("browser/runtime/base/build-base.ts"), + RuntimeType::Development => { + runtime_base_code.push("browser/runtime/base/dev-base.ts"); + } + #[cfg(feature = "test")] + RuntimeType::Dummy => { + panic!("This configuration is not supported in the browser runtime") + } + } let chunk_loading = &*asset_context .compile_time_info() @@ -34,17 +43,33 @@ pub async fn get_browser_runtime_code( .chunk_loading() .await?; - let runtime_backend_code = embed_static_code( - asset_context, - match chunk_loading { - ChunkLoading::Edge => "browser/dev/runtime/edge/runtime-backend-edge.ts".into(), - // This case should never be hit. - ChunkLoading::NodeJs => { - panic!("Node.js runtime is not supported in the browser runtime!") - } - ChunkLoading::Dom => "browser/dev/runtime/dom/runtime-backend-dom.ts".into(), - }, - ); + let mut runtime_backend_code = vec![]; + match (chunk_loading, *runtime_type) { + (ChunkLoading::Edge, RuntimeType::Development) => { + runtime_backend_code.push("browser/runtime/edge/runtime-backend-edge.ts"); + runtime_backend_code.push("browser/runtime/edge/dev-backend-edge.ts"); + } + (ChunkLoading::Edge, RuntimeType::Production) => { + runtime_backend_code.push("browser/runtime/edge/runtime-backend-edge.ts"); + } + // This case should never be hit. + (ChunkLoading::NodeJs, _) => { + panic!("Node.js runtime is not supported in the browser runtime!") + } + (ChunkLoading::Dom, RuntimeType::Development) => { + runtime_backend_code.push("browser/runtime/dom/runtime-backend-dom.ts"); + runtime_backend_code.push("browser/runtime/dom/dev-backend-dom.ts"); + } + (ChunkLoading::Dom, RuntimeType::Production) => { + // TODO + runtime_backend_code.push("browser/runtime/dom/runtime-backend-dom.ts"); + } + + #[cfg(feature = "test")] + (_, RuntimeType::Dummy) => { + panic!("This configuration is not supported in the browser runtime") + } + }; let mut code: CodeBuilder = CodeBuilder::default(); let output_root = output_root.await?.to_string(); @@ -69,7 +94,10 @@ pub async fn get_browser_runtime_code( )?; code.push_code(&*shared_runtime_utils_code.await?); - code.push_code(&*runtime_base_code.await?); + for runtime_code in runtime_base_code { + println!("runtime_code: {}", runtime_code); + code.push_code(&*embed_static_code(asset_context, runtime_code.into()).await?); + } if *environment.supports_commonjs_externals().await? { code.push_code( @@ -89,7 +117,9 @@ pub async fn get_browser_runtime_code( ); } - code.push_code(&*runtime_backend_code.await?); + for backend_code in runtime_backend_code { + code.push_code(&*embed_static_code(asset_context, backend_code.into()).await?); + } // Registering chunks depends on the BACKEND variable, which is set by the // specific runtime code, hence it must be appended after it.