Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turbopack: fix loading of externals on Edge #72349

Merged
merged 10 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export default class EvalSourceMapDevToolPlugin {
{
requestShortener: runtimeTemplate.requestShortener,
chunkGraph,
hashFunction: compilation.outputOptions.hashFunction,
hashFunction: compilation.outputOptions.hashFunction!,
}
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('default', () => {
allBundles += output
}
expect(allBundles).toContain(
'__turbopack_external_require__("external-package")'
'__turbopack_external_require__("external-package"'
)
} else {
const output = await fs.readFile(
Expand Down
1 change: 1 addition & 0 deletions turbopack/crates/turbopack-core/src/compile_time_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ pub enum FreeVarReference {
lookup_path: Option<ResolvedVc<FileSystemPath>>,
export: Option<RcStr>,
},
Ident(RcStr),
Value(CompileTimeDefineValue),
Error(RcStr),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ let BACKEND: RuntimeBackend;

type ExternalRequire = (
id: ModuleId,
thunk: () => any,
esm?: boolean
) => Exports | EsmNamespaceObject;

type ExternalImport = (id: ModuleId) => Promise<Exports | EsmNamespaceObject>;

interface TurbopackEdgeContext extends TurbopackBaseContext<Module> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ function stringifySourceInfo(source: SourceInfo): string {
}
}

type ExternalRequire = (id: ModuleId) => Exports | EsmNamespaceObject;
type ExternalRequire = (
id: ModuleId,
thunk: () => any,
esm?: boolean
) => Exports | EsmNamespaceObject;
type ExternalImport = (id: ModuleId) => Promise<Exports | EsmNamespaceObject>;

interface TurbopackNodeBuildContext extends TurbopackBaseContext<Module> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,52 @@
/// If a fn requires node.js specific behavior, it should be placed in `node-external-utils` instead.

async function externalImport(id: ModuleId) {
let raw;
let raw
try {
raw = await import(id);
raw = await import(id)
} catch (err) {
// TODO(alexkirsz) This can happen when a client-side module tries to load
// an external module we don't provide a shim for (e.g. querystring, url).
// For now, we fail semi-silently, but in the future this should be a
// compilation error.
throw new Error(`Failed to load external module ${id}: ${err}`);
throw new Error(`Failed to load external module ${id}: ${err}`)
}

if (raw && raw.__esModule && raw.default && "default" in raw.default) {
return interopEsm(raw.default, createNS(raw), true);
if (raw && raw.__esModule && raw.default && 'default' in raw.default) {
return interopEsm(raw.default, createNS(raw), true)
}

return raw;
return raw
}

function externalRequire(
id: ModuleId,
thunk: () => any,
esm: boolean = false
): Exports | EsmNamespaceObject {
let raw;
let raw
try {
raw = require(id);
raw = thunk()
} catch (err) {
// TODO(alexkirsz) This can happen when a client-side module tries to load
// an external module we don't provide a shim for (e.g. querystring, url).
// For now, we fail semi-silently, but in the future this should be a
// compilation error.
throw new Error(`Failed to load external module ${id}: ${err}`);
throw new Error(`Failed to load external module ${id}: ${err}`)
}

if (!esm || raw.__esModule) {
return raw;
return raw
}

return interopEsm(raw, createNS(raw), true);
return interopEsm(raw, createNS(raw), true)
}

externalRequire.resolve = (
id: string,
options?: {
paths?: string[];
paths?: string[]
}
) => {
return require.resolve(id, options);
};
return require.resolve(id, options)
}
3 changes: 3 additions & 0 deletions turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,9 @@ impl From<&FreeVarReference> for JsValue {
fn from(v: &FreeVarReference) -> Self {
match v {
FreeVarReference::Value(v) => v.into(),
FreeVarReference::Ident(_) => {
JsValue::unknown_empty(false, "compile time injected ident")
}
FreeVarReference::EcmaScriptModule { .. } => {
JsValue::unknown_empty(false, "compile time injected free var module")
}
Expand Down
8 changes: 4 additions & 4 deletions turbopack/crates/turbopack-ecmascript/src/chunk/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ impl EcmascriptChunkItemContent {
args.push("e: exports");
}
if self.options.stub_require {
args.push("z: require");
args.push("z: __turbopack_require_stub__");
} else {
args.push("t: require");
args.push("t: __turbopack_require_real__");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need that at all? We could just call require directly.

Any indirect usage of require would cause these nested bundling problems again...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is this special handling to always have require available though (and we want this for both ESM and CJS-on-Edge-without-require):

// 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");
};

}
if self.options.wasm {
args.push("w: __turbopack_wasm__");
Expand Down Expand Up @@ -193,8 +193,8 @@ pub struct EcmascriptChunkItemOptions {
/// Whether this chunk item's module factory should include an `exports`
/// argument.
pub exports: bool,
/// Whether this chunk item's module factory should include an argument for the real `require`,
/// or just a throwing stub (for ESM)
/// Whether this chunk item's module factory should include an argument for a throwing require
/// stub (for ESM)
pub stub_require: bool,
/// Whether this chunk item's module factory should include a
/// `__turbopack_external_require__` argument.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ impl CodeGenerateable for EsmAssetReference {
)
} else {
quote!(
"var $name = __turbopack_external_require__($id, true);" as Stmt,
"var $name = __turbopack_external_require__($id, () => require($id), true);" as Stmt,
name = Ident::new(ident.clone().into(), DUMMY_SP, Default::default()),
id: Expr = Expr::Lit(request.clone().to_string().into())
)
Expand Down Expand Up @@ -351,7 +351,7 @@ impl CodeGenerateable for EsmAssetReference {
ident.clone().into(),
var_decl_with_span(
quote!(
"var $name = __turbopack_external_require__($id, true);" as Stmt,
"var $name = __turbopack_external_require__($id, () => require($id), true);" as Stmt,
name = Ident::new(ident.clone().into(), DUMMY_SP, Default::default()),
id: Expr = Expr::Lit(request.clone().to_string().into())
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ impl CachedExternalModule {
} else {
writeln!(
code,
"const mod = __turbopack_external_require__({});",
"const mod = __turbopack_external_require__({}, () => require({}));",
StringifyJs(&self.request),
StringifyJs(&self.request)
)?;
}
Expand Down
44 changes: 44 additions & 0 deletions turbopack/crates/turbopack-ecmascript/src/references/ident.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use anyhow::Result;
use swc_core::{ecma::ast::Expr, quote};
use turbo_rcstr::RcStr;
use turbo_tasks::Vc;
use turbopack_core::chunk::ChunkingContext;

use super::AstPath;
use crate::{
code_gen::{CodeGenerateable, CodeGeneration},
create_visitor,
};

#[turbo_tasks::value]
pub struct IdentReplacement {
value: RcStr,
path: Vc<AstPath>,
}

#[turbo_tasks::value_impl]
impl IdentReplacement {
#[turbo_tasks::function]
pub fn new(value: RcStr, path: Vc<AstPath>) -> Vc<Self> {
Self::cell(IdentReplacement { value, path })
}
}

#[turbo_tasks::value_impl]
impl CodeGenerateable for IdentReplacement {
#[turbo_tasks::function]
async fn code_generation(
&self,
_context: Vc<Box<dyn ChunkingContext>>,
) -> Result<Vc<CodeGeneration>> {
let value = self.value.clone();
let path = &self.path.await?;

let visitor = create_visitor!(path, visit_mut_expr(expr: &mut Expr) {
let id = Expr::Ident((&*value).into());
*expr = quote!("(\"TURBOPACK ident replacement\", $e)" as Expr, e: Expr = id);
});

Ok(CodeGeneration::visitors(vec![visitor]))
}
}
26 changes: 22 additions & 4 deletions turbopack/crates/turbopack-ecmascript/src/references/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod constant_value;
pub mod dynamic_expression;
pub mod esm;
pub mod external_module;
pub mod ident;
pub mod node;
pub mod pattern_mapping;
pub mod raw;
Expand Down Expand Up @@ -133,6 +134,7 @@ use crate::{
cjs::{CjsRequireAssetReference, CjsRequireCacheAccess, CjsRequireResolveAssetReference},
dynamic_expression::DynamicExpression,
esm::{module_id::EsmModuleIdAssetReference, EsmBinding, UrlRewriteBehavior},
ident::IdentReplacement,
node::PackageJsonReference,
require_context::{RequireContextAssetReference, RequireContextMap},
type_issue::SpecifiedModuleTypeIssue,
Expand Down Expand Up @@ -1158,7 +1160,14 @@ pub(crate) async fn analyse_ecmascript_module_internal(
span,
in_try: _,
} => {
handle_free_var(&ast_path, var, span, &analysis_state, &mut analysis).await?;
// FreeVar("require") might be turbopackIgnore-d
if !analysis_state
.link_value(var.clone(), eval_context.imports.get_attributes(span))
.await?
.is_unknown()
{
handle_free_var(&ast_path, var, span, &analysis_state, &mut analysis).await?;
}
}
Effect::Member {
obj,
Expand Down Expand Up @@ -1242,10 +1251,10 @@ async fn compile_time_info_for_module_type(
let free_var_references = compile_time_info.free_var_references;

let mut free_var_references = free_var_references.await?.clone_value();
let (typeof_exports, typeof_module) = if is_esm {
("undefined", "undefined")
let (typeof_exports, typeof_module, require) = if is_esm {
("undefined", "undefined", "__turbopack_require_stub__")
} else {
("object", "object")
("object", "object", "__turbopack_require_real__")
};
free_var_references
.entry(vec![
Expand All @@ -1272,6 +1281,9 @@ async fn compile_time_info_for_module_type(
DefineableNameSegment::TypeOf,
])
.or_insert("function".into());
free_var_references
.entry(vec![DefineableNameSegment::Name("require".into())])
.or_insert(FreeVarReference::Ident(require.into()));

Ok(CompileTimeInfo {
environment: compile_time_info.environment,
Expand Down Expand Up @@ -2203,6 +2215,12 @@ async fn handle_free_var_reference(
Vc::cell(ast_path.to_vec()),
));
}
FreeVarReference::Ident(value) => {
analysis.add_code_gen(IdentReplacement::new(
value.clone(),
Vc::cell(ast_path.to_vec()),
));
}
FreeVarReference::EcmaScriptModule {
request,
lookup_path,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,27 +107,15 @@ impl SinglePatternMapping {
match self {
Self::Invalid => self.create_id(key_expr),
Self::Unresolvable(request) => throw_module_not_found_expr(request),
Self::Ignored => {
quote!("{}" as Expr)
}
Self::Module(_) | Self::ModuleLoader(_) => Expr::Call(CallExpr {
callee: Callee::Expr(quote_expr!("__turbopack_require__")),
args: vec![ExprOrSpread {
spread: None,
expr: Box::new(self.create_id(key_expr)),
}],
span: DUMMY_SP,
..Default::default()
}),
Self::External(request, ExternalType::CommonJs) => Expr::Call(CallExpr {
callee: Callee::Expr(quote_expr!("__turbopack_external_require__")),
args: vec![ExprOrSpread {
spread: None,
expr: request.as_str().into(),
}],
span: DUMMY_SP,
..Default::default()
}),
Self::Ignored => quote!("{}" as Expr),
Self::Module(_) | Self::ModuleLoader(_) => quote!(
"__turbopack_require__($arg)" as Expr,
arg: Expr = self.create_id(key_expr)
),
Self::External(request, ExternalType::CommonJs) => quote!(
"__turbopack_external_require__($arg, () => require($arg))" as Expr,
arg: Expr = request.as_str().into()
),
Self::External(request, ty) => throw_module_not_found_error_expr(
request,
&format!("Unsupported external type {:?} for commonjs reference", ty),
Expand Down Expand Up @@ -170,7 +158,7 @@ impl SinglePatternMapping {
args: vec![ExprOrSpread {
spread: None,
expr: quote_expr!(
"() => __turbopack_external_require__($arg, true)",
"() => __turbopack_external_require__($arg, () => require($arg), true)",
arg: Expr = key_expr.into_owned()
),
}],
Expand All @@ -184,7 +172,7 @@ impl SinglePatternMapping {
args: vec![ExprOrSpread {
spread: None,
expr: quote_expr!(
"() => __turbopack_external_require__($arg, true)",
"() => __turbopack_external_require__($arg, () => require($arg), true)",
arg: Expr = key_expr.into_owned()
),
}],
Expand Down
4 changes: 2 additions & 2 deletions turbopack/crates/turbopack-node/js/src/transforms/postcss.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare const __turbopack_external_require__: (id: string) => any;
declare const __turbopack_external_require__: (id: string, thunk: () => any, esm?: boolean) => any;

// @ts-ignore
import postcss from "@vercel/turbopack/postcss";
Expand Down Expand Up @@ -54,7 +54,7 @@ export const init = async (ipc: Ipc<IpcInfoMessage, IpcRequestMessage>) => {
let pluginFactory = arg;

if (typeof pluginFactory === "string") {
pluginFactory = __turbopack_external_require__(pluginFactory);
pluginFactory = require(/* turbopackIgnore: true */ pluginFactory);
}

if (pluginFactory.default) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
declare const __turbopack_external_require__: {
resolve: (name: string, opt: { paths: string[] }) => string;
} & ((id: string) => any);
} & ((id: string, thunk: () => any, esm?: boolean) => any);

import type { Ipc } from "../ipc/evaluate";
import {
Expand Down Expand Up @@ -58,12 +58,7 @@ type LoaderConfig =
options: { [k: string]: unknown };
};

let runLoaders: typeof import("loader-runner")["runLoaders"];
try {
({ runLoaders } = require("@vercel/turbopack/loader-runner"));
} catch {
({ runLoaders } = __turbopack_external_require__("loader-runner"));
}
const { runLoaders }: typeof import("loader-runner") = require("@vercel/turbopack/loader-runner");

const contextDir = process.cwd();
const toPath = (file: string) => {
Expand Down Expand Up @@ -498,11 +493,13 @@ function makeErrorEmitter(
name: error.name,
message: error.message,
stack: error.stack ? parseStackTrace(error.stack) : [],
cause: undefined,
}
: {
name: "Error",
message: error,
stack: [],
cause: undefined,
},
});
};
Expand Down
Loading
Loading