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:
Donny/강동윤 2021-10-19 23:58:10 +09:00 committed by GitHub
parent 9b96885171
commit b0361caa58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 270 additions and 97 deletions

6
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -47,6 +47,7 @@
"elems", "elems",
"esbuild", "esbuild",
"esms", "esms",
"eval",
"Eval", "Eval",
"exponentation", "exponentation",
"fargs", "fargs",

View File

@ -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]

View File

@ -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'"

View File

@ -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,
}; };

View File

@ -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)
} }

View File

@ -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");

View 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 =

View File

@ -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> {

View File

@ -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))
} }
} }

View File

@ -2,6 +2,7 @@
"jsc": { "jsc": {
"transform": { "transform": {
"optimizer": { "optimizer": {
"simplify": false,
"globals": { "globals": {
"envs": { "envs": {
"NODE_ENV_ALT": "true" "NODE_ENV_ALT": "true"

View File

@ -2,6 +2,7 @@
"jsc": { "jsc": {
"transform": { "transform": {
"optimizer": { "optimizer": {
"simplify": false,
"globals": { "globals": {
"envs": { "envs": {
"NODE_ENV_ALT": "true" "NODE_ENV_ALT": "true"

View File

@ -0,0 +1,14 @@
{
"jsc": {
"transform": {
"optimizer": {
"simplify": false,
"globals": {
"vars": {
"process.browser": "true"
}
}
}
}
}
}

View File

@ -0,0 +1,4 @@
if (process.browser) {
console.log('Pass')
}

View File

@ -0,0 +1,3 @@
if (true) {
console.log('Pass');
}

View File

@ -0,0 +1,15 @@
{
"jsc": {
"transform": {
"optimizer": {
"simplify": false,
"globals": {
"vars": {
"value.debug": "true",
"value": "'foo'"
}
}
}
}
}
}

View File

@ -0,0 +1,8 @@
console.log(value.debug)
if (value.debug) {
console.log('Pass')
}
console.log(value)

View File

@ -0,0 +1,5 @@
console.log(true);
if (true) {
console.log('Pass');
}
console.log('foo');

View File

@ -2,6 +2,7 @@
"jsc": { "jsc": {
"transform": { "transform": {
"optimizer": { "optimizer": {
"simplify": false,
"globals": { "globals": {
"typeofs": { "typeofs": {
"window": "object" "window": "object"