Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/swc_ecma_compat_es2020/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ swc_common = { version = "17.0.1", path = "../swc_common" }
swc_ecma_ast = { version = "18.0.0", path = "../swc_ecma_ast" }
swc_ecma_compat_es2022 = { version = "31.0.0", path = "../swc_ecma_compat_es2022" }
swc_ecma_compiler = { version = "8.0.0", path = "../swc_ecma_compiler" }
swc_ecma_transformer = { version = "0.1.0", path = "../swc_ecma_transformer" }
swc_ecma_transforms_base = { version = "30.0.0", path = "../swc_ecma_transforms_base" }
swc_ecma_utils = { version = "24.0.0", path = "../swc_ecma_utils" }
swc_ecma_visit = { version = "18.0.1", path = "../swc_ecma_visit" }
Expand Down
8 changes: 3 additions & 5 deletions crates/swc_ecma_compat_es2020/src/export_namespace_from.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use swc_ecma_ast::Pass;
use swc_ecma_compiler::{Compiler, Features};

pub fn export_namespace_from() -> impl Pass {
Compiler::new(swc_ecma_compiler::Config {
includes: Features::EXPORT_NAMESPACE_FROM,
..Default::default()
})
let mut options = swc_ecma_transformer::Options::default();
options.env.es2020.export_namespace_from = true;
options.into_pass()
}
13 changes: 8 additions & 5 deletions crates/swc_ecma_preset_env/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,10 @@ where
pass,
/* ES2022 */ | PrivatePropertyInObject
/* ES2021 */ | LogicalAssignmentOperators
/* ES2020 */ | ExportNamespaceFrom
);
if !caniuse(Feature::ExportNamespaceFrom) {
options.env.es2020.export_namespace_from = true;
}

// ES2020
let pass = add!(
Expand Down Expand Up @@ -209,6 +211,9 @@ where
// ES2016
let pass = add!(pass, ExponentiationOperator, es2016::exponentiation());

// Single-pass compiler
let pass = (pass, options.into_pass());

// ES2015
let pass = add!(pass, BlockScopedFunctions, es2015::block_scoped_functions());
let pass = add!(
Expand Down Expand Up @@ -325,13 +330,11 @@ where
bugfixes::template_literal_caching()
);

let pass = add!(
add!(
pass,
BugfixSafariIdDestructuringCollisionInFunctionExpression,
bugfixes::safari_id_destructuring_collision_in_function_expression()
);

(pass, options.into_pass())
)
}

pub fn transform_from_env<C>(
Expand Down
1 change: 1 addition & 0 deletions crates/swc_ecma_transformer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ version = "0.1.0"
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] }

[dependencies]
swc_atoms = { version = "9.0.0", path = "../swc_atoms" }
swc_common = { version = "17.0.1", path = "../swc_common" }
swc_ecma_ast = { version = "18.0.0", path = "../swc_ecma_ast" }
swc_ecma_hooks = { version = "0.2.0", path = "../swc_ecma_hooks" }
Expand Down
132 changes: 132 additions & 0 deletions crates/swc_ecma_transformer/src/es2020/export_namespace_from.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use swc_ecma_ast::*;
use swc_ecma_hooks::VisitMutHook;
use swc_ecma_utils::private_ident;

use crate::{utils::normalize_module_export_name, TraverseCtx};

pub fn hook() -> impl VisitMutHook<TraverseCtx> {
ExportNamespaceFromPass
}

struct ExportNamespaceFromPass;

impl ExportNamespaceFromPass {
fn transform_export_namespace_from(&mut self, items: &mut Vec<ModuleItem>) {
let count = items
.iter()
.filter(|m| {
matches!(m, ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
specifiers,
src: Some(..),
type_only: false,
..
})) if specifiers.iter().any(|s| s.is_namespace()))
})
.count();

if count == 0 {
return;
}

let mut stmts = Vec::<ModuleItem>::with_capacity(items.len() + count);

for item in items.drain(..) {
match item {
ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
span,
specifiers,
src: Some(src),
type_only: false,
with,
})) if specifiers.iter().any(|s| s.is_namespace()) => {
let mut origin_specifiers = Vec::new();

let mut import_specifiers = Vec::new();
let mut export_specifiers = Vec::new();

for s in specifiers.into_iter() {
match s {
ExportSpecifier::Namespace(ExportNamespaceSpecifier { span, name }) => {
let local_bridge = private_ident!(format!(
"_{}",
normalize_module_export_name(&name)
));

import_specifiers.push(ImportSpecifier::Namespace(
ImportStarAsSpecifier {
span,
local: local_bridge.clone(),
},
));
export_specifiers.push(ExportSpecifier::Named(
ExportNamedSpecifier {
span,
orig: local_bridge.into(),
exported: Some(name),
is_type_only: false,
},
))
}
ExportSpecifier::Default(..) | ExportSpecifier::Named(..) => {
origin_specifiers.push(s);
}
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
}
}

stmts.push(
ImportDecl {
span,
specifiers: import_specifiers,
src: src.clone(),
type_only: false,
with: with.clone(),
phase: Default::default(),
}
.into(),
);

stmts.push(
NamedExport {
span,
specifiers: export_specifiers,
src: None,
type_only: false,
with: None,
}
.into(),
);

if !origin_specifiers.is_empty() {
stmts.push(
NamedExport {
span,
specifiers: origin_specifiers,
src: Some(src),
type_only: false,
with,
}
.into(),
);
}
}
_ => {
stmts.push(item);
}
}
}

*items = stmts;
}
}

impl VisitMutHook<TraverseCtx> for ExportNamespaceFromPass {
fn exit_program(&mut self, node: &mut Program, _: &mut TraverseCtx) {
let Program::Module(module) = node else {
return;
};

self.transform_export_namespace_from(&mut module.body);
}
}
20 changes: 11 additions & 9 deletions crates/swc_ecma_transformer/src/es2020/mod.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use swc_ecma_hooks::VisitMutHook;

use crate::TraverseCtx;
use crate::{hook_utils::OptionalHook, TraverseCtx};

mod export_namespace_from;

#[derive(Debug, Default)]
#[non_exhaustive]
pub struct Es2020Options {}

pub fn hook(options: Es2020Options) -> impl VisitMutHook<TraverseCtx> {
Es2020Pass { options }
pub struct Es2020Options {
pub export_namespace_from: bool,
}

struct Es2020Pass {
options: Es2020Options,
pub fn hook(options: Es2020Options) -> impl VisitMutHook<TraverseCtx> {
OptionalHook(if options.export_namespace_from {
Some(self::export_namespace_from::hook())
} else {
None
})
}

impl VisitMutHook<TraverseCtx> for Es2020Pass {}
1 change: 1 addition & 0 deletions crates/swc_ecma_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod jsx;
mod options;
mod regexp;
mod typescript;
mod utils;

pub struct TraverseCtx {}

Expand Down
20 changes: 20 additions & 0 deletions crates/swc_ecma_transformer/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use std::borrow::Cow;

use swc_atoms::Atom;
use swc_ecma_ast::*;

pub(crate) fn normalize_module_export_name(module_export_name: &ModuleExportName) -> Cow<Atom> {
match module_export_name {
ModuleExportName::Ident(Ident { sym: name, .. }) => Cow::Borrowed(name),
ModuleExportName::Str(Str { value: name, .. }) => {
// Normally, the export name should be valid UTF-8. But it might also contain
// unpaired surrogates. Node would give an error in this case:
// `SyntaxError: Invalid module export name: contains unpaired
// surrogate`. Here, we temporarily replace the unpaired surrogates
// with U+FFFD REPLACEMENT CHARACTER by using Wtf8::to_string_lossy.
Cow::Owned(Atom::from(name.to_string_lossy()))
}
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
}
}
Loading