Skip to content

Commit

Permalink
feat: support analysing AMD module format (#8389)
Browse files Browse the repository at this point in the history
  • Loading branch information
nilptr authored Nov 27, 2024
1 parent 8dc9466 commit 96f500d
Show file tree
Hide file tree
Showing 85 changed files with 2,686 additions and 22 deletions.
1 change: 1 addition & 0 deletions crates/node_binding/binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,7 @@ export interface RawOptions {
experiments: RawExperiments
node?: RawNodeOption
profile: boolean
amd?: string
bail: boolean
__references: Record<string, any>
}
Expand Down
2 changes: 2 additions & 0 deletions crates/rspack_binding_options/src/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub struct RawOptions {
pub experiments: RawExperiments,
pub node: Option<RawNodeOption>,
pub profile: bool,
pub amd: Option<String>,
pub bail: bool,
#[napi(js_name = "__references", ts_type = "Record<string, any>")]
pub __references: References,
Expand Down Expand Up @@ -93,6 +94,7 @@ impl TryFrom<RawOptions> for CompilerOptions {
optimization,
node,
profile: value.profile,
amd: value.amd,
bail: value.bail,
__references: value.__references,
})
Expand Down
2 changes: 2 additions & 0 deletions crates/rspack_core/src/dependency/dependency_category.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub enum DependencyCategory {
Unknown,
Esm,
CommonJS,
Amd,
Url,
CssImport,
CssCompose,
Expand Down Expand Up @@ -41,6 +42,7 @@ impl DependencyCategory {
DependencyCategory::Unknown => "unknown",
DependencyCategory::Esm => "esm",
DependencyCategory::CommonJS => "commonjs",
DependencyCategory::Amd => "amd",
DependencyCategory::Url => "url",
DependencyCategory::CssImport => "css-import",
DependencyCategory::CssCompose => "css-compose",
Expand Down
11 changes: 11 additions & 0 deletions crates/rspack_core/src/dependency/dependency_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ pub enum DependencyType {
CjsExportRequire,
// cjs self reference
CjsSelfReference,
// AMD
AmdDefine,
AmdRequireArray,
AmdRequireContext,
AmdRequire,
AmdRequireItem,
// new URL("./foo", import.meta.url)
NewUrl,
// new Worker()
Expand Down Expand Up @@ -126,6 +132,11 @@ impl DependencyType {
DependencyType::CjsExports => "cjs exports",
DependencyType::CjsExportRequire => "cjs export require",
DependencyType::CjsSelfReference => "cjs self exports reference",
DependencyType::AmdDefine => "amd define",
DependencyType::AmdRequireArray => "amd require array",
DependencyType::AmdRequireContext => "amd require context",
DependencyType::AmdRequire => "amd",
DependencyType::AmdRequireItem => "amd require",
DependencyType::NewUrl => "new URL()",
DependencyType::NewWorker => "new Worker()",
DependencyType::CreateScriptUrl => "create script url",
Expand Down
1 change: 1 addition & 0 deletions crates/rspack_core/src/options/compiler_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct CompilerOptions {
pub node: Option<NodeOption>,
pub optimization: Optimization,
pub profile: bool,
pub amd: Option<String>,
pub bail: bool,
pub __references: References,
}
Expand Down
6 changes: 6 additions & 0 deletions crates/rspack_core/src/runtime_globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ bitflags! {
const RSPACK_UNIQUE_ID = 1 << 65;

const HAS_FETCH_PRIORITY = 1 << 66;

// amd module support
const AMD_DEFINE = 1 << 67;
const AMD_OPTIONS = 1 << 68;
}
}

Expand Down Expand Up @@ -295,6 +299,8 @@ impl RuntimeGlobals {
R::GET_CHUNK_UPDATE_CSS_FILENAME => "__webpack_require__.hk",
R::HMR_MODULE_DATA => "__webpack_require__.hmrD",
R::HMR_RUNTIME_STATE_PREFIX => "__webpack_require__.hmrS",
R::AMD_DEFINE => "__webpack_require__.amdD",
R::AMD_OPTIONS => "__webpack_require__.amdO",
R::EXTERNAL_INSTALL_CHUNK => "__webpack_require__.C",
R::GET_FULL_HASH => "__webpack_require__.h",
R::GLOBAL => "__webpack_require__.g",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
use bitflags::bitflags;
use rspack_core::{
AffectType, AsContextDependency, AsModuleDependency, Compilation, Dependency, DependencyCategory,
DependencyId, DependencyTemplate, DependencyType, RuntimeGlobals, RuntimeSpec, TemplateContext,
TemplateReplaceSource,
};
use rspack_util::{atom::Atom, json_stringify};

use super::local_module::LocalModule;

bitflags! {
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
struct Branch: u8 {
const L = 1 << 0;
const A = 1 << 1;
const O = 1 << 2;
const F = 1 << 3;
}
}

impl Branch {
pub fn get_requests(&self) -> RuntimeGlobals {
match *self {
f if f == Branch::F => {
RuntimeGlobals::REQUIRE | RuntimeGlobals::EXPORTS | RuntimeGlobals::MODULE
}
o if o == Branch::O => RuntimeGlobals::MODULE,
o_f if o_f == (Branch::O | Branch::F) => {
RuntimeGlobals::REQUIRE | RuntimeGlobals::EXPORTS | RuntimeGlobals::MODULE
}
a_f if a_f == (Branch::A | Branch::F) => RuntimeGlobals::EXPORTS | RuntimeGlobals::MODULE,
a_o if a_o == (Branch::A | Branch::O) => RuntimeGlobals::MODULE,
a_o_f if a_o_f == (Branch::A | Branch::O | Branch::F) => {
RuntimeGlobals::EXPORTS | RuntimeGlobals::MODULE
}
l_f if l_f == (Branch::L | Branch::F) => RuntimeGlobals::REQUIRE | RuntimeGlobals::MODULE,
l_o if l_o == (Branch::L | Branch::O) => RuntimeGlobals::empty(),
l_o_f if l_o_f == (Branch::L | Branch::O | Branch::F) => {
RuntimeGlobals::REQUIRE | RuntimeGlobals::MODULE
}
l_a_f if l_a_f == (Branch::L | Branch::A | Branch::F) => RuntimeGlobals::empty(),
l_a_o if l_a_o == (Branch::L | Branch::A | Branch::O) => RuntimeGlobals::empty(),
l_a_o_f if l_a_o_f == (Branch::L | Branch::A | Branch::O | Branch::F) => {
RuntimeGlobals::empty()
}
_ => RuntimeGlobals::empty(),
}
}

pub fn get_definition(&self, local_module_var: &Option<String>) -> String {
let name = match local_module_var {
Some(name) => name,
None => "XXX",
};
match *self {
f if f == Branch::F => "var __WEBPACK_AMD_DEFINE_RESULT__;".to_string(),
o if o == Branch::O => "".to_string(),
o_f if o_f == (Branch::O | Branch::F) => {
"var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;".to_string()
}
a_f if a_f == (Branch::A | Branch::F) => {
"var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;".to_string()
}
a_o if a_o == (Branch::A | Branch::O) => "".to_string(),
a_o_f if a_o_f == (Branch::A | Branch::O | Branch::F) => "var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;".to_string(),
l_f if l_f == (Branch::L | Branch::F) => {
format!("var {}, {}module;", name, name)
},
l_o if l_o == (Branch::L | Branch::O) => {
format!("var {};", name)
},
l_o_f if l_o_f == (Branch::L | Branch::O | Branch::F) => {
format!("var {}, {}factory, {}module;", name, name, name)
},
l_a_f if l_a_f == (Branch::L | Branch::A | Branch::F) => {
format!("var __WEBPACK_AMD_DEFINE_ARRAY__, {}, {}exports;", name, name)
},
l_a_o if l_a_o == (Branch::L | Branch::A | Branch::O)=> {
format!("var {};", name)
},
l_a_o_f if l_a_o_f == (Branch::L | Branch::A | Branch::O | Branch::F) => {
format!("var {}array, {}factory, {}exports, {};", name, name, name, name)
},
_ => "".to_string(),
}
}

pub fn get_content(
&self,
local_module_var: &Option<String>,
named_module: &Option<Atom>,
) -> String {
let local_module_var = match local_module_var {
Some(name) => name,
None => "XXX",
};
let named_module = match named_module {
Some(name) => name,
None => "YYY",
};
match *self {
f if f == Branch::F => {
format!(
"!(__WEBPACK_AMD_DEFINE_RESULT__ = (#).call(exports, {require}, exports, module),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))",
require = RuntimeGlobals::REQUIRE.name()
)
}
o if o == Branch::O => "!(module.exports = #)".to_string(),
o_f if o_f == (Branch::O | Branch::F) => {
format!(
"!(__WEBPACK_AMD_DEFINE_FACTORY__ = (#),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, {require}, exports, module)) :
__WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))",
require = RuntimeGlobals::REQUIRE.name()
)
}
a_f if a_f == (Branch::A | Branch::F) => "!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, __WEBPACK_AMD_DEFINE_RESULT__ = (#).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))".to_string(),
a_o if a_o == (Branch::A | Branch::O) => "!(#, module.exports = #)".to_string(),
a_o_f if a_o_f == (Branch::A | Branch::O | Branch::F) => {
"!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, __WEBPACK_AMD_DEFINE_FACTORY__ = (#),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))".to_string()
}
l_f if l_f == (Branch::L | Branch::F) => {
format!(
"!({var_name}module = {{ id: {module_id}, exports: {{}}, loaded: false }}, {var_name} = (#).call({var_name}module.exports, {require}, {var_name}module.exports, {var_name}module), {var_name}module.loaded = true, {var_name} === undefined && ({var_name} = {var_name}module.exports))",
var_name = local_module_var,
module_id = json_stringify(named_module),
require = RuntimeGlobals::REQUIRE.name(),
)
}
l_o if l_o == (Branch::L | Branch::O) => format!("!({} = #)", local_module_var),
l_o_f if l_o_f == (Branch::L | Branch::O | Branch::F) => {
format!(
"!({var_name}factory = (#), (typeof {var_name}factory === 'function' ? (({var_name}module = {{ id: {module_id}, exports: {{}}, loaded: false }}), ({var_name} = {var_name}factory.call({var_name}module.exports, {require}, {var_name}module.exports, {var_name}module)), ({var_name}module.loaded = true), {var_name} === undefined && ({var_name} = {var_name}module.exports)) : {var_name} = {var_name}factory))",
var_name = local_module_var,
module_id = json_stringify(named_module),
require = RuntimeGlobals::REQUIRE.name(),
)
}
l_a_f if l_a_f == (Branch::L | Branch::A | Branch::F) => format!("!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, {} = (#).apply({}exports = {{}}, __WEBPACK_AMD_DEFINE_ARRAY__), {} === undefined && ({} = {}exports))", local_module_var, local_module_var, local_module_var, local_module_var, local_module_var),
l_a_o if l_a_o == (Branch::L | Branch::A | Branch::O) => format!("!(#, {} = #)", local_module_var),
l_a_o_f if l_a_o_f == (Branch::L | Branch::A | Branch::O | Branch::F) => format!(
"!({var_name}array = #, {var_name}factory = (#),
(typeof {var_name}factory === 'function' ?
(({var_name} = {var_name}factory.apply({var_name}exports = {{}}, {var_name}array)), {var_name} === undefined && ({var_name} = {var_name}exports)) :
({var_name} = {var_name}factory)
))",
var_name = local_module_var,
),
_ => "".to_string(),
}
}
}

#[derive(Debug, Clone)]
pub struct AmdDefineDependency {
id: DependencyId,
range: (u32, u32),
array_range: Option<(u32, u32)>,
function_range: Option<(u32, u32)>,
object_range: Option<(u32, u32)>,
named_module: Option<Atom>,
local_module: Option<LocalModule>,
}

impl AmdDefineDependency {
pub fn new(
range: (u32, u32),
array_range: Option<(u32, u32)>,
function_range: Option<(u32, u32)>,
object_range: Option<(u32, u32)>,
named_module: Option<Atom>,
local_module: Option<LocalModule>,
) -> Self {
Self {
id: DependencyId::new(),
range,
array_range,
function_range,
object_range,
named_module,
local_module,
}
}

pub fn get_local_module_mut(&mut self) -> Option<&mut LocalModule> {
self.local_module.as_mut()
}
}

impl Dependency for AmdDefineDependency {
fn id(&self) -> &DependencyId {
&self.id
}

fn category(&self) -> &DependencyCategory {
&DependencyCategory::Amd
}

fn dependency_type(&self) -> &DependencyType {
&DependencyType::AmdDefine
}

fn could_affect_referencing_module(&self) -> AffectType {
AffectType::False
}
}

impl AmdDefineDependency {
fn local_module_var(&self) -> Option<String> {
self.local_module.as_ref().and_then(|m| {
if m.is_used() {
Some(m.variable_name())
} else {
None
}
})
}

fn branch(&self) -> Branch {
let mut ret = Branch::empty();
if self.local_module.as_ref().is_some_and(|m| m.is_used()) {
ret |= Branch::L;
}
if self.array_range.is_some() {
ret |= Branch::A;
}
if self.object_range.is_some() {
ret |= Branch::O;
}
if self.function_range.is_some() {
ret |= Branch::F;
}
ret
}
}

impl DependencyTemplate for AmdDefineDependency {
fn apply(
&self,
source: &mut TemplateReplaceSource,
code_generatable_context: &mut TemplateContext,
) {
let branch = self.branch();
code_generatable_context
.runtime_requirements
.insert(branch.get_requests());

let local_module_var = self.local_module_var();

let text = branch.get_content(&local_module_var, &self.named_module);
let definition = branch.get_definition(&local_module_var);

let mut texts = text.split('#');

if !definition.is_empty() {
source.insert(0, &definition, None);
}

let mut current = self.range.0;
if let Some(array_range) = self.array_range {
source.replace(current, array_range.0, texts.next().unwrap_or(""), None);
current = array_range.1;
}

if let Some(object_range) = self.object_range {
source.replace(current, object_range.0, texts.next().unwrap_or(""), None);
current = object_range.1;
} else if let Some(function_range) = self.function_range {
source.replace(current, function_range.0, texts.next().unwrap_or(""), None);
current = function_range.1;
}

source.replace(current, self.range.1, texts.next().unwrap_or(""), None);

if texts.next().is_some() {
panic!("Implementation error");
}
}

fn dependency_id(&self) -> Option<DependencyId> {
Some(self.id)
}

fn update_hash(
&self,
_hasher: &mut dyn std::hash::Hasher,
_compilation: &Compilation,
_runtime: Option<&RuntimeSpec>,
) {
}
}

impl AsModuleDependency for AmdDefineDependency {}

impl AsContextDependency for AmdDefineDependency {}
Loading

2 comments on commit 96f500d

@rspack-bot
Copy link

Choose a reason for hiding this comment

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

📝 Ran ecosystem CI: Open

suite result
modernjs ✅ success
_selftest ✅ success
rsdoctor ❌ failure
rspress ✅ success
rslib ❌ failure
rsbuild ✅ success
examples ✅ success
devserver ✅ success
nuxt ✅ success

@rspack-bot
Copy link

Choose a reason for hiding this comment

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

📝 Benchmark detail: Open

Name Base (2024-11-27 c1e8f11) Current Change
10000_big_production-mode_disable-minimize + exec 40.2 s ± 1.43 s 40.1 s ± 927 ms -0.25 %
10000_development-mode + exec 1.82 s ± 32 ms 1.83 s ± 52 ms +0.66 %
10000_development-mode_hmr + exec 644 ms ± 15 ms 640 ms ± 7.1 ms -0.59 %
10000_production-mode + exec 2.41 s ± 42 ms 2.4 s ± 40 ms -0.39 %
arco-pro_development-mode + exec 1.79 s ± 61 ms 1.76 s ± 68 ms -1.66 %
arco-pro_development-mode_hmr + exec 429 ms ± 2.1 ms 429 ms ± 1.3 ms -0.08 %
arco-pro_production-mode + exec 3.1 s ± 93 ms 3.11 s ± 75 ms +0.43 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 3.17 s ± 67 ms 3.14 s ± 99 ms -0.97 %
threejs_development-mode_10x + exec 1.62 s ± 16 ms 1.62 s ± 8.4 ms -0.12 %
threejs_development-mode_10x_hmr + exec 811 ms ± 12 ms 814 ms ± 8.6 ms +0.30 %
threejs_production-mode_10x + exec 4.9 s ± 36 ms 4.91 s ± 41 ms +0.15 %
10000_big_production-mode_disable-minimize + rss memory 12451 MiB ± 95.7 MiB 12772 MiB ± 168 MiB +2.58 %
10000_development-mode + rss memory 742 MiB ± 18.2 MiB 757 MiB ± 23 MiB +2.03 %
10000_development-mode_hmr + rss memory 1578 MiB ± 403 MiB 1501 MiB ± 324 MiB -4.84 %
10000_production-mode + rss memory 679 MiB ± 64.6 MiB 646 MiB ± 32.9 MiB -4.78 %
arco-pro_development-mode + rss memory 701 MiB ± 43.4 MiB 690 MiB ± 33.3 MiB -1.67 %
arco-pro_development-mode_hmr + rss memory 871 MiB ± 88.7 MiB 868 MiB ± 67.6 MiB -0.35 %
arco-pro_production-mode + rss memory 876 MiB ± 56.8 MiB 851 MiB ± 68.4 MiB -2.84 %
arco-pro_production-mode_generate-package-json-webpack-plugin + rss memory 859 MiB ± 52.9 MiB 870 MiB ± 34.2 MiB +1.27 %
threejs_development-mode_10x + rss memory 785 MiB ± 49.8 MiB 797 MiB ± 40.3 MiB +1.51 %
threejs_development-mode_10x_hmr + rss memory 1478 MiB ± 164 MiB 1617 MiB ± 222 MiB +9.38 %
threejs_production-mode_10x + rss memory 1044 MiB ± 66.8 MiB 1057 MiB ± 50.4 MiB +1.31 %

Please sign in to comment.