Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions crates/rspack_core/src/utils/property_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,17 @@ pub fn property_name(prop: &str) -> Result<Cow<'_, str>> {
.map(Cow::from)
}
}

/// Returns the name quoted for ESM export/import specifiers.
/// Unlike `property_name`, this function allows reserved words like "default"
/// since they are valid in export/import context (e.g., `export { foo as default }`).
/// Only quotes names that are not valid JavaScript identifiers (e.g., "a.b" -> "\"a.b\"").
pub fn export_name(name: &str) -> Result<Cow<'_, str>> {
if SAFE_IDENTIFIER.is_match(name) {
Ok(Cow::from(name))
} else {
serde_json::to_string(name)
.to_rspack_result()
.map(Cow::from)
}
}
56 changes: 39 additions & 17 deletions crates/rspack_plugin_esm_library/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use rspack_collections::{IdentifierIndexSet, UkeyIndexMap, UkeySet};
use rspack_core::{
AssetInfo, Chunk, ChunkGraph, ChunkRenderContext, ChunkUkey, CodeGenerationDataFilename,
Compilation, ConcatenatedModuleInfo, DependencyId, InitFragment, ModuleIdentifier, PathData,
PathInfo, RuntimeGlobals, RuntimeVariable, SourceType, get_js_chunk_filename_template,
get_undo_path, render_init_fragments,
PathInfo, RuntimeGlobals, RuntimeVariable, SourceType, export_name,
get_js_chunk_filename_template, get_undo_path, render_init_fragments,
rspack_sources::{ConcatSource, RawStringSource, ReplaceSource, Source, SourceExt},
};
use rspack_error::Result;
Expand Down Expand Up @@ -374,10 +374,12 @@ var {} = {{}};
} else {
let mut imports = Vec::new();
for (atom, local) in symbols.atoms.iter() {
let atom_name = export_name(atom).expect("should have export_name");
if atom == local {
imports.push(atom.to_string());
imports.push(atom_name.into_owned());
} else {
imports.push(format!("{atom} as {local}"));
let local_name = export_name(local).expect("should have export_name");
imports.push(format!("{atom_name} as {local_name}"));
}
}
format!(
Expand Down Expand Up @@ -436,10 +438,12 @@ var {} = {{}};
imported
.iter()
.map(|(imported, local)| {
let imported_name = export_name(imported).expect("should have export_name");
if imported == local {
Cow::Borrowed(imported.as_str())
imported_name.into_owned()
} else {
Cow::Owned(format!("{imported} as {local}"))
let local_name = export_name(local).expect("should have export_name");
format!("{imported_name} as {local_name}")
}
})
.collect::<Vec<_>>()
Expand Down Expand Up @@ -478,20 +482,32 @@ var {} = {{}};
for (raw_symbol, exports) in exports {
let mut exports = exports.iter().collect::<Vec<_>>();
exports.sort_unstable();
for export_name in exports {
let is_default = export_name.as_str() == "default";
for exported_name in exports {
let is_default = exported_name.as_str() == "default";

if is_default {
if export_default.is_none() {
export_default = Some(raw_symbol);
} else {
// multiple export default
export_specifiers.insert(Cow::Borrowed(raw_symbol));
export_specifiers.insert(Cow::Owned(
export_name(raw_symbol)
.expect("should have export_name")
.into_owned(),
));
}
} else if raw_symbol == export_name {
export_specifiers.insert(Cow::Borrowed(raw_symbol));
} else if raw_symbol == exported_name {
export_specifiers.insert(Cow::Owned(
export_name(raw_symbol)
.expect("should have export_name")
.into_owned(),
));
} else {
export_specifiers.insert(Cow::Owned(format!("{raw_symbol} as {export_name}")));
let raw_symbol_name = export_name(raw_symbol).expect("should have export_name");
let exported_name_str = export_name(exported_name).expect("should have export_name");
export_specifiers.insert(Cow::Owned(format!(
"{raw_symbol_name} as {exported_name_str}"
)));
}
}
}
Expand All @@ -516,8 +532,9 @@ var {} = {{}};
serde_json::to_string(source).expect("should have correct request")
)));
} else {
let name_str = export_name(name).expect("should have export_name");
final_source.add(RawStringSource::from(format!(
"export * as {name} from {};\n",
"export * as {name_str} from {};\n",
serde_json::to_string(source).expect("should have correct request")
)));
}
Expand All @@ -536,11 +553,16 @@ var {} = {{}};
.flat_map(|(imported, exports)| {
let mut vec = exports.iter().collect::<Vec<_>>();
vec.sort_unstable();
vec.into_iter().map(move |export_name| {
if *imported == export_name {
Cow::Borrowed(imported.as_str())
let imported_name = export_name(imported)
.expect("should have export_name")
.into_owned();
vec.into_iter().map(move |exported_name| {
if *imported == exported_name {
imported_name.clone()
} else {
Cow::Owned(format!("{imported} as {export_name}"))
let exported_name_str =
export_name(exported_name).expect("should have export_name");
format!("{imported_name} as {exported_name_str}")
}
})
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
```mjs title=lib-lib_js.mjs
// ./lib.js
const foo = () => 42


export { foo as "b.c" };

```

```mjs title=main.mjs
import { "b.c" as lib_foo } from "./lib-lib_js.mjs";

// ./index.js


console.log.bind(lib_foo)



it('should have correct export', async () => {
const { "b.c": foo } = await import(/* webpackIgnore: true */ './main.mjs')

expect(foo()).toBe(42)
})
export { "b.c" } from "./lib-lib_js.mjs";

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { "a.b" as lib } from './lib';

console.log.bind(lib)

export {lib as "b.c"}

it('should have correct export', async () => {
const { "b.c": foo } = await import(/* webpackIgnore: true */ './main.mjs')

expect(foo()).toBe(42)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const foo = () => 42

export {foo as "a.b"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = {
module: {
rules: [
{
test: /\.json$/,
type: 'json'
}
]
},
optimization: {
splitChunks: {
Comment thread
JSerFeng marked this conversation as resolved.
cacheGroups: {
lib: {
test: /lib\.js/
}
}
}
}
};
Loading