Skip to content

Commit

Permalink
feat(modern-module): make dynamic import runtime-less (#7759)
Browse files Browse the repository at this point in the history
  • Loading branch information
fi3ework authored Sep 5, 2024
1 parent dbc5223 commit d9a6d59
Show file tree
Hide file tree
Showing 15 changed files with 330 additions and 8 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions crates/rspack_core/src/dependencies_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ impl DependenciesBlock for AsyncDependenciesBlock {
}
}

impl AsyncDependenciesBlock {
pub fn remove_dependency_id(&mut self, dependency: DependencyId) {
self.dependency_ids.retain(|dep| dep != &dependency);
}
}

#[derive(Debug, Error, Diagnostic)]
#[diagnostic(code(AsyncDependencyToInitialChunkError))]
#[error("It's not allowed to load an initial chunk on demand. The chunk name \"{0}\" is already used by an entrypoint.")]
Expand Down
6 changes: 3 additions & 3 deletions crates/rspack_core/src/external_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub enum ExternalRequest {

#[derive(Debug, Clone)]
pub struct ExternalRequestValue {
primary: String,
pub primary: String,
rest: Option<Vec<String>>,
}

Expand Down Expand Up @@ -121,9 +121,9 @@ pub struct ExternalModule {
blocks: Vec<AsyncDependenciesBlockIdentifier>,
id: Identifier,
pub request: ExternalRequest,
external_type: ExternalType,
pub external_type: ExternalType,
/// Request intended by user (without loaders from config)
user_request: String,
pub user_request: String,
factory_meta: Option<FactoryMeta>,
build_info: Option<BuildInfo>,
build_meta: Option<BuildMeta>,
Expand Down
16 changes: 16 additions & 0 deletions crates/rspack_core/src/module_graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,22 @@ impl<'a> ModuleGraph<'a> {
.map(|b| &**b)
}

pub fn block_by_id_mut(
&mut self,
block_id: &AsyncDependenciesBlockIdentifier,
) -> Option<&mut Box<AsyncDependenciesBlock>> {
self
.loop_partials_mut(
|p| p.blocks.contains_key(block_id),
|p, search_result| {
p.blocks.insert(*block_id, search_result);
},
|p| p.blocks.get(block_id).cloned(),
|p| p.blocks.get_mut(block_id),
)?
.as_mut()
}

pub fn block_by_id_expect(
&self,
block_id: &AsyncDependenciesBlockIdentifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ pub fn create_import_dependency_referenced_exports(
#[derive(Debug, Clone)]
pub struct ImportDependency {
id: DependencyId,
request: Atom,
range: RealDependencyLocation,
pub request: Atom,
pub range: RealDependencyLocation,
referenced_exports: Option<Vec<Atom>>,
attributes: Option<ImportAttributes>,
resource_identifier: String,
Expand Down
12 changes: 12 additions & 0 deletions crates/rspack_plugin_library/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ rspack_util = { version = "0.1.0", path = "../rspack_util" }
rustc-hash = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true }
swc_core = { workspace = true, features = [
"__parser",
"__utils",
"common_sourcemap",
"ecma_preset_env",
"ecma_transforms_optimization",
"ecma_transforms_module",
"ecma_transforms_compat",
"ecma_transforms_typescript",
"base",
"ecma_quote",
] }

[package.metadata.cargo-shear]
ignored = ["tracing"]
1 change: 1 addition & 0 deletions crates/rspack_plugin_library/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
mod amd_library_plugin;
mod assign_library_plugin;
mod export_property_library_plugin;
mod modern_module;
mod modern_module_library_plugin;
mod module_library_plugin;
mod system_library_plugin;
Expand Down
127 changes: 127 additions & 0 deletions crates/rspack_plugin_library/src/modern_module/dependency.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use rspack_core::{AsContextDependency, Dependency};
use rspack_core::{
Compilation, DependencyType, ErrorSpan, ExternalRequest, ExternalType, ImportAttributes,
RealDependencyLocation, RuntimeSpec,
};
use rspack_core::{DependencyCategory, DependencyId, DependencyTemplate};
use rspack_core::{ModuleDependency, TemplateContext, TemplateReplaceSource};
use rspack_plugin_javascript::dependency::create_resource_identifier_for_esm_dependency;
use swc_core::ecma::atoms::Atom;

#[derive(Debug, Clone)]
pub struct ModernModuleImportDependency {
id: DependencyId,
request: Atom,
target_request: ExternalRequest,
external_type: ExternalType,
range: RealDependencyLocation,
attributes: Option<ImportAttributes>,
resource_identifier: String,
}

impl ModernModuleImportDependency {
pub fn new(
request: Atom,
target_request: ExternalRequest,
external_type: ExternalType,
range: RealDependencyLocation,
attributes: Option<ImportAttributes>,
) -> Self {
let resource_identifier =
create_resource_identifier_for_esm_dependency(request.as_str(), attributes.as_ref());
Self {
request,
target_request,
external_type,
range,
id: DependencyId::new(),
attributes,
resource_identifier,
}
}
}

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

fn resource_identifier(&self) -> Option<&str> {
Some(&self.resource_identifier)
}

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

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

fn get_attributes(&self) -> Option<&ImportAttributes> {
self.attributes.as_ref()
}

fn span(&self) -> Option<ErrorSpan> {
Some(ErrorSpan::new(self.range.start, self.range.end))
}

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

impl ModuleDependency for ModernModuleImportDependency {
fn request(&self) -> &str {
&self.request
}

fn user_request(&self) -> &str {
&self.request
}

fn set_request(&mut self, request: String) {
self.request = request.into();
}
}

impl DependencyTemplate for ModernModuleImportDependency {
fn apply(
&self,
source: &mut TemplateReplaceSource,
_code_generatable_context: &mut TemplateContext,
) {
let request_and_external_type = match &self.target_request {
ExternalRequest::Single(request) => (Some(request), &self.external_type),
ExternalRequest::Map(map) => (map.get(&self.external_type), &self.external_type),
};

if let Some(request_and_external_type) = request_and_external_type.0 {
source.replace(
self.range.start,
self.range.end,
format!(
"import({})",
serde_json::to_string(request_and_external_type.primary())
.expect("invalid json to_string")
)
.as_str(),
None,
);
}
}

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 AsContextDependency for ModernModuleImportDependency {}
3 changes: 3 additions & 0 deletions crates/rspack_plugin_library/src/modern_module/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod dependency;

pub use dependency::*;
81 changes: 78 additions & 3 deletions crates/rspack_plugin_library/src/modern_module_library_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ use std::hash::Hash;
use rspack_core::rspack_sources::{ConcatSource, RawSource, SourceExt};
use rspack_core::{
merge_runtime, to_identifier, ApplyContext, ChunkUkey, CodeGenerationExportsFinalNames,
Compilation, CompilationOptimizeChunkModules, CompilationParams, CompilerCompilation,
CompilerOptions, ConcatenatedModule, ConcatenatedModuleExportsDefinitions, LibraryOptions,
ModuleIdentifier, Plugin, PluginContext,
Compilation, CompilationFinishModules, CompilationOptimizeChunkModules, CompilationParams,
CompilerCompilation, CompilerOptions, ConcatenatedModule, ConcatenatedModuleExportsDefinitions,
DependenciesBlock, Dependency, LibraryOptions, ModuleIdentifier, Plugin, PluginContext,
};
use rspack_error::{error_bail, Result};
use rspack_hash::RspackHash;
use rspack_hook::{plugin, plugin_hook};
use rspack_plugin_javascript::dependency::ImportDependency;
use rspack_plugin_javascript::ModuleConcatenationPlugin;
use rspack_plugin_javascript::{
ConcatConfiguration, JavascriptModulesChunkHash, JavascriptModulesRenderStartup, JsPlugin,
RenderSource,
};
use rustc_hash::FxHashSet as HashSet;

use super::modern_module::ModernModuleImportDependency;
use crate::utils::{get_options_for_chunk, COMMON_LIBRARY_NAME_MESSAGE};

const PLUGIN_NAME: &str = "rspack.ModernModuleLibraryPlugin";
Expand Down Expand Up @@ -189,6 +191,74 @@ fn render_startup(
Ok(())
}

#[plugin_hook(CompilationFinishModules for ModernModuleLibraryPlugin)]
async fn finish_modules(&self, compilation: &mut Compilation) -> Result<()> {
let mut mg = compilation.get_module_graph_mut();
let modules = mg.modules();
let module_ids = modules.keys().cloned().collect::<Vec<_>>();

for module_id in module_ids {
let mut deps_to_replace = Vec::new();
let module = mg
.module_by_identifier(&module_id)
.expect("should have mgm");
let connections = mg.get_outgoing_connections(&module_id);
let block_ids = module.get_blocks();

for block_id in block_ids {
let block = mg.block_by_id(block_id).expect("should have block");
for block_dep_id in block.get_dependencies() {
let block_dep = mg.dependency_by_id(block_dep_id);
if let Some(block_dep) = block_dep {
if let Some(import_dependency) = block_dep.as_any().downcast_ref::<ImportDependency>() {
let import_dep_connection = connections
.iter()
.find(|c| c.dependency_id == *block_dep_id);

// Try find the connection with a import dependency pointing to an external module.
// If found, remove the connection and add a new import dependency to performs the external module ID replacement.
if let Some(import_dep_connection) = import_dep_connection {
let import_module_id = import_dep_connection.module_identifier();
let import_module = mg
.module_by_identifier(import_module_id)
.expect("should have mgm");

if let Some(external_module) = import_module.as_external_module() {
let new_dep = ModernModuleImportDependency::new(
import_dependency.request.as_str().into(),
external_module.request.clone(),
external_module.external_type.clone(),
import_dependency.range.clone(),
None,
);

deps_to_replace.push((
*block_id,
block_dep.clone(),
new_dep.clone(),
import_dep_connection.id,
));
}
}
}
}
}
}

for (block_id, dep, new_dep, connection_id) in deps_to_replace.iter() {
let block = mg.block_by_id_mut(block_id).expect("should have block");
let dep_id = dep.id();
block.remove_dependency_id(*dep_id);
let boxed_dep = Box::new(new_dep.clone()) as Box<dyn rspack_core::Dependency>;
block.add_dependency_id(*new_dep.id());
mg.add_dependency(boxed_dep);
mg.revoke_connection(connection_id, true);
}
}

Ok(())
}

#[plugin_hook(JavascriptModulesChunkHash for ModernModuleLibraryPlugin)]
async fn js_chunk_hash(
&self,
Expand Down Expand Up @@ -256,6 +326,11 @@ impl Plugin for ModernModuleLibraryPlugin {
.compilation_hooks
.optimize_chunk_modules
.tap(optimize_chunk_modules::new(self));
ctx
.context
.compilation_hooks
.finish_modules
.tap(finish_modules::new(self));

Ok(())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { lit } from 'lit' // 'module' + 'import' externalized
import { svelte } from 'svelte' // 'module' externalized

export default dynamic = async () => {
const litNs = await import('lit') // 'module' + 'import' externalized
const solidNs = await import('solid') // 'import' externalized
console.log(svelte, lit, litNs, solidNs)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const fs = require("fs");
const path = require("path");

it("modern-module-dynamic-import-runtime", () => {
const initialChunk = fs.readFileSync(path.resolve(__dirname, "main.js"), "utf-8");
const asyncChunk = fs.readFileSync(path.resolve(__dirname, "async.js"), "utf-8");

expect(initialChunk).toContain('import * as __WEBPACK_EXTERNAL_MODULE_lit_alias__ from "lit-alias"');
expect(initialChunk).toContain('import * as __WEBPACK_EXTERNAL_MODULE_svelte_alias__ from "svelte-alias"');
expect(initialChunk).toContain('import * as __WEBPACK_EXTERNAL_MODULE_react_alias__ from "react-alias"');
expect(initialChunk).toContain('import * as __WEBPACK_EXTERNAL_MODULE_angular_alias__ from "angular-alias"');
expect(initialChunk).toContain('const reactNs = await import("react-alias")');
expect(initialChunk).toContain('const vueNs = await import("vue-alias")');

expect(asyncChunk).toContain('const litNs = await import("lit-alias")');
expect(asyncChunk).toContain('const solidNs = await import("solid-alias")');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { react } from 'react' // 'module' + 'import' externalized
import { angular } from 'angular' // 'module' externalized

export const main = async () => {
const dyn = await import('./dyn.js') // lazy dynamic import
const reactNs = await import('react') // 'module' + 'import' externalized
const vueNs = await import('vue') // 'import' externalized
console.log(angular, react, reactNs, vueNs, dyn)
}
Loading

2 comments on commit d9a6d59

@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-09-05 976f5df) Current Change
10000_development-mode + exec 2.22 s ± 31 ms 2.23 s ± 14 ms +0.43 %
10000_development-mode_hmr + exec 684 ms ± 12 ms 683 ms ± 11 ms -0.16 %
10000_production-mode + exec 2.84 s ± 44 ms 2.85 s ± 46 ms +0.16 %
arco-pro_development-mode + exec 1.83 s ± 54 ms 1.85 s ± 72 ms +0.92 %
arco-pro_development-mode_hmr + exec 435 ms ± 3 ms 434 ms ± 3.7 ms -0.15 %
arco-pro_production-mode + exec 3.24 s ± 88 ms 3.23 s ± 67 ms -0.25 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 3.36 s ± 68 ms 3.31 s ± 89 ms -1.43 %
threejs_development-mode_10x + exec 1.65 s ± 14 ms 1.67 s ± 20 ms +1.07 %
threejs_development-mode_10x_hmr + exec 798 ms ± 4.1 ms 816 ms ± 9.8 ms +2.26 %
threejs_production-mode_10x + exec 5.15 s ± 46 ms 5.18 s ± 36 ms +0.48 %

@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
nx ❌ failure
rspress ✅ success
rslib ✅ success
rsbuild ✅ success
examples ✅ success

Please sign in to comment.