bundler: Handle swc helpers (#1199)

swc_bundler:
 - Handle helpers from `swc_ecma_transforms`.

swc_ecma_transforms:
 - dce: Remove unused self-referential functions.
This commit is contained in:
강동윤 2020-11-05 09:53:18 +09:00 committed by GitHub
parent 64942b5006
commit 0a5e23f97c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 220 additions and 65 deletions

View File

@ -8,7 +8,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_bundler"
repository = "https://github.com/swc-project/swc.git"
version = "0.15.0"
version = "0.16.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
@ -33,7 +33,7 @@ swc_common = {version = "0.10.0", path = "../common"}
swc_ecma_ast = {version = "0.34.0", path = "../ecmascript/ast"}
swc_ecma_codegen = {version = "0.40.0", path = "../ecmascript/codegen"}
swc_ecma_parser = {version = "0.42.0", path = "../ecmascript/parser"}
swc_ecma_transforms = {version = "0.29.1", path = "../ecmascript/transforms"}
swc_ecma_transforms = {version = "0.29.3", path = "../ecmascript/transforms"}
swc_ecma_utils = {version = "0.24.0", path = "../ecmascript/utils"}
swc_ecma_visit = {version = "0.20.0", path = "../ecmascript/visit"}

View File

@ -1,6 +1,6 @@
use anyhow::Error;
use std::{collections::HashMap, io::stdout};
use swc_bundler::{BundleKind, Bundler, Config, Hook, Load, ModuleRecord, Resolve};
use swc_bundler::{BundleKind, Bundler, Config, Hook, Load, ModuleData, ModuleRecord, Resolve};
use swc_common::{sync::Lrc, FileName, FilePathMapping, Globals, SourceMap, Span};
use swc_ecma_ast::KeyValueProp;
use swc_ecma_codegen::{text_writer::JsWriter, Emitter};
@ -59,10 +59,7 @@ struct PathLoader {
}
impl Load for PathLoader {
fn load(
&self,
file: &FileName,
) -> Result<(Lrc<swc_common::SourceFile>, swc_ecma_ast::Module), Error> {
fn load(&self, file: &FileName) -> Result<ModuleData, Error> {
let file = match file {
FileName::Real(v) => v,
_ => unreachable!(),
@ -81,7 +78,11 @@ impl Load for PathLoader {
let mut parser = Parser::new_from(lexer);
let module = parser.parse_module().expect("This should not happen");
Ok((fm, module))
Ok(ModuleData {
fm,
module,
helpers: Default::default(),
})
}
}
struct PathResolver;

View File

@ -151,17 +151,18 @@ where
let deps = reexports
.into_par_iter()
.map(|(src, specifiers)| -> Result<_, Error> {
let imported = self.scope.get_module(src.module_id).unwrap();
assert!(imported.is_es6, "Reexports are es6 only");
info.helpers.extend(&imported.helpers);
info.swc_helpers.extend_from(&imported.swc_helpers);
if !merged.insert(src.module_id) {
return Ok(None);
}
log::debug!("Merging exports: {} <- {}", info.fm.name, src.src.value);
let imported = self.scope.get_module(src.module_id).unwrap();
assert!(imported.is_es6, "Reexports are es6 only");
info.helpers.extend(&imported.helpers);
let mut dep = self
.merge_modules(plan, src.module_id, false, false, merged)
.with_context(|| {

View File

@ -138,6 +138,10 @@ where
.into_par_iter()
.map(|(src, specifiers)| -> Result<Option<_>, Error> {
self.run(|| {
let dep_info = self.scope.get_module(src.module_id).unwrap();
info.helpers.extend(&dep_info.helpers);
info.swc_helpers.extend_from(&dep_info.swc_helpers);
if !merged.insert(src.module_id) {
log::debug!("Skipping: {} <= {}", info.fm.name, src.src.value);
return Ok(None);
@ -145,8 +149,6 @@ where
log::debug!("Merging: {} <= {}", info.fm.name, src.src.value);
let dep_info = self.scope.get_module(src.module_id).unwrap();
info.helpers.extend(&dep_info.helpers);
// In the case of
//
// a <- b

View File

@ -8,7 +8,11 @@ use std::{
use swc_atoms::js_word;
use swc_common::{util::move_map::MoveMap, FileName, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms::{fixer, hygiene};
use swc_ecma_transforms::{
fixer,
helpers::{inject_helpers, HELPERS},
hygiene,
};
use swc_ecma_utils::{find_ids, private_ident, ExprFactory};
use swc_ecma_visit::{noop_fold_type, noop_visit_type, Fold, FoldWith, Node, Visit, VisitWith};
@ -29,12 +33,27 @@ where
for mut bundle in bundles {
bundle.module = self.optimize(bundle.module);
bundle.module = self.may_wrap_with_iife(bundle.module);
bundle.module = bundle.module.fold_with(&mut hygiene());
bundle.module = bundle.module.fold_with(&mut fixer(None));
{
// Inject swc helpers
let swc_helpers = self
.scope
.get_module(bundle.id)
.expect("module should exist at this point")
.swc_helpers;
let module = bundle.module;
bundle.module =
HELPERS.set(&swc_helpers, || module.fold_with(&mut inject_helpers()));
}
bundle.module = self.may_wrap_with_iife(bundle.module);
match bundle.kind {
BundleKind::Named { .. } => {
// Inject helpers
@ -44,7 +63,7 @@ where
.expect("module should exist at this point")
.helpers;
helpers.append_to(&mut bundle.module.body);
helpers.add_to(&mut bundle.module.body);
new.push(Bundle { ..bundle });
}

View File

@ -59,7 +59,7 @@ impl Helpers {
}
}
pub fn append_to(&self, to: &mut Vec<ModuleItem>) {
pub fn add_to(&self, to: &mut Vec<ModuleItem>) {
let mut buf = vec![];
if self.require.load(SeqCst) {

View File

@ -2,6 +2,7 @@ use super::{export::Exports, helpers::Helpers, Bundler};
use crate::{
bundler::{export::RawExports, import::RawImports},
id::{Id, ModuleId},
load::ModuleData,
util,
util::IntoParallelIterator,
Load, Resolve,
@ -33,6 +34,8 @@ pub(super) struct TransformedModule {
/// Used helpers
pub helpers: Lrc<Helpers>,
pub swc_helpers: Lrc<swc_ecma_transforms::helpers::Helpers>,
mark: Mark,
}
@ -69,9 +72,9 @@ where
return Ok(Some(cached));
}
let (_, fm, module) = self.load(&file_name).context("Bundler.load() failed")?;
let (_, data) = self.load(&file_name).context("Bundler.load() failed")?;
let (v, mut files) = self
.analyze(&file_name, fm.clone(), module)
.analyze(&file_name, data)
.context("failed to analyze module")?;
files.dedup_by_key(|v| v.1.clone());
@ -96,16 +99,16 @@ where
})
}
fn load(&self, file_name: &FileName) -> Result<(ModuleId, Lrc<SourceFile>, Module), Error> {
fn load(&self, file_name: &FileName) -> Result<(ModuleId, ModuleData), Error> {
self.run(|| {
let (module_id, _) = self.scope.module_id_gen.gen(file_name);
let (fm, module) = self
let data = self
.loader
.load(&file_name)
.with_context(|| format!("Bundler.loader.load({}) failed", file_name))?;
self.scope.mark_as_loaded(module_id);
Ok((module_id, fm, module))
Ok((module_id, data))
})
}
@ -113,14 +116,13 @@ where
fn analyze(
&self,
file_name: &FileName,
fm: Lrc<SourceFile>,
mut module: Module,
data: ModuleData,
) -> Result<(TransformedModule, Vec<(Source, Lrc<FileName>)>), Error> {
self.run(|| {
log::trace!("transform_module({})", fm.name);
log::trace!("transform_module({})", data.fm.name);
let (id, mark) = self.scope.module_id_gen.gen(file_name);
module = module.fold_with(&mut resolver_with_mark(mark));
let mut module = data.module.fold_with(&mut resolver_with_mark(mark));
// {
// let code = self
@ -180,13 +182,14 @@ where
Ok((
TransformedModule {
id,
fm,
fm: data.fm,
module,
imports: Lrc::new(imports),
exports: Lrc::new(exports),
is_es6,
helpers: Default::default(),
mark,
swc_helpers: Lrc::new(data.helpers),
},
import_files,
))

View File

@ -1,9 +1,9 @@
//! Utilities for testing.
use super::{load::TransformedModule, Bundler, Config};
use crate::{util::HygieneRemover, Load, ModuleId, ModuleRecord, Resolve};
use crate::{load::ModuleData, util::HygieneRemover, Load, ModuleId, ModuleRecord, Resolve};
use anyhow::Error;
use std::{collections::HashMap, path::PathBuf};
use swc_common::{sync::Lrc, FileName, SourceFile, SourceMap, Span, GLOBALS};
use swc_common::{sync::Lrc, FileName, SourceMap, Span, GLOBALS};
use swc_ecma_ast::*;
use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput};
use swc_ecma_utils::drop_span;
@ -20,7 +20,7 @@ pub struct Loader {
}
impl Load for Loader {
fn load(&self, f: &FileName) -> Result<(Lrc<SourceFile>, Module), Error> {
fn load(&self, f: &FileName) -> Result<ModuleData, Error> {
eprintln!("load: {}", f);
let v = self.files.get(&f.to_string());
let v = v.unwrap();
@ -37,7 +37,11 @@ impl Load for Loader {
let mut parser = Parser::new_from(lexer);
let module = parser.parse_module().unwrap();
Ok((fm, module))
Ok(ModuleData {
fm,
module,
helpers: Default::default(),
})
}
}

View File

@ -2,7 +2,7 @@ pub use self::{
bundler::{Bundle, BundleKind, Bundler, Config, ModuleType},
hook::{Hook, ModuleRecord},
id::ModuleId,
load::Load,
load::{Load, ModuleData},
resolve::Resolve,
};

View File

@ -1,6 +1,35 @@
use anyhow::Error;
use swc_common::{sync::Lrc, FileName, SourceFile};
use swc_ecma_ast::Module;
use swc_ecma_transforms::helpers::Helpers;
#[derive(Debug)]
pub struct ModuleData {
pub fm: Lrc<SourceFile>,
pub module: Module,
/// Used helpers
///
/// # Exmaple
///
/// ```rust,ignore
///
/// impl Load for Loader {
/// fn load(&self, name: &FileName) -> Result<ModuleData, Error> {
/// let helpers = Helpers::new(false);
/// let fm = self.load_file(name)?;
/// let module = self.parse(fm.clone())?;
///
/// let module = helpers::HELPERS.set(&helpers, || {
/// // Apply transforms (like decorators pass)
/// module
/// });
///
/// Ok(ModuleData { fm, module, helpers })
/// }
/// }
/// ```
pub helpers: Helpers,
}
/// Responsible for providing files to the bundler.
///
@ -11,17 +40,17 @@ use swc_ecma_ast::Module;
///
/// This trait is designed to allow passing pre-parsed module.
pub trait Load: swc_common::sync::Send + swc_common::sync::Sync {
fn load(&self, file: &FileName) -> Result<(Lrc<SourceFile>, Module), Error>;
fn load(&self, file: &FileName) -> Result<ModuleData, Error>;
}
impl<T: ?Sized + Load> Load for Box<T> {
fn load(&self, file: &FileName) -> Result<(Lrc<SourceFile>, Module), Error> {
fn load(&self, file: &FileName) -> Result<ModuleData, Error> {
(**self).load(file)
}
}
impl<'a, T: ?Sized + Load> Load for &'a T {
fn load(&self, file: &FileName) -> Result<(Lrc<SourceFile>, Module), Error> {
fn load(&self, file: &FileName) -> Result<ModuleData, Error> {
(**self).load(file)
}
}

View File

@ -12,11 +12,10 @@ use std::{
process::{Command, Stdio},
};
use swc_atoms::js_word;
use swc_bundler::{Bundler, Load, ModuleRecord, Resolve};
use swc_common::{sync::Lrc, FileName, SourceFile, SourceMap, Span, GLOBALS};
use swc_bundler::{Bundler, Load, ModuleData, ModuleRecord, Resolve};
use swc_common::{sync::Lrc, FileName, SourceMap, Span, GLOBALS};
use swc_ecma_ast::{
Bool, Expr, ExprOrSuper, Ident, KeyValueProp, Lit, MemberExpr, MetaPropExpr, Module, PropName,
Str,
Bool, Expr, ExprOrSuper, Ident, KeyValueProp, Lit, MemberExpr, MetaPropExpr, PropName, Str,
};
use swc_ecma_codegen::{text_writer::JsWriter, Emitter};
use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax, TsConfig};
@ -185,7 +184,7 @@ fn load_url(url: Url) -> Result<String, Error> {
}
impl Load for Loader {
fn load(&self, file: &FileName) -> Result<(Lrc<SourceFile>, Module), Error> {
fn load(&self, file: &FileName) -> Result<ModuleData, Error> {
eprintln!("{}", file);
let url = match file {
@ -218,7 +217,11 @@ impl Load for Loader {
}));
let module = module.fold_with(&mut strip());
Ok((fm, module))
Ok(ModuleData {
fm,
module,
helpers: Default::default(),
})
}
}

View File

@ -12,11 +12,10 @@ use std::{
path::{Path, PathBuf},
};
use swc_atoms::js_word;
use swc_bundler::{BundleKind, Bundler, Config, Load, ModuleRecord, Resolve};
use swc_common::{sync::Lrc, FileName, Globals, SourceFile, SourceMap, Span};
use swc_bundler::{BundleKind, Bundler, Config, Load, ModuleData, ModuleRecord, Resolve};
use swc_common::{sync::Lrc, FileName, Globals, SourceMap, Span};
use swc_ecma_ast::{
Bool, Expr, ExprOrSuper, Ident, KeyValueProp, Lit, MemberExpr, MetaPropExpr, Module, PropName,
Str,
Bool, Expr, ExprOrSuper, Ident, KeyValueProp, Lit, MemberExpr, MetaPropExpr, PropName, Str,
};
use swc_ecma_codegen::{text_writer::JsWriter, Emitter};
use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax, TsConfig};
@ -291,7 +290,7 @@ pub struct Loader {
}
impl Load for Loader {
fn load(&self, f: &FileName) -> Result<(Lrc<SourceFile>, Module), Error> {
fn load(&self, f: &FileName) -> Result<ModuleData, Error> {
eprintln!("load: {}", f);
let fm = self.cm.load_file(match f {
@ -314,7 +313,11 @@ impl Load for Loader {
let module = module.fold_with(&mut strip());
Ok((fm, module))
Ok(ModuleData {
fm,
module,
helpers: Default::default(),
})
}
}

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_ecmascript"
repository = "https://github.com/swc-project/swc.git"
version = "0.13.2"
version = "0.13.3"
[features]
codegen = ["swc_ecma_codegen"]
@ -24,7 +24,7 @@ swc_ecma_ast = {version = "0.34.0", path = "./ast"}
swc_ecma_codegen = {version = "0.40.0", path = "./codegen", optional = true}
swc_ecma_dep_graph = {version = "0.8.0", path = "./dep-graph", optional = true}
swc_ecma_parser = {version = "0.42.0", path = "./parser", optional = true}
swc_ecma_transforms = {version = "0.29.1", path = "./transforms", optional = true}
swc_ecma_transforms = {version = "0.29.3", path = "./transforms", optional = true}
swc_ecma_utils = {version = "0.24.0", path = "./utils", optional = true}
swc_ecma_visit = {version = "0.20.0", path = "./visit", optional = true}

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_ecma_transforms"
repository = "https://github.com/swc-project/swc.git"
version = "0.29.2"
version = "0.29.3"
[features]
const-modules = ["dashmap"]

View File

@ -63,7 +63,7 @@ macro_rules! add_to {
scoped_thread_local!(pub static HELPERS: Helpers);
/// Tracks used helper methods. (e.g. __extends)
#[derive(Default)]
#[derive(Debug, Default)]
pub struct Helpers {
external: bool,
mark: HelperMark,
@ -78,6 +78,7 @@ impl Helpers {
inner: Default::default(),
}
}
pub(crate) const fn mark(&self) -> Mark {
self.mark.0
}
@ -86,7 +87,7 @@ impl Helpers {
}
}
#[derive(Clone, Copy)]
#[derive(Debug, Clone, Copy)]
struct HelperMark(Mark);
impl Default for HelperMark {
fn default() -> Self {
@ -100,7 +101,7 @@ macro_rules! define_helpers {
$( $name:ident : ( $( $dep:ident ),* ), )*
}
) => {
#[derive(Default)]
#[derive(Debug,Default)]
struct Inner {
$( $name: AtomicBool, )*
}
@ -117,6 +118,16 @@ macro_rules! define_helpers {
)*
}
impl Helpers {
pub fn extend_from(&self, other: &Self) {
$(
if other.inner.$name.load(Ordering::SeqCst) {
self.inner.$name.store(true, Ordering::Relaxed);
}
)*
}
}
impl InjectHelpers {
fn is_helper_used(&self) -> bool{
let mut value = false;

View File

@ -61,6 +61,7 @@ pub fn dce<'a>(config: Config<'a>) -> impl RepeatedJsPass + 'a {
changed: false,
marking_phase: false,
decl_dropping_phase: false,
cur_defining: Default::default()
}),
as_folder(UsedMarkRemover { used_mark })
)
@ -112,6 +113,12 @@ struct Dce<'a> {
decl_dropping_phase: bool,
dropped: bool,
/// Functions that we are currently defining.
///
/// Reference to function itself in a function should not make function
/// preserved.
cur_defining: FxHashSet<Id>,
}
impl CompilerPass for Dce<'_> {
@ -288,7 +295,12 @@ impl VisitMut for Dce<'_> {
return;
}
f.visit_mut_children_with(self)
let id = f.ident.to_id();
self.cur_defining.insert(id.clone());
f.visit_mut_children_with(self);
self.cur_defining.remove(&id);
}
fn visit_mut_for_in_stmt(&mut self, node: &mut ForInStmt) {
@ -361,7 +373,13 @@ impl VisitMut for Dce<'_> {
}
if self.marking_phase {
if self.included.insert(i.to_id()) {
let id = i.to_id();
// This is required to drop recursive functions.
if self.cur_defining.contains(&id) {
return;
}
if self.included.insert(id) {
log::debug!("{} is used", i.sym);
self.changed = true;
}

View File

@ -8,6 +8,9 @@ macro_rules! external_name {
("throw") => {
"_throw"
};
("extends") => {
"_extends"
};
($s:literal) => {
$s
};

View File

@ -775,3 +775,23 @@ test!(
new A();
"
);
optimized_out!(
self_referential_function_01,
"
function foo() {
if (Math.random() > 0.5) {
foo()
}
}
"
);
optimized_out!(
pr_1199_01,
"
function _nonIterableSpread() {
throw new TypeError('Invalid attempt to spread non-iterable instance');
}
"
);

View File

@ -3,9 +3,9 @@ use helpers::Helpers;
use std::{collections::HashMap, env, sync::Arc};
use swc::config::{InputSourceMap, JscConfig, TransformConfig};
use swc_atoms::JsWord;
use swc_bundler::Load;
use swc_common::{FileName, SourceFile, DUMMY_SP};
use swc_ecma_ast::{Expr, Lit, Module, Program, Str};
use swc_bundler::{Load, ModuleData};
use swc_common::{FileName, DUMMY_SP};
use swc_ecma_ast::{Expr, Lit, Program, Str};
use swc_ecma_parser::JscTarget;
use swc_ecma_transforms::{
helpers,
@ -33,8 +33,9 @@ impl SwcLoader {
}
impl Load for SwcLoader {
fn load(&self, name: &FileName) -> Result<(Arc<SourceFile>, Module), Error> {
fn load(&self, name: &FileName) -> Result<ModuleData, Error> {
log::debug!("JsLoader.load({})", name);
let helpers = Helpers::new(false);
let fm = self
.compiler
@ -55,7 +56,7 @@ impl Load for SwcLoader {
true,
true,
)?;
let program = helpers::HELPERS.set(&Helpers::new(true), || {
let program = helpers::HELPERS.set(&helpers, || {
swc_ecma_utils::HANDLER.set(&self.compiler.handler, || {
let program =
program.fold_with(&mut inline_globals(env_map(), Default::default()));
@ -99,6 +100,7 @@ impl Load for SwcLoader {
None
}
},
skip_helper_injection: true,
disable_hygiene: false,
disable_fixer: true,
global_mark: self.options.global_mark,
@ -135,7 +137,7 @@ impl Load for SwcLoader {
log::trace!("JsLoader.load: parsed");
// Fold module
let program = helpers::HELPERS.set(&Helpers::new(true), || {
let program = helpers::HELPERS.set(&helpers, || {
swc_ecma_utils::HANDLER.set(&self.compiler.handler, || {
let program =
program.fold_with(&mut inline_globals(env_map(), Default::default()));
@ -154,7 +156,11 @@ impl Load for SwcLoader {
};
match program {
Program::Module(module) => Ok((fm, module)),
Program::Module(module) => Ok(ModuleData {
fm,
module,
helpers,
}),
_ => unreachable!(),
}
}

View File

@ -0,0 +1,8 @@
{
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true
}
}
}

View File

@ -0,0 +1,2 @@
@isDecorator
class Foo { }

View File

@ -0,0 +1,10 @@
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var _class;
var Foo = _class = isDecorator((_class = function Foo() {
"use strict";
_classCallCheck(this, Foo);
}) || _class) || _class;

View File

@ -20,6 +20,7 @@ pub struct PassBuilder<'a, 'b, P: swc_ecma_visit::Fold> {
loose: bool,
hygiene: bool,
fixer: bool,
inject_helpers: bool,
}
impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> {
@ -40,6 +41,7 @@ impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> {
hygiene: true,
env: None,
fixer: true,
inject_helpers: true,
}
}
@ -58,9 +60,15 @@ impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> {
env: self.env,
global_mark: self.global_mark,
fixer: self.fixer,
inject_helpers: self.inject_helpers,
}
}
pub fn skip_helper_injection(mut self, skip: bool) -> Self {
self.inject_helpers = !skip;
self
}
/// Note: fixer is enabled by default.
pub fn fixer(mut self, enable: bool) -> Self {
self.fixer = enable;
@ -173,7 +181,7 @@ impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> {
modules::import_analysis::import_analyzer(),
need_interop_analysis
),
helpers::inject_helpers(),
Optional::new(helpers::inject_helpers(), self.inject_helpers),
ModuleConfig::build(self.cm.clone(), self.global_mark, module),
Optional::new(hygiene(), self.hygiene),
Optional::new(fixer(comments), self.fixer),

View File

@ -58,6 +58,9 @@ pub struct Options {
#[serde(flatten, default)]
pub config: Option<Config>,
#[serde(skip_deserializing, default)]
pub skip_helper_injection: bool,
#[cfg(not(target_arch = "wasm32"))]
#[serde(skip_deserializing, default)]
pub disable_hygiene: bool,
@ -248,6 +251,7 @@ impl Options {
let pass = PassBuilder::new(&cm, &handler, loose, root_mark, pass)
.target(target)
.skip_helper_injection(self.skip_helper_injection)
.hygiene(!self.disable_hygiene)
.fixer(!self.disable_fixer)
.preset_env(config.env)