mirror of
https://github.com/swc-project/swc.git
synced 2024-12-22 21:21:31 +03:00
feat(es/transforms/optimization): Improve inline_globals
(#2479)
swc_ecma_transforms_optimization: - `inline_globals`: Support inlining into shorthand properties. - `inline_globals`: Support replacing member expressions. swc: - Add an option to disable simplifier when using `inline_globals`. node_swc: - Improve error message on panic.
This commit is contained in:
parent
9b96885171
commit
b0361caa58
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -883,6 +883,7 @@ dependencies = [
|
|||||||
"autocfg 1.0.1",
|
"autocfg 1.0.1",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2324,13 +2325,14 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swc"
|
name = "swc"
|
||||||
version = "0.75.0"
|
version = "0.76.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.13.0",
|
"base64 0.13.0",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"either",
|
"either",
|
||||||
|
"indexmap",
|
||||||
"lru",
|
"lru",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pathdiff",
|
"pathdiff",
|
||||||
@ -2878,7 +2880,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swc_ecma_transforms_optimization"
|
name = "swc_ecma_transforms_optimization"
|
||||||
version = "0.58.0"
|
version = "0.58.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
|
@ -21,7 +21,7 @@ include = ["Cargo.toml", "src/**/*.rs"]
|
|||||||
license = "Apache-2.0/MIT"
|
license = "Apache-2.0/MIT"
|
||||||
name = "swc"
|
name = "swc"
|
||||||
repository = "https://github.com/swc-project/swc.git"
|
repository = "https://github.com/swc-project/swc.git"
|
||||||
version = "0.75.0"
|
version = "0.76.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "swc"
|
name = "swc"
|
||||||
@ -51,6 +51,7 @@ anyhow = "1"
|
|||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
dashmap = "4.0.2"
|
dashmap = "4.0.2"
|
||||||
either = "1"
|
either = "1"
|
||||||
|
indexmap = {version = "1", features = ["serde"]}
|
||||||
lru = "0.6.1"
|
lru = "0.6.1"
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
pathdiff = "0.2.0"
|
pathdiff = "0.2.0"
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
"elems",
|
"elems",
|
||||||
"esbuild",
|
"esbuild",
|
||||||
"esms",
|
"esms",
|
||||||
|
"eval",
|
||||||
"Eval",
|
"Eval",
|
||||||
"exponentation",
|
"exponentation",
|
||||||
"fargs",
|
"fargs",
|
||||||
|
@ -6,7 +6,7 @@ edition = "2018"
|
|||||||
license = "Apache-2.0/MIT"
|
license = "Apache-2.0/MIT"
|
||||||
name = "swc_ecma_transforms_optimization"
|
name = "swc_ecma_transforms_optimization"
|
||||||
repository = "https://github.com/swc-project/swc.git"
|
repository = "https://github.com/swc-project/swc.git"
|
||||||
version = "0.58.0"
|
version = "0.58.1"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@ -2,21 +2,44 @@ use swc_atoms::{js_word, JsWord};
|
|||||||
use swc_common::{
|
use swc_common::{
|
||||||
collections::{AHashMap, AHashSet},
|
collections::{AHashMap, AHashSet},
|
||||||
sync::Lrc,
|
sync::Lrc,
|
||||||
|
EqIgnoreSpan,
|
||||||
};
|
};
|
||||||
use swc_ecma_ast::*;
|
use swc_ecma_ast::*;
|
||||||
use swc_ecma_transforms_base::perf::Parallel;
|
use swc_ecma_transforms_base::perf::Parallel;
|
||||||
use swc_ecma_transforms_macros::parallel;
|
use swc_ecma_transforms_macros::parallel;
|
||||||
use swc_ecma_utils::{collect_decls, Id};
|
use swc_ecma_utils::{collect_decls, ident::IdentLike, Id};
|
||||||
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
|
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
|
||||||
|
|
||||||
|
/// The key will be compared using [EqIgnoreSpan::eq_ignore_span], and matched
|
||||||
|
/// expressions will be replaced with the value.
|
||||||
|
pub type GlobalExprMap = Lrc<Vec<(Expr, Expr)>>;
|
||||||
|
|
||||||
|
/// Create a global inlining pass, which replaces expressions with the specified
|
||||||
|
/// value.
|
||||||
pub fn inline_globals(
|
pub fn inline_globals(
|
||||||
envs: Lrc<AHashMap<JsWord, Expr>>,
|
envs: Lrc<AHashMap<JsWord, Expr>>,
|
||||||
globals: Lrc<AHashMap<JsWord, Expr>>,
|
globals: Lrc<AHashMap<JsWord, Expr>>,
|
||||||
typeofs: Lrc<AHashMap<JsWord, JsWord>>,
|
typeofs: Lrc<AHashMap<JsWord, JsWord>>,
|
||||||
|
) -> impl Fold + VisitMut {
|
||||||
|
inline_globals2(envs, globals, Default::default(), typeofs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a global inlining pass, which replaces expressions with the specified
|
||||||
|
/// value.
|
||||||
|
///
|
||||||
|
/// See [GlobalExprMap] for description.
|
||||||
|
///
|
||||||
|
/// Note: Values specified in `global_exprs` have higher precedence than
|
||||||
|
pub fn inline_globals2(
|
||||||
|
envs: Lrc<AHashMap<JsWord, Expr>>,
|
||||||
|
globals: Lrc<AHashMap<JsWord, Expr>>,
|
||||||
|
global_exprs: GlobalExprMap,
|
||||||
|
typeofs: Lrc<AHashMap<JsWord, JsWord>>,
|
||||||
) -> impl Fold + VisitMut {
|
) -> impl Fold + VisitMut {
|
||||||
as_folder(InlineGlobals {
|
as_folder(InlineGlobals {
|
||||||
envs,
|
envs,
|
||||||
globals,
|
globals,
|
||||||
|
global_exprs,
|
||||||
typeofs,
|
typeofs,
|
||||||
bindings: Default::default(),
|
bindings: Default::default(),
|
||||||
})
|
})
|
||||||
@ -26,6 +49,8 @@ pub fn inline_globals(
|
|||||||
struct InlineGlobals {
|
struct InlineGlobals {
|
||||||
envs: Lrc<AHashMap<JsWord, Expr>>,
|
envs: Lrc<AHashMap<JsWord, Expr>>,
|
||||||
globals: Lrc<AHashMap<JsWord, Expr>>,
|
globals: Lrc<AHashMap<JsWord, Expr>>,
|
||||||
|
global_exprs: GlobalExprMap,
|
||||||
|
|
||||||
typeofs: Lrc<AHashMap<JsWord, JsWord>>,
|
typeofs: Lrc<AHashMap<JsWord, JsWord>>,
|
||||||
|
|
||||||
bindings: Lrc<AHashSet<Id>>,
|
bindings: Lrc<AHashSet<Id>>,
|
||||||
@ -44,14 +69,28 @@ impl VisitMut for InlineGlobals {
|
|||||||
noop_visit_mut_type!();
|
noop_visit_mut_type!();
|
||||||
|
|
||||||
fn visit_mut_expr(&mut self, expr: &mut Expr) {
|
fn visit_mut_expr(&mut self, expr: &mut Expr) {
|
||||||
expr.visit_mut_children_with(self);
|
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Ident(Ident { ref sym, span, .. }) => {
|
Expr::Ident(Ident { ref sym, span, .. }) => {
|
||||||
if self.bindings.contains(&(sym.clone(), span.ctxt)) {
|
if self.bindings.contains(&(sym.clone(), span.ctxt)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (key, value) in self.global_exprs.iter() {
|
||||||
|
if key.eq_ignore_span(&*expr) {
|
||||||
|
*expr = value.clone();
|
||||||
|
expr.visit_mut_with(self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expr.visit_mut_children_with(self);
|
||||||
|
|
||||||
|
match expr {
|
||||||
|
Expr::Ident(Ident { ref sym, .. }) => {
|
||||||
// It's ok because we don't recurse into member expressions.
|
// It's ok because we don't recurse into member expressions.
|
||||||
if let Some(value) = self.globals.get(sym) {
|
if let Some(value) = self.globals.get(sym) {
|
||||||
let mut value = value.clone();
|
let mut value = value.clone();
|
||||||
@ -152,6 +191,30 @@ impl VisitMut for InlineGlobals {
|
|||||||
module.visit_mut_children_with(self);
|
module.visit_mut_children_with(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_mut_prop(&mut self, p: &mut Prop) {
|
||||||
|
p.visit_mut_children_with(self);
|
||||||
|
|
||||||
|
match p {
|
||||||
|
Prop::Shorthand(i) => {
|
||||||
|
if self.bindings.contains(&i.to_id()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's ok because we don't recurse into member expressions.
|
||||||
|
if let Some(mut value) = self.globals.get(&i.sym).cloned().map(Box::new) {
|
||||||
|
value.visit_mut_with(self);
|
||||||
|
*p = Prop::KeyValue(KeyValueProp {
|
||||||
|
key: PropName::Ident(i.clone()),
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_mut_script(&mut self, script: &mut Script) {
|
fn visit_mut_script(&mut self, script: &mut Script) {
|
||||||
self.bindings = Lrc::new(collect_decls(&*script));
|
self.bindings = Lrc::new(collect_decls(&*script));
|
||||||
|
|
||||||
@ -212,12 +275,7 @@ mod tests {
|
|||||||
|
|
||||||
test!(
|
test!(
|
||||||
::swc_ecma_parser::Syntax::default(),
|
::swc_ecma_parser::Syntax::default(),
|
||||||
|tester| as_folder(InlineGlobals {
|
|tester| inline_globals(envs(tester, &[]), globals(tester, &[]), Default::default(),),
|
||||||
envs: envs(tester, &[]),
|
|
||||||
globals: globals(tester, &[]),
|
|
||||||
typeofs: Default::default(),
|
|
||||||
bindings: Default::default()
|
|
||||||
}),
|
|
||||||
issue_215,
|
issue_215,
|
||||||
r#"if (process.env.x === 'development') {}"#,
|
r#"if (process.env.x === 'development') {}"#,
|
||||||
r#"if (process.env.x === 'development') {}"#
|
r#"if (process.env.x === 'development') {}"#
|
||||||
@ -225,12 +283,11 @@ mod tests {
|
|||||||
|
|
||||||
test!(
|
test!(
|
||||||
::swc_ecma_parser::Syntax::default(),
|
::swc_ecma_parser::Syntax::default(),
|
||||||
|tester| as_folder(InlineGlobals {
|
|tester| inline_globals(
|
||||||
envs: envs(tester, &[("NODE_ENV", "development")]),
|
envs(tester, &[("NODE_ENV", "development")]),
|
||||||
globals: globals(tester, &[]),
|
globals(tester, &[]),
|
||||||
typeofs: Default::default(),
|
Default::default(),
|
||||||
bindings: Default::default()
|
),
|
||||||
}),
|
|
||||||
node_env,
|
node_env,
|
||||||
r#"if (process.env.NODE_ENV === 'development') {}"#,
|
r#"if (process.env.NODE_ENV === 'development') {}"#,
|
||||||
r#"if ('development' === 'development') {}"#
|
r#"if ('development' === 'development') {}"#
|
||||||
@ -238,25 +295,23 @@ mod tests {
|
|||||||
|
|
||||||
test!(
|
test!(
|
||||||
::swc_ecma_parser::Syntax::default(),
|
::swc_ecma_parser::Syntax::default(),
|
||||||
|tester| as_folder(InlineGlobals {
|
|tester| inline_globals(
|
||||||
envs: envs(tester, &[]),
|
envs(tester, &[]),
|
||||||
globals: globals(tester, &[("__DEBUG__", "true")]),
|
globals(tester, &[("__DEBUG__", "true")]),
|
||||||
typeofs: Default::default(),
|
Default::default(),
|
||||||
bindings: Default::default()
|
),
|
||||||
}),
|
globals_simple,
|
||||||
inline_globals,
|
|
||||||
r#"if (__DEBUG__) {}"#,
|
r#"if (__DEBUG__) {}"#,
|
||||||
r#"if (true) {}"#
|
r#"if (true) {}"#
|
||||||
);
|
);
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
::swc_ecma_parser::Syntax::default(),
|
::swc_ecma_parser::Syntax::default(),
|
||||||
|tester| as_folder(InlineGlobals {
|
|tester| inline_globals(
|
||||||
envs: envs(tester, &[]),
|
envs(tester, &[]),
|
||||||
globals: globals(tester, &[("debug", "true")]),
|
globals(tester, &[("debug", "true")]),
|
||||||
typeofs: Default::default(),
|
Default::default(),
|
||||||
bindings: Default::default()
|
),
|
||||||
}),
|
|
||||||
non_global,
|
non_global,
|
||||||
r#"if (foo.debug) {}"#,
|
r#"if (foo.debug) {}"#,
|
||||||
r#"if (foo.debug) {}"#
|
r#"if (foo.debug) {}"#
|
||||||
@ -264,12 +319,7 @@ mod tests {
|
|||||||
|
|
||||||
test!(
|
test!(
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|tester| as_folder(InlineGlobals {
|
|tester| inline_globals(envs(tester, &[]), globals(tester, &[]), Default::default(),),
|
||||||
envs: envs(tester, &[]),
|
|
||||||
globals: globals(tester, &[]),
|
|
||||||
typeofs: Default::default(),
|
|
||||||
bindings: Default::default()
|
|
||||||
}),
|
|
||||||
issue_417_1,
|
issue_417_1,
|
||||||
"const test = process.env['x']",
|
"const test = process.env['x']",
|
||||||
"const test = process.env['x']"
|
"const test = process.env['x']"
|
||||||
@ -277,12 +327,11 @@ mod tests {
|
|||||||
|
|
||||||
test!(
|
test!(
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|tester| as_folder(InlineGlobals {
|
|tester| inline_globals(
|
||||||
envs: envs(tester, &[("x", "FOO")]),
|
envs(tester, &[("x", "FOO")]),
|
||||||
globals: globals(tester, &[]),
|
globals(tester, &[]),
|
||||||
typeofs: Default::default(),
|
Default::default(),
|
||||||
bindings: Default::default()
|
),
|
||||||
}),
|
|
||||||
issue_417_2,
|
issue_417_2,
|
||||||
"const test = process.env['x']",
|
"const test = process.env['x']",
|
||||||
"const test = 'FOO'"
|
"const test = 'FOO'"
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
pub use self::{
|
pub use self::{
|
||||||
const_modules::const_modules, inline_globals::inline_globals, json_parse::json_parse,
|
const_modules::const_modules,
|
||||||
|
inline_globals::{inline_globals, inline_globals2, GlobalExprMap},
|
||||||
|
json_parse::json_parse,
|
||||||
simplify::simplifier,
|
simplify::simplifier,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
complete_output, get_compiler,
|
complete_output, get_compiler,
|
||||||
util::{CtxtExt, MapErr},
|
util::{try_with, CtxtExt, MapErr},
|
||||||
};
|
};
|
||||||
use napi::{CallContext, JsObject, Task};
|
use napi::{CallContext, JsObject, Task};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use swc::{try_with_handler, TransformOutput};
|
use swc::TransformOutput;
|
||||||
use swc_common::{collections::AHashMap, sync::Lrc, FileName, SourceFile, SourceMap};
|
use swc_common::{collections::AHashMap, sync::Lrc, FileName, SourceFile, SourceMap};
|
||||||
|
|
||||||
struct MinifyTask {
|
struct MinifyTask {
|
||||||
@ -48,7 +48,7 @@ impl Task for MinifyTask {
|
|||||||
type JsValue = JsObject;
|
type JsValue = JsObject;
|
||||||
|
|
||||||
fn compute(&mut self) -> napi::Result<Self::Output> {
|
fn compute(&mut self) -> napi::Result<Self::Output> {
|
||||||
try_with_handler(self.c.cm.clone(), |handler| {
|
try_with(self.c.cm.clone(), |handler| {
|
||||||
let fm = self.code.to_file(self.c.cm.clone());
|
let fm = self.code.to_file(self.c.cm.clone());
|
||||||
|
|
||||||
self.c.minify(fm, &handler, &self.opts)
|
self.c.minify(fm, &handler, &self.opts)
|
||||||
@ -82,8 +82,7 @@ pub fn minify_sync(cx: CallContext) -> napi::Result<JsObject> {
|
|||||||
|
|
||||||
let fm = code.to_file(c.cm.clone());
|
let fm = code.to_file(c.cm.clone());
|
||||||
|
|
||||||
let output =
|
let output = try_with(c.cm.clone(), |handler| c.minify(fm, &handler, &opts)).convert_err()?;
|
||||||
try_with_handler(c.cm.clone(), |handler| c.minify(fm, &handler, &opts)).convert_err()?;
|
|
||||||
|
|
||||||
complete_output(&cx.env, output)
|
complete_output(&cx.env, output)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
get_compiler,
|
get_compiler,
|
||||||
util::{CtxtExt, MapErr},
|
util::{try_with, CtxtExt, MapErr},
|
||||||
};
|
};
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use napi::{CallContext, Either, Env, JsObject, JsString, JsUndefined, Task};
|
use napi::{CallContext, Either, Env, JsObject, JsString, JsUndefined, Task};
|
||||||
@ -8,7 +8,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use swc::{config::ParseOptions, try_with_handler, Compiler};
|
use swc::{config::ParseOptions, Compiler};
|
||||||
use swc_common::{FileName, SourceFile};
|
use swc_common::{FileName, SourceFile};
|
||||||
use swc_ecma_ast::Program;
|
use swc_ecma_ast::Program;
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ impl Task for ParseTask {
|
|||||||
type JsValue = JsString;
|
type JsValue = JsString;
|
||||||
|
|
||||||
fn compute(&mut self) -> napi::Result<Self::Output> {
|
fn compute(&mut self) -> napi::Result<Self::Output> {
|
||||||
let program = try_with_handler(self.c.cm.clone(), |handler| {
|
let program = try_with(self.c.cm.clone(), |handler| {
|
||||||
self.c.parse_js(
|
self.c.parse_js(
|
||||||
self.fm.clone(),
|
self.fm.clone(),
|
||||||
&handler,
|
&handler,
|
||||||
@ -63,7 +63,7 @@ impl Task for ParseFileTask {
|
|||||||
type JsValue = JsString;
|
type JsValue = JsString;
|
||||||
|
|
||||||
fn compute(&mut self) -> napi::Result<Self::Output> {
|
fn compute(&mut self) -> napi::Result<Self::Output> {
|
||||||
try_with_handler(self.c.cm.clone(), |handler| {
|
try_with(self.c.cm.clone(), |handler| {
|
||||||
self.c.run(|| {
|
self.c.run(|| {
|
||||||
let fm = self
|
let fm = self
|
||||||
.c
|
.c
|
||||||
@ -125,7 +125,7 @@ pub fn parse_sync(cx: CallContext) -> napi::Result<JsString> {
|
|||||||
FileName::Anon
|
FileName::Anon
|
||||||
};
|
};
|
||||||
|
|
||||||
let program = try_with_handler(c.cm.clone(), |handler| {
|
let program = try_with(c.cm.clone(), |handler| {
|
||||||
c.run(|| {
|
c.run(|| {
|
||||||
let fm = c.cm.new_source_file(filename, src);
|
let fm = c.cm.new_source_file(filename, src);
|
||||||
c.parse_js(
|
c.parse_js(
|
||||||
@ -150,7 +150,7 @@ pub fn parse_file_sync(cx: CallContext) -> napi::Result<JsString> {
|
|||||||
let options: ParseOptions = cx.get_deserialized(1)?;
|
let options: ParseOptions = cx.get_deserialized(1)?;
|
||||||
|
|
||||||
let program = {
|
let program = {
|
||||||
try_with_handler(c.cm.clone(), |handler| {
|
try_with(c.cm.clone(), |handler| {
|
||||||
let fm =
|
let fm =
|
||||||
c.cm.load_file(Path::new(path.as_str()?))
|
c.cm.load_file(Path::new(path.as_str()?))
|
||||||
.expect("failed to read program file");
|
.expect("failed to read program file");
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
complete_output, get_compiler,
|
complete_output, get_compiler,
|
||||||
util::{deserialize_json, CtxtExt, MapErr},
|
util::{deserialize_json, try_with, CtxtExt, MapErr},
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Error};
|
use anyhow::{Context as _, Error};
|
||||||
use napi::{CallContext, Env, JsBoolean, JsObject, JsString, Task};
|
use napi::{CallContext, Env, JsBoolean, JsObject, JsString, Task};
|
||||||
@ -9,7 +9,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use swc::{config::Options, try_with_handler, Compiler, TransformOutput};
|
use swc::{config::Options, Compiler, TransformOutput};
|
||||||
use swc_common::{FileName, SourceFile};
|
use swc_common::{FileName, SourceFile};
|
||||||
use swc_ecma_ast::Program;
|
use swc_ecma_ast::Program;
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ impl Task for TransformTask {
|
|||||||
type JsValue = JsObject;
|
type JsValue = JsObject;
|
||||||
|
|
||||||
fn compute(&mut self) -> napi::Result<Self::Output> {
|
fn compute(&mut self) -> napi::Result<Self::Output> {
|
||||||
try_with_handler(self.c.cm.clone(), |handler| {
|
try_with(self.c.cm.clone(), |handler| {
|
||||||
self.c.run(|| match self.input {
|
self.c.run(|| match self.input {
|
||||||
Input::Program(ref s) => {
|
Input::Program(ref s) => {
|
||||||
let program: Program =
|
let program: Program =
|
||||||
@ -93,7 +93,7 @@ where
|
|||||||
options.config.adjust(Path::new(&options.filename));
|
options.config.adjust(Path::new(&options.filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = try_with_handler(c.cm.clone(), |handler| {
|
let output = try_with(c.cm.clone(), |handler| {
|
||||||
c.run(|| {
|
c.run(|| {
|
||||||
if is_module.get_value()? {
|
if is_module.get_value()? {
|
||||||
let program: Program =
|
let program: Program =
|
||||||
|
@ -1,7 +1,35 @@
|
|||||||
use anyhow::{Context, Error};
|
use anyhow::{anyhow, Context, Error};
|
||||||
use napi::{CallContext, JsBuffer, Status};
|
use napi::{CallContext, JsBuffer, Status};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::any::type_name;
|
use std::{
|
||||||
|
any::type_name,
|
||||||
|
panic::{catch_unwind, AssertUnwindSafe},
|
||||||
|
};
|
||||||
|
use swc::try_with_handler;
|
||||||
|
use swc_common::{errors::Handler, sync::Lrc, SourceMap};
|
||||||
|
|
||||||
|
pub fn try_with<F, Ret>(cm: Lrc<SourceMap>, op: F) -> Result<Ret, Error>
|
||||||
|
where
|
||||||
|
F: FnOnce(&Handler) -> Result<Ret, Error>,
|
||||||
|
{
|
||||||
|
try_with_handler(cm, |handler| {
|
||||||
|
//
|
||||||
|
let result = catch_unwind(AssertUnwindSafe(|| op(handler)));
|
||||||
|
|
||||||
|
let p = match result {
|
||||||
|
Ok(v) => return v,
|
||||||
|
Err(v) => v,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(s) = p.downcast_ref::<String>() {
|
||||||
|
Err(anyhow!("failed to handle: {}", s))
|
||||||
|
} else if let Some(s) = p.downcast_ref::<&str>() {
|
||||||
|
Err(anyhow!("failed to handle: {}", s))
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("failed to handle with unknown panic message"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub trait MapErr<T>: Into<Result<T, anyhow::Error>> {
|
pub trait MapErr<T>: Into<Result<T, anyhow::Error>> {
|
||||||
fn convert_err(self) -> napi::Result<T> {
|
fn convert_err(self) -> napi::Result<T> {
|
||||||
|
@ -3,6 +3,7 @@ use crate::{builder::PassBuilder, SwcComments, SwcImportResolver};
|
|||||||
use anyhow::{bail, Context, Error};
|
use anyhow::{bail, Context, Error};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use either::Either;
|
use either::Either;
|
||||||
|
use indexmap::IndexMap;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -23,7 +24,7 @@ use swc_common::{
|
|||||||
errors::Handler,
|
errors::Handler,
|
||||||
FileName, Mark, SourceMap,
|
FileName, Mark, SourceMap,
|
||||||
};
|
};
|
||||||
use swc_ecma_ast::{Expr, ExprStmt, ModuleItem, Stmt};
|
use swc_ecma_ast::Expr;
|
||||||
use swc_ecma_ext_transforms::jest;
|
use swc_ecma_ext_transforms::jest;
|
||||||
use swc_ecma_loader::resolvers::{
|
use swc_ecma_loader::resolvers::{
|
||||||
lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver,
|
lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver,
|
||||||
@ -37,11 +38,12 @@ use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax, TsConfig};
|
|||||||
use swc_ecma_transforms::{
|
use swc_ecma_transforms::{
|
||||||
hygiene, modules,
|
hygiene, modules,
|
||||||
modules::{hoist::import_hoister, path::NodeImportResolver, util::Scope},
|
modules::{hoist::import_hoister, path::NodeImportResolver, util::Scope},
|
||||||
optimization::{const_modules, inline_globals, json_parse, simplifier},
|
optimization::{const_modules, json_parse, simplifier},
|
||||||
pass::{noop, Optional},
|
pass::{noop, Optional},
|
||||||
proposals::{decorators, export_default_from, import_assertions},
|
proposals::{decorators, export_default_from, import_assertions},
|
||||||
react, resolver_with_mark, typescript,
|
react, resolver_with_mark, typescript,
|
||||||
};
|
};
|
||||||
|
use swc_ecma_transforms_optimization::{inline_globals2, GlobalExprMap};
|
||||||
use swc_ecma_visit::Fold;
|
use swc_ecma_visit::Fold;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -218,7 +220,6 @@ impl Options {
|
|||||||
transform.legacy_decorator = true;
|
transform.legacy_decorator = true;
|
||||||
}
|
}
|
||||||
let optimizer = transform.optimizer;
|
let optimizer = transform.optimizer;
|
||||||
let enable_optimizer = optimizer.is_some();
|
|
||||||
|
|
||||||
let const_modules = {
|
let const_modules = {
|
||||||
let enabled = transform.const_modules.is_some();
|
let enabled = transform.const_modules.is_some();
|
||||||
@ -236,6 +237,8 @@ impl Options {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let enable_simplifier = optimizer.as_ref().map(|v| v.simplify).unwrap_or_default();
|
||||||
|
|
||||||
let optimization = {
|
let optimization = {
|
||||||
let pass =
|
let pass =
|
||||||
if let Some(opts) = optimizer.map(|o| o.globals.unwrap_or_else(Default::default)) {
|
if let Some(opts) = optimizer.map(|o| o.globals.unwrap_or_else(Default::default)) {
|
||||||
@ -255,7 +258,7 @@ impl Options {
|
|||||||
const_modules,
|
const_modules,
|
||||||
optimization,
|
optimization,
|
||||||
Optional::new(export_default_from(), syntax.export_default_from()),
|
Optional::new(export_default_from(), syntax.export_default_from()),
|
||||||
Optional::new(simplifier(Default::default()), enable_optimizer),
|
Optional::new(simplifier(Default::default()), enable_simplifier),
|
||||||
json_parse_pass
|
json_parse_pass
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -968,6 +971,9 @@ pub struct OptimizerConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub globals: Option<GlobalPassOption>,
|
pub globals: Option<GlobalPassOption>,
|
||||||
|
|
||||||
|
#[serde(default = "true_by_default")]
|
||||||
|
pub simplify: bool,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub jsonify: Option<JsonifyOption>,
|
pub jsonify: Option<JsonifyOption>,
|
||||||
}
|
}
|
||||||
@ -987,7 +993,7 @@ fn default_jsonify_min_cost() -> usize {
|
|||||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||||
pub struct GlobalPassOption {
|
pub struct GlobalPassOption {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub vars: AHashMap<JsWord, JsWord>,
|
pub vars: IndexMap<JsWord, JsWord, ahash::RandomState>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub envs: GlobalInliningPassEnvs,
|
pub envs: GlobalInliningPassEnvs,
|
||||||
|
|
||||||
@ -1016,6 +1022,28 @@ impl GlobalPassOption {
|
|||||||
pub fn build(self, cm: &SourceMap, handler: &Handler) -> impl 'static + Fold {
|
pub fn build(self, cm: &SourceMap, handler: &Handler) -> impl 'static + Fold {
|
||||||
type ValuesMap = Arc<AHashMap<JsWord, Expr>>;
|
type ValuesMap = Arc<AHashMap<JsWord, Expr>>;
|
||||||
|
|
||||||
|
fn expr(cm: &SourceMap, handler: &Handler, src: String) -> Box<Expr> {
|
||||||
|
let fm = cm.new_source_file(FileName::Anon, src);
|
||||||
|
let lexer = Lexer::new(
|
||||||
|
Syntax::Es(Default::default()),
|
||||||
|
Default::default(),
|
||||||
|
StringInput::from(&*fm),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut p = Parser::new_from(lexer);
|
||||||
|
let expr = p.parse_expr();
|
||||||
|
|
||||||
|
for e in p.take_errors() {
|
||||||
|
e.into_diagnostic(handler).emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
match expr {
|
||||||
|
Ok(v) => v,
|
||||||
|
_ => panic!("{} is not a valid expression", fm.src),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn mk_map(
|
fn mk_map(
|
||||||
cm: &SourceMap,
|
cm: &SourceMap,
|
||||||
handler: &Handler,
|
handler: &Handler,
|
||||||
@ -1031,36 +1059,10 @@ impl GlobalPassOption {
|
|||||||
(*v).into()
|
(*v).into()
|
||||||
};
|
};
|
||||||
let v_str = v.clone();
|
let v_str = v.clone();
|
||||||
let fm = cm.new_source_file(FileName::Custom(format!("GLOBAL.{}", k)), v);
|
|
||||||
let lexer = Lexer::new(
|
|
||||||
Syntax::Es(Default::default()),
|
|
||||||
Default::default(),
|
|
||||||
StringInput::from(&*fm),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut p = Parser::new_from(lexer);
|
let e = expr(cm, handler, v_str);
|
||||||
let module = p.parse_module();
|
|
||||||
|
|
||||||
for e in p.take_errors() {
|
m.insert((*k).into(), *e);
|
||||||
e.into_diagnostic(handler).emit()
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut module = module
|
|
||||||
.map_err(|e| e.into_diagnostic(handler).emit())
|
|
||||||
.unwrap_or_else(|()| {
|
|
||||||
panic!(
|
|
||||||
"failed to parse global variable {}=`{}` as module",
|
|
||||||
k, v_str
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let expr = match module.body.pop() {
|
|
||||||
Some(ModuleItem::Stmt(Stmt::Expr(ExprStmt { expr, .. }))) => *expr,
|
|
||||||
_ => panic!("{} is not a valid expression", v_str),
|
|
||||||
};
|
|
||||||
|
|
||||||
m.insert((*k).into(), expr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Arc::new(m)
|
Arc::new(m)
|
||||||
@ -1117,6 +1119,37 @@ impl GlobalPassOption {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let global_exprs = {
|
||||||
|
static CACHE: Lazy<DashMap<Vec<(JsWord, JsWord)>, GlobalExprMap, ahash::RandomState>> =
|
||||||
|
Lazy::new(|| Default::default());
|
||||||
|
|
||||||
|
let cache_key = self
|
||||||
|
.vars
|
||||||
|
.iter()
|
||||||
|
.filter(|(k, _)| k.contains('.'))
|
||||||
|
.map(|(k, v)| (k.clone(), v.clone()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if let Some(v) = CACHE.get(&cache_key) {
|
||||||
|
(*v).clone()
|
||||||
|
} else {
|
||||||
|
let map = self
|
||||||
|
.vars
|
||||||
|
.iter()
|
||||||
|
.filter(|(k, _)| k.contains('.'))
|
||||||
|
.map(|(k, v)| {
|
||||||
|
(
|
||||||
|
*expr(cm, handler, k.to_string()),
|
||||||
|
*expr(cm, handler, v.to_string()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let map = Arc::new(map);
|
||||||
|
CACHE.insert(cache_key, map.clone());
|
||||||
|
map
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let global_map = {
|
let global_map = {
|
||||||
static CACHE: Lazy<DashMap<Vec<(JsWord, JsWord)>, ValuesMap, ahash::RandomState>> =
|
static CACHE: Lazy<DashMap<Vec<(JsWord, JsWord)>, ValuesMap, ahash::RandomState>> =
|
||||||
Lazy::new(|| Default::default());
|
Lazy::new(|| Default::default());
|
||||||
@ -1124,18 +1157,24 @@ impl GlobalPassOption {
|
|||||||
let cache_key = self
|
let cache_key = self
|
||||||
.vars
|
.vars
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|(k, _)| !k.contains('.'))
|
||||||
.map(|(k, v)| (k.clone(), v.clone()))
|
.map(|(k, v)| (k.clone(), v.clone()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if let Some(v) = CACHE.get(&cache_key) {
|
if let Some(v) = CACHE.get(&cache_key) {
|
||||||
(*v).clone()
|
(*v).clone()
|
||||||
} else {
|
} else {
|
||||||
let map = mk_map(cm, handler, self.vars.into_iter(), false);
|
let map = mk_map(
|
||||||
|
cm,
|
||||||
|
handler,
|
||||||
|
self.vars.into_iter().filter(|(k, _)| !k.contains('.')),
|
||||||
|
false,
|
||||||
|
);
|
||||||
CACHE.insert(cache_key, map.clone());
|
CACHE.insert(cache_key, map.clone());
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
inline_globals(env_map, global_map, Arc::new(self.typeofs))
|
inline_globals2(env_map, global_map, global_exprs, Arc::new(self.typeofs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"jsc": {
|
"jsc": {
|
||||||
"transform": {
|
"transform": {
|
||||||
"optimizer": {
|
"optimizer": {
|
||||||
|
"simplify": false,
|
||||||
"globals": {
|
"globals": {
|
||||||
"envs": {
|
"envs": {
|
||||||
"NODE_ENV_ALT": "true"
|
"NODE_ENV_ALT": "true"
|
@ -2,6 +2,7 @@
|
|||||||
"jsc": {
|
"jsc": {
|
||||||
"transform": {
|
"transform": {
|
||||||
"optimizer": {
|
"optimizer": {
|
||||||
|
"simplify": false,
|
||||||
"globals": {
|
"globals": {
|
||||||
"envs": {
|
"envs": {
|
||||||
"NODE_ENV_ALT": "true"
|
"NODE_ENV_ALT": "true"
|
14
tests/fixture/globals/member-expr/1/input/.swcrc
Normal file
14
tests/fixture/globals/member-expr/1/input/.swcrc
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"jsc": {
|
||||||
|
"transform": {
|
||||||
|
"optimizer": {
|
||||||
|
"simplify": false,
|
||||||
|
"globals": {
|
||||||
|
"vars": {
|
||||||
|
"process.browser": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
tests/fixture/globals/member-expr/1/input/index.js
Normal file
4
tests/fixture/globals/member-expr/1/input/index.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
if (process.browser) {
|
||||||
|
console.log('Pass')
|
||||||
|
}
|
3
tests/fixture/globals/member-expr/1/output/index.js
Normal file
3
tests/fixture/globals/member-expr/1/output/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
if (true) {
|
||||||
|
console.log('Pass');
|
||||||
|
}
|
15
tests/fixture/globals/member-expr/precendence/input/.swcrc
Normal file
15
tests/fixture/globals/member-expr/precendence/input/.swcrc
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"jsc": {
|
||||||
|
"transform": {
|
||||||
|
"optimizer": {
|
||||||
|
"simplify": false,
|
||||||
|
"globals": {
|
||||||
|
"vars": {
|
||||||
|
"value.debug": "true",
|
||||||
|
"value": "'foo'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
console.log(value.debug)
|
||||||
|
|
||||||
|
if (value.debug) {
|
||||||
|
console.log('Pass')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(value)
|
@ -0,0 +1,5 @@
|
|||||||
|
console.log(true);
|
||||||
|
if (true) {
|
||||||
|
console.log('Pass');
|
||||||
|
}
|
||||||
|
console.log('foo');
|
@ -2,6 +2,7 @@
|
|||||||
"jsc": {
|
"jsc": {
|
||||||
"transform": {
|
"transform": {
|
||||||
"optimizer": {
|
"optimizer": {
|
||||||
|
"simplify": false,
|
||||||
"globals": {
|
"globals": {
|
||||||
"typeofs": {
|
"typeofs": {
|
||||||
"window": "object"
|
"window": "object"
|
Loading…
Reference in New Issue
Block a user