Skip to content

Commit 4fd9023

Browse files
committed
export_namespace_from
1 parent 440b391 commit 4fd9023

File tree

6 files changed

+166
-9
lines changed

6 files changed

+166
-9
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/swc_ecma_transformer/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ version = "0.1.0"
1313
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] }
1414

1515
[dependencies]
16+
swc_atoms = { version = "9.0.0", path = "../swc_atoms" }
1617
swc_common = { version = "17.0.1", path = "../swc_common" }
1718
swc_ecma_ast = { version = "18.0.0", path = "../swc_ecma_ast" }
1819
swc_ecma_hooks = { version = "0.2.0", path = "../swc_ecma_hooks" }
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use swc_ecma_ast::*;
2+
use swc_ecma_hooks::VisitMutHook;
3+
use swc_ecma_utils::private_ident;
4+
5+
use crate::{utils::normalize_module_export_name, TraverseCtx};
6+
7+
pub fn hook() -> impl VisitMutHook<TraverseCtx> {
8+
ExportNamespaceFromPass
9+
}
10+
11+
struct ExportNamespaceFromPass;
12+
13+
impl ExportNamespaceFromPass {
14+
fn transform_export_namespace_from(&mut self, items: &mut Vec<ModuleItem>) {
15+
let count = items
16+
.iter()
17+
.filter(|m| {
18+
matches!(m, ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
19+
specifiers,
20+
src: Some(..),
21+
type_only: false,
22+
..
23+
})) if specifiers.iter().any(|s| s.is_namespace()))
24+
})
25+
.count();
26+
27+
if count == 0 {
28+
return;
29+
}
30+
31+
let mut stmts = Vec::<ModuleItem>::with_capacity(items.len() + count);
32+
33+
for item in items.drain(..) {
34+
match item {
35+
ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
36+
span,
37+
specifiers,
38+
src: Some(src),
39+
type_only: false,
40+
with,
41+
})) if specifiers.iter().any(|s| s.is_namespace()) => {
42+
let mut origin_specifiers = Vec::new();
43+
44+
let mut import_specifiers = Vec::new();
45+
let mut export_specifiers = Vec::new();
46+
47+
for s in specifiers.into_iter() {
48+
match s {
49+
ExportSpecifier::Namespace(ExportNamespaceSpecifier { span, name }) => {
50+
let local_bridge = private_ident!(format!(
51+
"_{}",
52+
normalize_module_export_name(&name)
53+
));
54+
55+
import_specifiers.push(ImportSpecifier::Namespace(
56+
ImportStarAsSpecifier {
57+
span,
58+
local: local_bridge.clone(),
59+
},
60+
));
61+
export_specifiers.push(ExportSpecifier::Named(
62+
ExportNamedSpecifier {
63+
span,
64+
orig: local_bridge.into(),
65+
exported: Some(name),
66+
is_type_only: false,
67+
},
68+
))
69+
}
70+
ExportSpecifier::Default(..) | ExportSpecifier::Named(..) => {
71+
origin_specifiers.push(s);
72+
}
73+
#[cfg(swc_ast_unknown)]
74+
_ => panic!("unable to access unknown nodes"),
75+
}
76+
}
77+
78+
stmts.push(
79+
ImportDecl {
80+
span,
81+
specifiers: import_specifiers,
82+
src: src.clone(),
83+
type_only: false,
84+
with: with.clone(),
85+
phase: Default::default(),
86+
}
87+
.into(),
88+
);
89+
90+
stmts.push(
91+
NamedExport {
92+
span,
93+
specifiers: export_specifiers,
94+
src: None,
95+
type_only: false,
96+
with: None,
97+
}
98+
.into(),
99+
);
100+
101+
if !origin_specifiers.is_empty() {
102+
stmts.push(
103+
NamedExport {
104+
span,
105+
specifiers: origin_specifiers,
106+
src: Some(src),
107+
type_only: false,
108+
with,
109+
}
110+
.into(),
111+
);
112+
}
113+
}
114+
_ => {
115+
stmts.push(item);
116+
}
117+
}
118+
}
119+
120+
*items = stmts;
121+
}
122+
}
123+
124+
impl VisitMutHook<TraverseCtx> for ExportNamespaceFromPass {
125+
fn exit_program(&mut self, node: &mut Program, _: &mut TraverseCtx) {
126+
let Program::Module(module) = node else {
127+
return;
128+
};
129+
130+
self.transform_export_namespace_from(&mut module.body);
131+
}
132+
}

crates/swc_ecma_transformer/src/es2020/mod.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
use swc_ecma_hooks::VisitMutHook;
22

3-
use crate::TraverseCtx;
3+
use crate::{hook_utils::OptionalHook, TraverseCtx};
4+
5+
mod export_namespace_from;
46

57
#[derive(Debug, Default)]
68
#[non_exhaustive]
7-
pub struct Es2020Options {}
8-
9-
pub fn hook(options: Es2020Options) -> impl VisitMutHook<TraverseCtx> {
10-
Es2020Pass { options }
9+
pub struct Es2020Options {
10+
pub export_namespace_from: bool,
1111
}
1212

13-
struct Es2020Pass {
14-
options: Es2020Options,
13+
pub fn hook(options: Es2020Options) -> impl VisitMutHook<TraverseCtx> {
14+
OptionalHook(if options.export_namespace_from {
15+
Some(self::export_namespace_from::hook())
16+
} else {
17+
None
18+
})
1519
}
16-
17-
impl VisitMutHook<TraverseCtx> for Es2020Pass {}

crates/swc_ecma_transformer/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod jsx;
2323
mod options;
2424
mod regexp;
2525
mod typescript;
26+
mod utils;
2627

2728
pub struct TraverseCtx {}
2829

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use std::borrow::Cow;
2+
3+
use swc_atoms::Atom;
4+
use swc_ecma_ast::*;
5+
6+
pub(crate) fn normalize_module_export_name(module_export_name: &ModuleExportName) -> Cow<Atom> {
7+
match module_export_name {
8+
ModuleExportName::Ident(Ident { sym: name, .. }) => Cow::Borrowed(name),
9+
ModuleExportName::Str(Str { value: name, .. }) => {
10+
// Normally, the export name should be valid UTF-8. But it might also contain
11+
// unpaired surrogates. Node would give an error in this case:
12+
// `SyntaxError: Invalid module export name: contains unpaired
13+
// surrogate`. Here, we temporarily replace the unpaired surrogates
14+
// with U+FFFD REPLACEMENT CHARACTER by using Wtf8::to_string_lossy.
15+
Cow::Owned(Atom::from(name.to_string_lossy()))
16+
}
17+
#[cfg(swc_ast_unknown)]
18+
_ => panic!("unable to access unknown nodes"),
19+
}
20+
}

0 commit comments

Comments
 (0)