Skip to content

Commit

Permalink
fix: preserve star export from external module
Browse files Browse the repository at this point in the history
  • Loading branch information
fi3ework committed Oct 28, 2024
1 parent 20f00c0 commit f1e00c4
Show file tree
Hide file tree
Showing 13 changed files with 307 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ impl ESMExportImportedSpecifierDependency {
}
}

pub fn reexport_star_from_external_module(&self, mg: &ModuleGraph) -> bool {
if let Some(m) = mg.get_module_by_dependency_id(&self.id) {
if let Some(m) = m.as_external_module() {
if m.get_external_type() == "module" || m.get_external_type() == "module-import" {
// Star reexport will meet the condition.
return self.name.is_none() && self.other_star_exports.is_some();
}
}
}

false
}

// Because it is shared by multiply ESMExportImportedSpecifierDependency, so put it to `BuildInfo`
pub fn active_exports<'a>(&self, module_graph: &'a ModuleGraph) -> &'a HashSet<Atom> {
let build_info = module_graph
Expand Down Expand Up @@ -267,6 +280,12 @@ impl ESMExportImportedSpecifierDependency {
export_mode.items = Some(items);
export_mode
} else {
if self.reexport_star_from_external_module(module_graph) {
let mut export_mode = ExportMode::new(ExportModeType::ReexportFromExternalModule);
export_mode.name = Some("*".into());
return export_mode;
}

let mut export_mode = ExportMode::new(ExportModeType::DynamicReexport);
export_mode.ignored = Some(ignored_exports);
export_mode.hidden = hidden;
Expand Down Expand Up @@ -709,6 +728,7 @@ impl ESMExportImportedSpecifierDependency {
.boxed(),
);
}
ExportModeType::ReexportFromExternalModule => {}
}
ctxt.init_fragments.extend(fragments);
}
Expand Down Expand Up @@ -1216,6 +1236,7 @@ impl Dependency for ESMExportImportedSpecifierDependency {
..Default::default()
})
}
ExportModeType::ReexportFromExternalModule => None,
}
}

Expand Down Expand Up @@ -1334,6 +1355,7 @@ impl Dependency for ESMExportImportedSpecifierDependency {
.map(ExtendedReferencedExport::Array)
.collect::<Vec<_>>()
}
ExportModeType::ReexportFromExternalModule => Vec::new(),
}
}

Expand Down Expand Up @@ -1430,6 +1452,7 @@ pub enum ExportModeType {
ReexportUndefined,
NormalReexport,
DynamicReexport,
ReexportFromExternalModule,
}

#[derive(Debug)]
Expand Down
6 changes: 4 additions & 2 deletions crates/rspack_plugin_library/src/modern_module/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod dependency;
mod import_dependency;
mod reexport_star_external_dependency;

pub use dependency::*;
pub use import_dependency::*;
pub use reexport_star_external_dependency::*;
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use rspack_core::{
AsContextDependency, Dependency, InitFragmentExt, InitFragmentKey, InitFragmentStage,
NormalInitFragment,
};
use rspack_core::{Compilation, DependencyType, ExternalRequest, ExternalType, 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 ModernModuleReexportStarExternalDependency {
id: DependencyId,
request: Atom,
target_request: ExternalRequest,
external_type: ExternalType,
resource_identifier: String,
}

impl ModernModuleReexportStarExternalDependency {
pub fn new(request: Atom, target_request: ExternalRequest, external_type: ExternalType) -> Self {
let resource_identifier = create_resource_identifier_for_esm_dependency(request.as_str(), None);
Self {
request,
target_request,
external_type,
id: DependencyId::new(),
resource_identifier,
}
}
}

impl Dependency for ModernModuleReexportStarExternalDependency {
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 could_affect_referencing_module(&self) -> rspack_core::AffectType {
rspack_core::AffectType::True
}
}

impl ModuleDependency for ModernModuleReexportStarExternalDependency {
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 ModernModuleReexportStarExternalDependency {
fn apply(
&self,
_source: &mut TemplateReplaceSource,
code_generatable_context: &mut TemplateContext,
) {
let request = match &self.target_request {
ExternalRequest::Single(request) => Some(request),
ExternalRequest::Map(map) => map.get(&self.external_type),
};

if let Some(request) = request {
let chunk_init_fragments = code_generatable_context.chunk_init_fragments();
chunk_init_fragments.push(
NormalInitFragment::new(
format!(
"export * from {};\n",
serde_json::to_string(request.primary()).expect("invalid json to_string")
),
InitFragmentStage::StageESMImports,
0,
InitFragmentKey::Const(format!("modern_module_reexport_star_{}", self.request)),
None,
)
.boxed(),
);
}
}

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 ModernModuleReexportStarExternalDependency {}
77 changes: 70 additions & 7 deletions crates/rspack_plugin_library/src/modern_module_library_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ use rspack_core::{
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::dependency::{
ESMExportImportedSpecifierDependency, 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 super::modern_module::ModernModuleReexportStarExternalDependency;
use crate::modern_module::ModernModuleImportDependency;
use crate::utils::{get_options_for_chunk, COMMON_LIBRARY_NAME_MESSAGE};

const PLUGIN_NAME: &str = "rspack.ModernModuleLibraryPlugin";
Expand Down Expand Up @@ -198,12 +201,12 @@ async fn finish_modules(&self, compilation: &mut Compilation) -> Result<()> {
let modules = mg.modules();
let module_ids = modules.keys().cloned().collect::<Vec<_>>();

for module_id in module_ids {
// Remove `import()` runtime.
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 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 {
Expand Down Expand Up @@ -257,6 +260,66 @@ async fn finish_modules(&self, compilation: &mut Compilation) -> Result<()> {
}
}

// Reexport star from external module.
for module_id in &module_ids {
// Only preserve star reexports for module graph entry, nested reexports are not supported.
if let Some(mgm) = mg.module_graph_module_by_identifier(module_id) {
let is_mg_entry = mgm.issuer().get_module(&mg).is_none();
if !is_mg_entry {
continue;
}
}

let mut new_deps = Vec::new();
let module = mg.module_by_identifier(module_id).expect("should have mgm");
let connections = mg.get_outgoing_connections(module_id);
let dep_ids = module.get_dependencies();

for dep_id in dep_ids {
if let Some(export_dep) = mg.dependency_by_id(dep_id) {
if let Some(reexport_dep) = export_dep
.as_any()
.downcast_ref::<ESMExportImportedSpecifierDependency>()
{
if reexport_dep.reexport_star_from_external_module(&mg) {
let reexport_connection = connections
.iter()
.find(|c| c.dependency_id == reexport_dep.id);

if let Some(reexport_connection) = reexport_connection {
let import_module_id = reexport_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() {
if reexport_dep.request == external_module.user_request {
let new_dep = ModernModuleReexportStarExternalDependency::new(
reexport_dep.request.as_str().into(),
external_module.request.clone(),
external_module.external_type.clone(),
);

new_deps.push((module_id, new_dep.clone()));
}
}
}
}
}
}
}

for (module_id, new_dep) in new_deps.iter() {
let importer = mg
.module_by_identifier_mut(module_id)
.expect("should have module");

let boxed_dep = Box::new(new_dep.clone()) as BoxDependency;
importer.add_dependency_id(*new_dep.id());
mg.add_dependency(boxed_dep);
}
}

Ok(())
}

Expand Down
47 changes: 47 additions & 0 deletions packages/rspack-test-tools/tests/__snapshots__/Config.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,53 @@ div {
head{--webpack--120:&_13;}
`;
exports[`config config/externals/reexport-star exported tests reexport star from external module 1`] = `
import * as __WEBPACK_EXTERNAL_MODULE_external1_alias__ from "external1-alias";
import * as __WEBPACK_EXTERNAL_MODULE_external2_alias__ from "external2-alias";
export * from "external1-alias";
export * from "external2-alias";
;// CONCATENATED MODULE: external "external1-alias"
;// CONCATENATED MODULE: external "external2-alias"
;// CONCATENATED MODULE: ./case1.js
`;
exports[`config config/externals/reexport-star exported tests reexport star from external module 2`] = `
import * as __WEBPACK_EXTERNAL_MODULE_external1_alias__ from "external1-alias";
export * from "external1-alias";
;// CONCATENATED MODULE: external "external1-alias"
;// CONCATENATED MODULE: ./case2.js
var __webpack_exports__a = __WEBPACK_EXTERNAL_MODULE_external1_alias__.a;
var __webpack_exports__b = __WEBPACK_EXTERNAL_MODULE_external1_alias__.b;
export { __WEBPACK_EXTERNAL_MODULE_external1_alias__ as n1, __webpack_exports__a as a, __webpack_exports__b as b };
`;
exports[`config config/externals/reexport-star exported tests reexport star from external module 3`] = `
import * as __WEBPACK_EXTERNAL_MODULE_external1_alias__ from "external1-alias";
;// CONCATENATED MODULE: external "external1-alias"
;// CONCATENATED MODULE: ./case3/foo.js
const foo = 2
;// CONCATENATED MODULE: ./case3/index.js
const bar = 1
export { bar, foo };
`;
exports[`config config/library/modern-module-force-concaten step should pass: .cjs should bail out 1`] = `
var __webpack_modules__ = ({
"851": (function (module) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from 'external1'
export * from 'external2'
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { b } from 'external1'
export * from 'external1'
export { a } from 'external1'
export { b }
export * as n1 from 'external1'
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from 'external1'
export const foo = 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { foo } from './foo'
export const bar = 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const fs = require("fs");
const path = require("path");
const readCase = (name)=> fs.readFileSync(path.resolve(__dirname, `${name}.mjs`), "utf-8");

it("reexport star from external module", function () {
expect(readCase("case1")).toMatchSnapshot();
expect(readCase("case2")).toMatchSnapshot();
expect(readCase("case3")).toMatchSnapshot();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** @type {import("@rspack/core").Configuration} */
module.exports = [
{
entry: {
"case1": "./case1.js",
"case2": "./case2.js",
"case3": "./case3/index.js",
"index": "./index.js",
},
output: {
module: true,
filename: "[name].mjs",
chunkFormat: "module",
library: {
type: "modern-module"
},
},
externals: {
external1: "module external1-alias",
external2: "module-import external2-alias",
},
experiments: {
outputModule: true
},
},
];
Loading

0 comments on commit f1e00c4

Please sign in to comment.