From 3689c03d60339fb49856c05c7b8f088068d2d0de Mon Sep 17 00:00:00 2001 From: OJ Kwon <1210596+kwonoj@users.noreply.github.com> Date: Tue, 19 Mar 2024 13:52:06 -0700 Subject: [PATCH] feat(next-core): support unsupported module runtime error (#63491) ### What Implement webpack's middleware plugin equivalent for webpack, to raise unsupported error in runtime. PR utilizes import map alias for the edge context, to resolve into modulereplacer internally provides a virtualsource to call runtime error logic. Since we already have globally augmented, the virtualsource only need to take export those into module. Closes PACK-2789 --- .../crates/next-core/src/next_edge/mod.rs | 1 + .../next-core/src/next_edge/unsupported.rs | 77 +++++++++++++++++++ .../crates/next-core/src/next_import_map.rs | 72 +++++++++++++++++ .../edge-runtime-module-errors/test/utils.js | 5 +- test/turbopack-dev-tests-manifest.json | 22 +++--- 5 files changed, 165 insertions(+), 12 deletions(-) create mode 100644 packages/next-swc/crates/next-core/src/next_edge/unsupported.rs diff --git a/packages/next-swc/crates/next-core/src/next_edge/mod.rs b/packages/next-swc/crates/next-core/src/next_edge/mod.rs index 6c72552f20..332bda6427 100644 --- a/packages/next-swc/crates/next-core/src/next_edge/mod.rs +++ b/packages/next-swc/crates/next-core/src/next_edge/mod.rs @@ -1,3 +1,4 @@ pub mod context; pub mod entry; pub mod route_regex; +pub mod unsupported; diff --git a/packages/next-swc/crates/next-core/src/next_edge/unsupported.rs b/packages/next-swc/crates/next-core/src/next_edge/unsupported.rs new file mode 100644 index 0000000000..1318c95c8f --- /dev/null +++ b/packages/next-swc/crates/next-core/src/next_edge/unsupported.rs @@ -0,0 +1,77 @@ +use anyhow::Result; +use indoc::formatdoc; +use turbo_tasks::Vc; +use turbo_tasks_fs::File; +use turbopack_binding::{ + turbo::tasks_fs::FileSystemPath, + turbopack::{ + core::{ + asset::AssetContent, + resolve::{ + options::{ImportMapResult, ImportMapping, ImportMappingReplacement}, + parse::Request, + ResolveResult, + }, + virtual_source::VirtualSource, + }, + node::execution_context::ExecutionContext, + }, +}; + +/// Intercepts requests for the given request to `unsupported` error messages +/// by returning a VirtualSource proxies to any import request to raise a +/// runtime error. +/// +/// This can be used by import map alias, refer `next_import_map` for the setup. +#[turbo_tasks::value(shared)] +pub struct NextEdgeUnsupportedModuleReplacer { + project_path: Vc, + execution_context: Vc, +} + +#[turbo_tasks::value_impl] +impl NextEdgeUnsupportedModuleReplacer { + #[turbo_tasks::function] + pub fn new( + project_path: Vc, + execution_context: Vc, + ) -> Vc { + Self::cell(NextEdgeUnsupportedModuleReplacer { + project_path, + execution_context, + }) + } +} + +#[turbo_tasks::value_impl] +impl ImportMappingReplacement for NextEdgeUnsupportedModuleReplacer { + #[turbo_tasks::function] + fn replace(&self, _capture: String) -> Vc { + ImportMapping::Ignore.into() + } + + #[turbo_tasks::function] + async fn result( + &self, + context: Vc, + request: Vc, + ) -> Result> { + let request = &*request.await?; + if let Request::Module { module, .. } = request { + // packages/next/src/server/web/globals.ts augments global with + // `__import_unsupported` and necessary functions. + let code = formatdoc! { + r#" + __turbopack_export_namespace__(__import_unsupported(`{module}`)); + "# + }; + let content = AssetContent::file(File::from(code).into()); + let source = VirtualSource::new(context, content); + return Ok( + ImportMapResult::Result(ResolveResult::source(Vc::upcast(source)).into()).into(), + ); + }; + + Ok(ImportMapResult::NoEntry.into()) + } +} diff --git a/packages/next-swc/crates/next-core/src/next_import_map.rs b/packages/next-swc/crates/next-core/src/next_import_map.rs index 6afdc1bb8e..8a420e7ae1 100644 --- a/packages/next-swc/crates/next-core/src/next_import_map.rs +++ b/packages/next-swc/crates/next-core/src/next_import_map.rs @@ -26,6 +26,7 @@ use crate::{ mode::NextMode, next_client::context::ClientContextType, next_config::NextConfig, + next_edge::unsupported::NextEdgeUnsupportedModuleReplacer, next_font::{ google::{ NextFontGoogleCssModuleReplacer, NextFontGoogleFontFileReplacer, NextFontGoogleReplacer, @@ -38,6 +39,57 @@ use crate::{ util::NextRuntime, }; +const NODE_INTERNALS: [&str; 48] = [ + "assert", + "async_hooks", + "child_process", + "cluster", + "console", + "constants", + "dgram", + "diagnostics_channel", + "dns", + "dns/promises", + "domain", + "events", + "fs", + "fs/promises", + "http", + "http2", + "https", + "inspector", + "module", + "net", + "os", + "path", + "path/posix", + "path/win32", + "perf_hooks", + "process", + "punycode", + "querystring", + "readline", + "repl", + "stream", + "stream/promises", + "stream/web", + "string_decoder", + "sys", + "timers", + "timers/promises", + "tls", + "trace_events", + "tty", + "util", + "util/types", + "v8", + "vm", + "wasi", + "worker_threads", + "zlib", + "pnpapi", +]; + // Make sure to not add any external requests here. /// Computes the Next-specific client import map. #[turbo_tasks::function] @@ -416,9 +468,29 @@ pub async fn get_next_edge_import_map( ) .await?; + insert_unsupported_node_internal_aliases(&mut import_map, project_path, execution_context); + Ok(import_map.cell()) } +/// Insert default aliases for the node.js's internal to raise unsupported +/// runtime errors. User may provide polyfills for their own by setting user +/// config's alias. +fn insert_unsupported_node_internal_aliases( + import_map: &mut ImportMap, + project_path: Vc, + execution_context: Vc, +) { + let unsupported_replacer = ImportMapping::Dynamic(Vc::upcast( + NextEdgeUnsupportedModuleReplacer::new(project_path, execution_context), + )) + .into(); + + NODE_INTERNALS.iter().for_each(|module| { + import_map.insert_alias(AliasPattern::exact(*module), unsupported_replacer); + }); +} + pub fn get_next_client_resolved_map( _context: Vc, _root: Vc, diff --git a/test/integration/edge-runtime-module-errors/test/utils.js b/test/integration/edge-runtime-module-errors/test/utils.js index 611a26d64a..291e1f51a9 100644 --- a/test/integration/edge-runtime-module-errors/test/utils.js +++ b/test/integration/edge-runtime-module-errors/test/utils.js @@ -55,7 +55,10 @@ export function expectUnsupportedModuleDevError( output = context.logs.output ) { expectUnsupportedModuleProdError(moduleName, output) - expect(stripAnsi(output)).toContain(importStatement) + // turbopack have correct error overly, but doesn't emit those into cli + if (!process.env.TURBOPACK) { + expect(stripAnsi(output)).toContain(importStatement) + } const moduleNotSupportedMessage = getUnsupportedModule(moduleName) expect(responseText).toContain(escapeLF(moduleNotSupportedMessage)) diff --git a/test/turbopack-dev-tests-manifest.json b/test/turbopack-dev-tests-manifest.json index db269d409e..ff8971efc7 100644 --- a/test/turbopack-dev-tests-manifest.json +++ b/test/turbopack-dev-tests-manifest.json @@ -9533,16 +9533,16 @@ "test/integration/edge-runtime-module-errors/test/index.test.js": { "passed": [ "Edge runtime code with imports Edge API importing vanilla 3rd party module does not throw in dev at runtime", - "Edge runtime code with imports Edge API using Buffer polyfill does not throw in dev at runtime", "Edge runtime code with imports Middleware importing vanilla 3rd party module does not throw in dev at runtime", - "Edge runtime code with imports Middleware using Buffer polyfill does not throw in dev at runtime" + "Edge runtime code with imports Edge API dynamically importing node.js module development mode throws unsupported module error in dev at runtime and highlights the faulty line", + "Edge runtime code with imports Middleware dynamically importing node.js module development mode throws unsupported module error in dev at runtime and highlights the faulty line", + "Edge runtime code with imports Edge API dynamically importing node.js module in a lib development mode throws unsupported module error in dev at runtime and highlights the faulty line", + "Edge runtime code with imports Middleware dynamically importing node.js module in a lib development mode throws unsupported module error in dev at runtime and highlights the faulty line" ], "failed": [ - "Edge runtime code with imports Edge API dynamically importing node.js module development mode throws unsupported module error in dev at runtime and highlights the faulty line", - "Edge runtime code with imports Edge API dynamically importing node.js module in a lib development mode throws unsupported module error in dev at runtime and highlights the faulty line", + "Edge runtime code with imports Edge API using Buffer polyfill does not throw in dev at runtime", + "Edge runtime code with imports Middleware using Buffer polyfill does not throw in dev at runtime", "Edge runtime code with imports Edge API statically importing 3rd party module throws not-found module error in dev at runtime and highlights the faulty line", - "Edge runtime code with imports Middleware dynamically importing node.js module development mode throws unsupported module error in dev at runtime and highlights the faulty line", - "Edge runtime code with imports Middleware dynamically importing node.js module in a lib development mode throws unsupported module error in dev at runtime and highlights the faulty line", "Edge runtime code with imports Middleware statically importing 3rd party module throws not-found module error in dev at runtime and highlights the faulty line" ], "pending": [ @@ -9562,16 +9562,16 @@ }, "test/integration/edge-runtime-module-errors/test/module-imports.test.js": { "passed": [ - "Edge runtime code with imports Edge API importing unused node.js module does not throw in dev at runtime" + "Edge runtime code with imports Edge API importing unused node.js module does not throw in dev at runtime", + "Edge runtime code with imports Middleware importing unused node.js module does not throw in dev at runtime", + "Edge runtime code with imports Edge API statically importing node.js module throws unsupported module error in dev at runtime and highlights the faulty line", + "Edge runtime code with imports Middleware statically importing node.js module throws unsupported module error in dev at runtime and highlights the faulty line" ], "failed": [ "Edge runtime code with imports Edge API dynamically importing 3rd party module throws not-found module error in dev at runtime and highlights the faulty line", "Edge runtime code with imports Edge API importing unused 3rd party module throws not-found module error in dev at runtime and highlights the faulty line", - "Edge runtime code with imports Edge API statically importing node.js module throws unsupported module error in dev at runtime and highlights the faulty line", "Edge runtime code with imports Middleware dynamically importing 3rd party module throws not-found module error in dev at runtime and highlights the faulty line", - "Edge runtime code with imports Middleware importing unused 3rd party module throws not-found module error in dev at runtime and highlights the faulty line", - "Edge runtime code with imports Middleware importing unused node.js module does not throw in dev at runtime", - "Edge runtime code with imports Middleware statically importing node.js module throws unsupported module error in dev at runtime and highlights the faulty line" + "Edge runtime code with imports Middleware importing unused 3rd party module throws not-found module error in dev at runtime and highlights the faulty line" ], "pending": [ "Edge runtime code with imports Edge API dynamically importing 3rd party module production mode does not build and reports module not found error",