Skip to content

Commit

Permalink
Base JS runtime for builds
Browse files Browse the repository at this point in the history
Currently we ship dev runtimes (with things like HMR logic) along with code for loading chunks. This separates them and allows us to include a minimal runtime for builds.

Test Plan: `TURBOPACK=1 TURBOPACK_BUILD=1 pnpm build` on an app with a `middleware.ts` and verified it loads when started.
  • Loading branch information
wbinnssmith committed Sep 25, 2024
1 parent 74d2136 commit 3473b85
Show file tree
Hide file tree
Showing 27 changed files with 898 additions and 590 deletions.
2 changes: 1 addition & 1 deletion crates/next-core/src/mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
5 changes: 0 additions & 5 deletions packages/next/src/build/swc/generated-native.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ export class ExternalObject<T> {
[K: symbol]: T
}
}
export interface TransformOutput {
code: string
map?: string
output?: string
}
export function mdxCompile(
value: string,
option: Buffer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?);
Expand All @@ -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?);
Expand Down
2 changes: 1 addition & 1 deletion turbopack/crates/turbopack-cli/src/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl TurbopackBuildBuilder {
log_level: IssueSeverity::Warning,
show_all: false,
log_detail: false,
minify_type: MinifyType::Minify,
minify_type: MinifyType::NoMinify,
}
}

Expand Down
6 changes: 3 additions & 3 deletions turbopack/crates/turbopack-ecmascript-runtime/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference path="../../../shared/runtime-types.d.ts" />
/// <reference path="../runtime/base/globals.d.ts" />
/// <reference path="../runtime/base/protocol.d.ts" />
/// <reference path="../runtime/base/extensions.d.ts" />
/// <reference path="../../runtime/base/dev-globals.d.ts" />
/// <reference path="../../runtime/base/dev-protocol.d.ts" />
/// <reference path="../../runtime/base/dev-extensions.ts" />

import {
addMessageListener as turboSocketAddMessageListener,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/// <reference path="./runtime-base.ts" />

const moduleCache: ModuleCache<BaseModule> = {};

/**
* 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<BaseModule> = (
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;
}

Loading

0 comments on commit 3473b85

Please sign in to comment.