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
This commit is contained in:
OJ Kwon 2024-03-19 13:52:06 -07:00 committed by GitHub
parent b2b5ab4aff
commit 3689c03d60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 165 additions and 12 deletions

View file

@ -1,3 +1,4 @@
pub mod context;
pub mod entry;
pub mod route_regex;
pub mod unsupported;

View file

@ -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<FileSystemPath>,
execution_context: Vc<ExecutionContext>,
}
#[turbo_tasks::value_impl]
impl NextEdgeUnsupportedModuleReplacer {
#[turbo_tasks::function]
pub fn new(
project_path: Vc<FileSystemPath>,
execution_context: Vc<ExecutionContext>,
) -> Vc<Self> {
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> {
ImportMapping::Ignore.into()
}
#[turbo_tasks::function]
async fn result(
&self,
context: Vc<FileSystemPath>,
request: Vc<Request>,
) -> Result<Vc<ImportMapResult>> {
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())
}
}

View file

@ -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<FileSystemPath>,
execution_context: Vc<ExecutionContext>,
) {
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<FileSystemPath>,
_root: Vc<FileSystemPath>,

View file

@ -55,7 +55,10 @@ export function expectUnsupportedModuleDevError(
output = context.logs.output
) {
expectUnsupportedModuleProdError(moduleName, output)
// 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))

View file

@ -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",