diff --git a/.changeset/silent-seals-rhyme.md b/.changeset/silent-seals-rhyme.md new file mode 100644 index 000000000000..ca22d91bfc21 --- /dev/null +++ b/.changeset/silent-seals-rhyme.md @@ -0,0 +1,10 @@ +--- +"@rspack/binding": patch +"@rspack/binding-darwin-arm64": patch +"@rspack/binding-darwin-x64": patch +"@rspack/binding-linux-x64-gnu": patch +"@rspack/binding-win32-x64-msvc": patch +"@rspack/core": patch +--- + +feat: inline external type syntax diff --git a/Cargo.lock b/Cargo.lock index 1ecb21b2c63a..212eeed74bfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2771,9 +2771,12 @@ name = "rspack_plugin_externals" version = "0.1.0" dependencies = [ "async-trait", + "once_cell", + "regex", "rspack_core", "rspack_error", "rspack_identifier", + "rspack_regex", "tracing", "tracing-subscriber", ] diff --git a/crates/node_binding/Cargo.lock b/crates/node_binding/Cargo.lock index 3d43cc900f4c..753b6f19d5cc 100644 --- a/crates/node_binding/Cargo.lock +++ b/crates/node_binding/Cargo.lock @@ -2370,9 +2370,12 @@ name = "rspack_plugin_externals" version = "0.1.0" dependencies = [ "async-trait", + "once_cell", + "regex", "rspack_core", "rspack_error", "rspack_identifier", + "rspack_regex", "tracing", "tracing-subscriber", ] diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index af345e7b64a1..01ce83b6261e 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -165,7 +165,12 @@ export interface RawExternalItem { type: "string" | "regexp" | "object" stringPayload?: string regexpPayload?: string - objectPayload?: Record + objectPayload?: Record +} +export interface RawExternalItemValue { + type: "string" | "bool" + stringPayload?: string + boolPayload?: boolean } export interface RawExternalsPresets { node: boolean diff --git a/crates/rspack_binding_options/src/options/mod.rs b/crates/rspack_binding_options/src/options/mod.rs index 6d1d3f0b45fc..205245195e26 100644 --- a/crates/rspack_binding_options/src/options/mod.rs +++ b/crates/rspack_binding_options/src/options/mod.rs @@ -2,10 +2,9 @@ use std::{collections::HashMap, fmt::Debug}; use napi_derive::napi; use rspack_core::{ - BoxPlugin, CompilerOptions, DevServerOptions, Devtool, EntryItem, Experiments, ExternalItem, - ModuleOptions, OutputOptions, PluginExt, TargetPlatform, + BoxPlugin, CompilerOptions, DevServerOptions, Devtool, EntryItem, Experiments, ModuleOptions, + OutputOptions, PluginExt, TargetPlatform, }; -use rspack_regex::RspackRegex; use serde::Deserialize; mod raw_builtins; @@ -145,7 +144,7 @@ impl RawOptionsApply for RawOptions { ); } if self.externals_presets.node { - plugins.push(node_target_plugin()); + plugins.push(rspack_plugin_externals::node_target_plugin()); } plugins.push(rspack_plugin_javascript::JsPlugin::new().boxed()); plugins.push( @@ -185,66 +184,3 @@ impl RawOptionsApply for RawOptions { }) } } - -fn node_target_plugin() -> BoxPlugin { - rspack_plugin_externals::ExternalPlugin::new( - "commonjs".to_string(), // TODO: should be "node-commonjs" - vec![ - ExternalItem::from("assert".to_string()), - ExternalItem::from("assert/strict".to_string()), - ExternalItem::from("async_hooks".to_string()), - ExternalItem::from("buffer".to_string()), - ExternalItem::from("child_process".to_string()), - ExternalItem::from("cluster".to_string()), - ExternalItem::from("console".to_string()), - ExternalItem::from("constants".to_string()), - ExternalItem::from("crypto".to_string()), - ExternalItem::from("dgram".to_string()), - ExternalItem::from("diagnostics_channel".to_string()), - ExternalItem::from("dns".to_string()), - ExternalItem::from("dns/promises".to_string()), - ExternalItem::from("domain".to_string()), - ExternalItem::from("events".to_string()), - ExternalItem::from("fs".to_string()), - ExternalItem::from("fs/promises".to_string()), - ExternalItem::from("http".to_string()), - ExternalItem::from("http2".to_string()), - ExternalItem::from("https".to_string()), - ExternalItem::from("inspector".to_string()), - ExternalItem::from("module".to_string()), - ExternalItem::from("net".to_string()), - ExternalItem::from("os".to_string()), - ExternalItem::from("path".to_string()), - ExternalItem::from("path/posix".to_string()), - ExternalItem::from("path/win32".to_string()), - ExternalItem::from("perf_hooks".to_string()), - ExternalItem::from("process".to_string()), - ExternalItem::from("punycode".to_string()), - ExternalItem::from("querystring".to_string()), - ExternalItem::from("readline".to_string()), - ExternalItem::from("repl".to_string()), - ExternalItem::from("stream".to_string()), - ExternalItem::from("stream/promises".to_string()), - ExternalItem::from("stream/web".to_string()), - ExternalItem::from("string_decoder".to_string()), - ExternalItem::from("sys".to_string()), - ExternalItem::from("timers".to_string()), - ExternalItem::from("timers/promises".to_string()), - ExternalItem::from("tls".to_string()), - ExternalItem::from("trace_events".to_string()), - ExternalItem::from("tty".to_string()), - ExternalItem::from("url".to_string()), - ExternalItem::from("util".to_string()), - ExternalItem::from("util/types".to_string()), - ExternalItem::from("v8".to_string()), - ExternalItem::from("vm".to_string()), - ExternalItem::from("wasi".to_string()), - ExternalItem::from("worker_threads".to_string()), - ExternalItem::from("zlib".to_string()), - ExternalItem::from(RspackRegex::new("^node:").expect("Invalid regexp")), - // Yarn PnP adds pnpapi as "builtin" - ExternalItem::from("pnpapi".to_string()), - ], - ) - .boxed() -} diff --git a/crates/rspack_binding_options/src/options/raw_external.rs b/crates/rspack_binding_options/src/options/raw_external.rs index d5785b8f6f24..6224b23f9d3c 100644 --- a/crates/rspack_binding_options/src/options/raw_external.rs +++ b/crates/rspack_binding_options/src/options/raw_external.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use napi_derive::napi; -use rspack_core::ExternalItem; +use rspack_core::{ExternalItem, ExternalItemObject, ExternalItemValue}; use rspack_regex::RspackRegex; use serde::Deserialize; @@ -13,7 +13,35 @@ pub struct RawExternalItem { pub r#type: String, pub string_payload: Option, pub regexp_payload: Option, - pub object_payload: Option>, + pub object_payload: Option>, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +#[napi(object)] +pub struct RawExternalItemValue { + #[napi(ts_type = r#""string" | "bool""#)] + pub r#type: String, + pub string_payload: Option, + pub bool_payload: Option, +} + +impl From for ExternalItemValue { + fn from(value: RawExternalItemValue) -> Self { + match value.r#type.as_str() { + "string" => Self::String( + value + .string_payload + .expect("should have a string_payload when RawExternalItemValue.type is \"string\""), + ), + "bool" => Self::Bool( + value + .bool_payload + .expect("should have a bool_payload when RawExternalItemValue.type is \"bool\""), + ), + _ => unreachable!(), + } + } } impl From for ExternalItem { @@ -32,11 +60,15 @@ impl From for ExternalItem { RspackRegex::new(&payload).expect("regex_payload is not a legal regex in rust side"); Self::from(reg) } - "object" => Self::from( - value + "object" => { + let payload: ExternalItemObject = value .object_payload - .expect("should have a object_payload when RawExternalItem.type is \"object\""), - ), + .expect("should have a object_payload when RawExternalItem.type is \"object\"") + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(); + payload.into() + } _ => unreachable!(), } } diff --git a/crates/rspack_core/src/options/externals.rs b/crates/rspack_core/src/options/externals.rs index cda87b23ee6f..efdc38053313 100644 --- a/crates/rspack_core/src/options/externals.rs +++ b/crates/rspack_core/src/options/externals.rs @@ -1,18 +1,26 @@ -use std::collections::HashMap; - use rspack_regex::RspackRegex; +use rustc_hash::FxHashMap as HashMap; pub type Externals = Vec; +#[derive(Debug)] +pub enum ExternalItemValue { + String(String), + Bool(bool), + // TODO: string[] | Record +} + +pub type ExternalItemObject = HashMap; + #[derive(Debug)] pub enum ExternalItem { - Object(HashMap), + Object(ExternalItemObject), String(String), RegExp(RspackRegex), } -impl From> for ExternalItem { - fn from(value: HashMap) -> Self { +impl From for ExternalItem { + fn from(value: ExternalItemObject) -> Self { Self::Object(value) } } diff --git a/crates/rspack_plugin_externals/Cargo.toml b/crates/rspack_plugin_externals/Cargo.toml index 0864cf68d582..525736dc7ca2 100644 --- a/crates/rspack_plugin_externals/Cargo.toml +++ b/crates/rspack_plugin_externals/Cargo.toml @@ -9,8 +9,11 @@ version = "0.1.0" [dependencies] async-trait = { workspace = true } +once_cell = { workspace = true } +regex = { workspace = true } rspack_core = { path = "../rspack_core" } rspack_error = { path = "../rspack_error" } rspack_identifier = { path = "../rspack_identifier" } +rspack_regex = { path = "../rspack_regex" } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/crates/rspack_plugin_externals/src/lib.rs b/crates/rspack_plugin_externals/src/lib.rs index 7f2c19c96211..436c9b3553bf 100644 --- a/crates/rspack_plugin_externals/src/lib.rs +++ b/crates/rspack_plugin_externals/src/lib.rs @@ -1,3 +1,7 @@ +#![feature(let_chains)] + +mod node_target_plugin; mod plugin; +pub use node_target_plugin::*; pub use plugin::ExternalPlugin; diff --git a/crates/rspack_plugin_externals/src/node_target_plugin.rs b/crates/rspack_plugin_externals/src/node_target_plugin.rs new file mode 100644 index 000000000000..739424056626 --- /dev/null +++ b/crates/rspack_plugin_externals/src/node_target_plugin.rs @@ -0,0 +1,68 @@ +use rspack_core::{BoxPlugin, ExternalItem, PluginExt}; +use rspack_regex::RspackRegex; + +pub fn node_target_plugin() -> BoxPlugin { + crate::ExternalPlugin::new( + "commonjs".to_string(), // TODO: should be "node-commonjs" + vec![ + ExternalItem::from("assert".to_string()), + ExternalItem::from("assert/strict".to_string()), + ExternalItem::from("async_hooks".to_string()), + ExternalItem::from("buffer".to_string()), + ExternalItem::from("child_process".to_string()), + ExternalItem::from("cluster".to_string()), + ExternalItem::from("console".to_string()), + ExternalItem::from("constants".to_string()), + ExternalItem::from("crypto".to_string()), + ExternalItem::from("dgram".to_string()), + ExternalItem::from("diagnostics_channel".to_string()), + ExternalItem::from("dns".to_string()), + ExternalItem::from("dns/promises".to_string()), + ExternalItem::from("domain".to_string()), + ExternalItem::from("events".to_string()), + ExternalItem::from("fs".to_string()), + ExternalItem::from("fs/promises".to_string()), + ExternalItem::from("http".to_string()), + ExternalItem::from("http2".to_string()), + ExternalItem::from("https".to_string()), + ExternalItem::from("inspector".to_string()), + ExternalItem::from("inspector/promises".to_string()), + ExternalItem::from("module".to_string()), + ExternalItem::from("net".to_string()), + ExternalItem::from("os".to_string()), + ExternalItem::from("path".to_string()), + ExternalItem::from("path/posix".to_string()), + ExternalItem::from("path/win32".to_string()), + ExternalItem::from("perf_hooks".to_string()), + ExternalItem::from("process".to_string()), + ExternalItem::from("punycode".to_string()), + ExternalItem::from("querystring".to_string()), + ExternalItem::from("readline".to_string()), + ExternalItem::from("readline/promises".to_string()), + ExternalItem::from("repl".to_string()), + ExternalItem::from("stream".to_string()), + ExternalItem::from("stream/consumers".to_string()), + ExternalItem::from("stream/promises".to_string()), + ExternalItem::from("stream/web".to_string()), + ExternalItem::from("string_decoder".to_string()), + ExternalItem::from("sys".to_string()), + ExternalItem::from("timers".to_string()), + ExternalItem::from("timers/promises".to_string()), + ExternalItem::from("tls".to_string()), + ExternalItem::from("trace_events".to_string()), + ExternalItem::from("tty".to_string()), + ExternalItem::from("url".to_string()), + ExternalItem::from("util".to_string()), + ExternalItem::from("util/types".to_string()), + ExternalItem::from("v8".to_string()), + ExternalItem::from("vm".to_string()), + ExternalItem::from("wasi".to_string()), + ExternalItem::from("worker_threads".to_string()), + ExternalItem::from("zlib".to_string()), + ExternalItem::from(RspackRegex::new("^node:").expect("Invalid regexp")), + // Yarn PnP adds pnpapi as "builtin" + ExternalItem::from("pnpapi".to_string()), + ], + ) + .boxed() +} diff --git a/crates/rspack_plugin_externals/src/plugin.rs b/crates/rspack_plugin_externals/src/plugin.rs index fb8e7fdde278..21b1979d4b95 100644 --- a/crates/rspack_plugin_externals/src/plugin.rs +++ b/crates/rspack_plugin_externals/src/plugin.rs @@ -1,10 +1,15 @@ +use once_cell::sync::Lazy; +use regex::Regex; use rspack_core::{ - ApplyContext, ExternalItem, ExternalModule, ExternalType, FactorizeArgs, ModuleExt, - ModuleFactoryResult, NormalModuleFactoryContext, Plugin, PluginContext, - PluginFactorizeHookOutput, + ApplyContext, ExternalItem, ExternalItemValue, ExternalModule, ExternalType, FactorizeArgs, + ModuleDependency, ModuleExt, ModuleFactoryResult, NormalModuleFactoryContext, Plugin, + PluginContext, PluginFactorizeHookOutput, }; use rspack_error::Result; +static UNSPECIFIED_EXTERNAL_TYPE_REGEXP: Lazy = + Lazy::new(|| Regex::new(r"^[a-z0-9-]+ ").expect("Invalid regex")); + #[derive(Debug)] pub struct ExternalPlugin { externals: Vec, @@ -15,6 +20,37 @@ impl ExternalPlugin { pub fn new(r#type: ExternalType, externals: Vec) -> Self { Self { externals, r#type } } + + fn handle_external( + &self, + config: &ExternalItemValue, + r#type: Option, + dependency: &dyn ModuleDependency, + ) -> Option { + let mut external_module_config = match config { + ExternalItemValue::String(config) => config, + ExternalItemValue::Bool(config) => { + if *config { + dependency.request() + } else { + return None; + } + } + }; + let external_module_type = r#type.unwrap_or_else(|| { + if UNSPECIFIED_EXTERNAL_TYPE_REGEXP.is_match(external_module_config) + && let Some((t, c)) = external_module_config.split_once(' ') { + external_module_config = c; + return t.to_owned(); + } + self.r#type.clone() + }); + Some(ExternalModule::new( + external_module_config.to_owned(), + external_module_type, + dependency.request().to_owned(), + )) + } } #[async_trait::async_trait] @@ -33,41 +69,36 @@ impl Plugin for ExternalPlugin { args: FactorizeArgs<'_>, _job_ctx: &mut NormalModuleFactoryContext, ) -> PluginFactorizeHookOutput { - let external_type = &self.r#type; for external_item in &self.externals { match external_item { ExternalItem::Object(eh) => { let request = args.dependency.request(); if let Some(value) = eh.get(request) { - let external_module = ExternalModule::new( - value.to_owned(), - external_type.to_owned(), - request.to_string(), - ); - return Ok(Some(ModuleFactoryResult::new(external_module.boxed()))); + let maybe_module = self.handle_external(value, None, args.dependency); + return Ok(maybe_module.map(|i| ModuleFactoryResult::new(i.boxed()))); } } ExternalItem::RegExp(r) => { let request = args.dependency.request(); if r.test(request) { - let external_module = ExternalModule::new( - request.to_owned(), - external_type.to_owned(), - request.to_string(), + let maybe_module = self.handle_external( + &ExternalItemValue::String(request.to_string()), + None, + args.dependency, ); - return Ok(Some(ModuleFactoryResult::new(external_module.boxed()))); + return Ok(maybe_module.map(|i| ModuleFactoryResult::new(i.boxed()))); } } ExternalItem::String(s) => { let request = args.dependency.request(); if s == request { - let external_module = ExternalModule::new( - request.to_owned(), - external_type.to_owned(), - request.to_string(), + let maybe_module = self.handle_external( + &ExternalItemValue::String(request.to_string()), + None, + args.dependency, ); - return Ok(Some(ModuleFactoryResult::new(external_module.boxed()))); + return Ok(maybe_module.map(|i| ModuleFactoryResult::new(i.boxed()))); } } } diff --git a/packages/rspack/src/config/adapter.ts b/packages/rspack/src/config/adapter.ts index 50a44026df6d..f34b60c37210 100644 --- a/packages/rspack/src/config/adapter.ts +++ b/packages/rspack/src/config/adapter.ts @@ -4,7 +4,8 @@ import { RawRuleSetCondition, RawRuleSetLogicalConditions, RawOptions, - RawExternalItem + RawExternalItem, + RawExternalItemValue } from "@rspack/binding"; import assert from "assert"; import { normalizeStatsPreset } from "../stats"; @@ -13,6 +14,7 @@ import { EntryNormalized, Experiments, ExternalItem, + ExternalItemValue, Externals, ExternalsPresets, LibraryOptions, @@ -319,7 +321,22 @@ function getRawExternals(externals: Externals): RawOptions["externals"] { } else if (item instanceof RegExp) { return { type: "regexp", regexpPayload: item.source }; } - return { type: "object", objectPayload: item }; + return { + type: "object", + objectPayload: Object.fromEntries( + Object.entries(item).map(([k, v]) => [k, getRawExternalItemValue(v)]) + ) + }; + } + function getRawExternalItemValue( + value: ExternalItemValue + ): RawExternalItemValue { + if (typeof value === "string") { + return { type: "string", stringPayload: value }; + } else if (typeof value === "boolean") { + return { type: "bool", boolPayload: value }; + } + throw new Error("unreachable"); } if (Array.isArray(externals)) { diff --git a/packages/rspack/src/config/schema.js b/packages/rspack/src/config/schema.js index a5d6862cf5bb..3d58eeb6851b 100644 --- a/packages/rspack/src/config/schema.js +++ b/packages/rspack/src/config/schema.js @@ -271,6 +271,11 @@ module.exports = { { description: "The target of the external.", type: "string" + }, + { + description: + "`true`: The dependency name is used as target of the external.", + type: "boolean" } ] }, diff --git a/packages/rspack/src/config/types.ts b/packages/rspack/src/config/types.ts index 100167d509dd..20e2f8835b34 100644 --- a/packages/rspack/src/config/types.ts +++ b/packages/rspack/src/config/types.ts @@ -306,7 +306,7 @@ export type ExternalItem = string | RegExp | ExternalItemObjectUnknown; export interface ExternalItemObjectUnknown { [k: string]: ExternalItemValue; } -export type ExternalItemValue = string; +export type ExternalItemValue = string | boolean; ///// ExternalsType ///// export type ExternalsType = diff --git a/packages/rspack/tests/configCases/externals/array-externals/index.js b/packages/rspack/tests/configCases/externals/array-externals/index.js index de193958bbe1..f4e52554b318 100644 --- a/packages/rspack/tests/configCases/externals/array-externals/index.js +++ b/packages/rspack/tests/configCases/externals/array-externals/index.js @@ -1,10 +1,15 @@ import "./inject"; import foo from "foo"; +import raz from "raz"; +const myos = require("myos"); const bar = require("bar"); -const react = require("react"); +const baz = require("baz"); it("should work with array type of externals", function () { expect(foo).toBe("foo"); expect(bar).toBe("bar"); + expect(baz).toBe("baz"); + expect(raz).toBe("raz"); + expect(typeof myos.constants.errno.EBUSY).toBe("number"); }); diff --git a/packages/rspack/tests/configCases/externals/array-externals/inject.js b/packages/rspack/tests/configCases/externals/array-externals/inject.js index f98458cd90f3..ae15abfda7ea 100644 --- a/packages/rspack/tests/configCases/externals/array-externals/inject.js +++ b/packages/rspack/tests/configCases/externals/array-externals/inject.js @@ -1,3 +1,2 @@ global.foo = "foo"; -global.bar = "bar"; -global.react = "react"; +global.raz = "raz"; diff --git a/packages/rspack/tests/configCases/externals/array-externals/webpack.config.js b/packages/rspack/tests/configCases/externals/array-externals/webpack.config.js index f8bbdcf0ba0b..69cafc4bf258 100644 --- a/packages/rspack/tests/configCases/externals/array-externals/webpack.config.js +++ b/packages/rspack/tests/configCases/externals/array-externals/webpack.config.js @@ -1,3 +1,14 @@ module.exports = { - externals: ["foo", "bar", /^re/] + externals: [ + "foo", + /^raz$/, + { + bar: "'bar'", + baz: "var 'baz'", + myos: "commonjs os" + } + ], + externalsPresets: { + node: false + } };