swc/ecmascript/transforms/src/helpers.rs
강동윤 3262052e33
Improve performance and reduce binary size (#973)
spack:
 - Optimize resolver

swc_bunder:
 - Parallize merging of reexports
 - Remove useless `clone`s

swc_ecma_utils:
 - Migrate DropSpan to VisitMut

swc_ecma_transforms:
 - Migrate simple transforms to VisitMut
 - Ignore types to reduce binary size
 - Hide actual types so that we can optimize it in future without breaking change

swc_visit:
 - Apply transforms for vector in-place
 - Apply transforms for box in-place
2020-08-19 01:29:49 +09:00

382 lines
10 KiB
Rust

use once_cell::sync::Lazy;
use scoped_tls::scoped_thread_local;
use std::sync::atomic::{AtomicBool, Ordering};
use swc_common::{FileName, FilePathMapping, Mark, SourceMap, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_parser::{lexer::Lexer, Parser, StringInput};
use swc_ecma_utils::{prepend_stmts, quote_ident, quote_str, DropSpan};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
#[macro_export]
macro_rules! enable_helper {
($i:ident) => {{
$crate::helpers::HELPERS.with(|helpers| {
helpers.$i();
helpers.mark()
})
}};
}
macro_rules! add_to {
($buf:expr, $name:ident, $b:expr, $mark:expr) => {{
static STMTS: Lazy<Vec<Stmt>> = Lazy::new(|| {
let cm = SourceMap::new(FilePathMapping::empty());
let code = include_str!(concat!("helpers/_", stringify!($name), ".js"));
let fm = cm.new_source_file(FileName::Custom(stringify!($name).into()), code.into());
let lexer = Lexer::new(
Default::default(),
Default::default(),
StringInput::from(&*fm),
None,
);
let stmts = Parser::new_from(lexer)
.parse_script()
.map(|mut script| {
script.body.visit_mut_with(&mut DropSpan {
preserve_ctxt: false,
});
script.body
})
.map_err(|e| {
unreachable!("Error occurred while parsing error: {:?}", e);
})
.unwrap();
stmts
});
let enable = $b.load(Ordering::Relaxed);
if enable {
$buf.extend(
STMTS
.iter()
.cloned()
.map(|mut stmt| {
stmt.visit_mut_with(&mut Marker($mark));
stmt
})
.map(ModuleItem::Stmt),
)
}
}};
}
scoped_thread_local!(pub static HELPERS: Helpers);
/// Tracks used helper methods. (e.g. __extends)
#[derive(Default)]
pub struct Helpers {
external: bool,
mark: HelperMark,
inner: Inner,
}
impl Helpers {
pub fn new(external: bool) -> Self {
Helpers {
external,
mark: Default::default(),
inner: Default::default(),
}
}
pub(crate) const fn mark(&self) -> Mark {
self.mark.0
}
pub(crate) const fn external(&self) -> bool {
self.external
}
}
#[derive(Clone, Copy)]
struct HelperMark(Mark);
impl Default for HelperMark {
fn default() -> Self {
HelperMark(Mark::fresh(Mark::root()))
}
}
macro_rules! define_helpers {
(
Helpers {
$( $name:ident : ( $( $dep:ident ),* ), )*
}
) => {
#[derive(Default)]
struct Inner {
$( $name: AtomicBool, )*
}
impl Helpers {
$(
pub fn $name(&self) {
self.inner.$name.store(true, Ordering::Relaxed);
$(
self.$dep();
)*
}
)*
}
impl InjectHelpers {
fn is_helper_used(&self) -> bool{
let mut value = false;
HELPERS.with(|helpers|{
$(
value |= helpers.inner.$name.load(Ordering::Relaxed);
)*
});
value
}
fn build_helpers(&self) -> Vec<ModuleItem> {
let mut buf = vec![];
HELPERS.with(|helpers|{
debug_assert!(!helpers.external);
$(
add_to!(buf, $name, helpers.inner.$name, helpers.mark.0);
)*
});
buf
}
}
};
}
define_helpers!(Helpers {
apply_decorated_descriptor: (),
array_with_holes: (),
array_without_holes: (),
assert_this_initialized: (),
async_generator: (await_value),
async_generator_delegate: (),
async_iterator: (),
async_to_generator: (),
await_async_generator: (await_value),
await_value: (),
class_call_check: (),
class_name_tdz_error: (),
class_private_field_get: (),
class_private_field_loose_base: (),
// class_private_field_loose_key: (),
class_private_field_set: (),
class_private_method_get: (),
class_private_method_set: (),
class_static_private_field_spec_get: (),
class_static_private_field_spec_set: (),
construct: (set_prototype_of),
create_class: (),
decorate: (to_array, to_property_key),
defaults: (),
define_enumerable_properties: (),
define_property: (),
extends: (),
get: (super_prop_base),
get_prototype_of: (),
inherits: (set_prototype_of),
inherits_loose: (),
initializer_define_property: (),
initializer_warning_helper: (),
instanceof: (),
interop_require_default: (),
interop_require_wildcard: (),
is_native_function: (),
iterable_to_array: (),
iterable_to_array_limit: (),
iterable_to_array_limit_loose: (),
jsx: (),
new_arrow_check: (),
non_iterable_rest: (),
non_iterable_spread: (),
object_spread: (define_property),
object_without_properties: (object_without_properties_loose),
object_without_properties_loose: (),
possible_constructor_return: (type_of, assert_this_initialized),
read_only_error: (),
set: (super_prop_base, define_property),
set_prototype_of: (),
skip_first_generator_next: (),
sliced_to_array: (array_with_holes, iterable_to_array_limit, non_iterable_rest),
sliced_to_array_loose: (
array_with_holes,
iterable_to_array_limit_loose,
non_iterable_rest
),
super_prop_base: (get_prototype_of),
tagged_template_literal: (),
tagged_template_literal_loose: (),
// temporal_ref: (temporal_undefined),
// temporal_undefined: (),
throw: (),
to_array: (array_with_holes, iterable_to_array, non_iterable_rest),
to_consumable_array: (array_without_holes, iterable_to_array, non_iterable_spread),
to_primitive: (type_of),
to_property_key: (type_of, to_primitive),
type_of: (),
wrap_async_generator: (async_generator),
wrap_native_super: (
construct,
get_prototype_of,
set_prototype_of,
is_native_function
),
class_private_field_destructure: (),
});
pub fn inject_helpers() -> impl Fold {
as_folder(InjectHelpers)
}
struct InjectHelpers;
impl InjectHelpers {
fn mk_helpers(&self) -> Vec<ModuleItem> {
let (mark, external) = HELPERS.with(|helper| (helper.mark(), helper.external()));
if external {
if self.is_helper_used() {
vec![ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span: DUMMY_SP,
specifiers: vec![ImportSpecifier::Namespace(ImportStarAsSpecifier {
span: DUMMY_SP,
local: quote_ident!(DUMMY_SP.apply_mark(mark), "swcHelpers"),
})],
src: quote_str!("@swc/helpers"),
type_only: false,
}))]
} else {
vec![]
}
} else {
self.build_helpers()
}
}
}
impl VisitMut for InjectHelpers {
noop_visit_mut_type!();
fn visit_mut_module(&mut self, module: &mut Module) {
let helpers = self.mk_helpers();
prepend_stmts(&mut module.body, helpers.into_iter());
}
}
struct Marker(Mark);
impl VisitMut for Marker {
noop_visit_mut_type!();
fn visit_mut_ident(&mut self, i: &mut Ident) {
i.span = i.span.apply_mark(self.0);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pass::noop;
use swc_ecma_visit::{as_folder, FoldWith};
#[test]
fn external_helper() {
let input = "_throw()
swcHelpers._throw()";
crate::tests::Tester::run(|tester| {
HELPERS.set(&Helpers::new(true), || {
let expected = tester.apply_transform(
as_folder(DropSpan {
preserve_ctxt: false,
}),
"output.js",
Default::default(),
"import * as swcHelpers1 from '@swc/helpers';
_throw();
swcHelpers._throw();",
)?;
enable_helper!(throw);
eprintln!("----- Actual -----");
let tr = as_folder(InjectHelpers);
let actual = tester
.apply_transform(tr, "input.js", Default::default(), input)?
.fold_with(&mut crate::hygiene::hygiene())
.fold_with(&mut crate::fixer::fixer(None));
if actual == expected {
return Ok(());
}
let (actual_src, expected_src) = (tester.print(&actual), tester.print(&expected));
if actual_src == expected_src {
return Ok(());
}
println!(">>>>> Orig <<<<<\n{}", input);
println!(">>>>> Code <<<<<\n{}", actual_src);
assert_eq!(
crate::tests::DebugUsingDisplay(&actual_src),
crate::tests::DebugUsingDisplay(&expected_src)
);
Err(())
})
});
}
#[test]
fn use_strict_before_helper() {
crate::tests::test_transform(
Default::default(),
|_| {
enable_helper!(throw);
as_folder(InjectHelpers)
},
"'use strict'",
"'use strict'
function _throw(e) {
throw e;
}
",
false,
)
}
#[test]
fn name_conflict() {
crate::tests::test_transform(
Default::default(),
|_| {
enable_helper!(throw);
as_folder(InjectHelpers)
},
"let _throw = null",
"function _throw(e) {
throw e;
}
let _throw1 = null;
",
false,
)
}
#[test]
fn use_strict_abort() {
crate::tests::test_transform(
Default::default(),
|_| noop(),
"'use strict'
let x = 4;",
"'use strict'
let x = 4;",
false,
);
}
}