Skip to content

Commit

Permalink
feat: reapply 1499 and fix import meta env (#1505)
Browse files Browse the repository at this point in the history
* Reapply "refactor: define env (#1499)" (#1504)

This reverts commit 4db869a.

* feat: reapply 1499 and fix import meta env

* fix: complicated define env replace
  • Loading branch information
xusd320 authored Aug 21, 2024
1 parent e09880d commit c9a8c59
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 144 deletions.
11 changes: 4 additions & 7 deletions crates/mako/src/build/transform.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::sync::Arc;

use anyhow::Result;
use swc_core::common::sync::Lrc;
use swc_core::common::GLOBALS;
use swc_core::css::ast::{AtRule, AtRulePrelude, ImportHref, Rule, Str, Stylesheet, UrlValue};
use swc_core::css::compat::compiler::{self, Compiler};
Expand Down Expand Up @@ -33,6 +32,7 @@ use crate::visitors::dynamic_import_to_require::DynamicImportToRequire;
use crate::visitors::env_replacer::{build_env_map, EnvReplacer};
use crate::visitors::fix_helper_inject_position::FixHelperInjectPosition;
use crate::visitors::fix_symbol_conflict::FixSymbolConflict;
use crate::visitors::import_meta_env_replacer::ImportMetaEnvReplacer;
use crate::visitors::import_template_to_string_literal::ImportTemplateToStringLiteral;
use crate::visitors::new_url_assets::NewUrlAssets;
use crate::visitors::provide::Provide;
Expand Down Expand Up @@ -114,18 +114,15 @@ impl Transform {
&unresolved_mark,
));
}
// TODO: refact env replacer
{
let mut define = context.config.define.clone();
let mode = context.config.mode.to_string();
define
.entry("NODE_ENV".to_string())
.entry("process.env.NODE_ENV".to_string())
.or_insert_with(|| format!("\"{}\"", mode).into());
let env_map = build_env_map(define, &context)?;
visitors.push(Box::new(EnvReplacer::new(
Lrc::new(env_map),
unresolved_mark,
)));
visitors.push(Box::new(EnvReplacer::new(env_map, unresolved_mark)));
visitors.push(Box::new(ImportMetaEnvReplacer::new(mode)));
}
visitors.push(Box::new(TryResolve {
path: file.path.to_string_lossy().to_string(),
Expand Down
265 changes: 128 additions & 137 deletions crates/mako/src/visitors/env_replacer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,172 +4,110 @@ use std::sync::Arc;

use anyhow::{anyhow, Result};
use serde_json::Value;
use swc_core::common::collections::AHashMap;
use swc_core::common::sync::Lrc;
use swc_core::common::{Mark, DUMMY_SP};
use swc_core::ecma::ast::{
ArrayLit, Bool, ComputedPropName, Expr, ExprOrSpread, Ident, KeyValueProp, Lit, MemberExpr,
MemberProp, MetaPropExpr, MetaPropKind, ModuleItem, Null, Number, ObjectLit, Prop, PropName,
PropOrSpread, Stmt, Str,
MemberProp, ModuleItem, Null, Number, ObjectLit, Prop, PropOrSpread, Stmt, Str,
};
use swc_core::ecma::atoms::{js_word, JsWord};
use swc_core::ecma::utils::{quote_ident, ExprExt};
use swc_core::ecma::visit::{VisitMut, VisitMutWith};

use crate::ast::js_ast::JsAst;
use crate::compiler::Context;
use crate::config::ConfigError;

enum EnvsType {
Node(Lrc<AHashMap<JsWord, Expr>>),
Browser(Lrc<AHashMap<String, Expr>>),
}

#[derive(Debug)]
pub struct EnvReplacer {
unresolved_mark: Mark,
envs: Lrc<AHashMap<JsWord, Expr>>,
meta_envs: Lrc<AHashMap<String, Expr>>,
define: HashMap<String, Expr>,
}

impl EnvReplacer {
pub fn new(envs: Lrc<AHashMap<JsWord, Expr>>, unresolved_mark: Mark) -> Self {
let mut meta_env_map = AHashMap::default();

// generate meta_envs from envs
for (k, v) in envs.iter() {
// convert NODE_ENV to MODE
let key: String = if k.eq(&js_word!("NODE_ENV")) {
"MODE".into()
} else {
k.to_string()
};

meta_env_map.insert(key, v.clone());
}

pub fn new(define: HashMap<String, Expr>, unresolved_mark: Mark) -> Self {
Self {
unresolved_mark,
envs,
meta_envs: Lrc::new(meta_env_map),
define,
}
}

fn get_env(envs: &EnvsType, sym: &JsWord) -> Option<Expr> {
match envs {
EnvsType::Node(envs) => envs.get(sym).cloned(),
EnvsType::Browser(envs) => envs.get(&sym.to_string()).cloned(),
}
fn get_define_env(&self, key: &str) -> Option<Expr> {
self.define.get(key).cloned()
}
}
impl VisitMut for EnvReplacer {
fn visit_mut_expr(&mut self, expr: &mut Expr) {
if let Expr::Ident(Ident { ref sym, span, .. }) = expr {
let envs = EnvsType::Node(self.envs.clone());

if let Expr::Ident(Ident { span, .. }) = expr {
// 先判断 env 中的变量名称,是否是上下文中已经存在的变量名称
if span.ctxt.outer() != self.unresolved_mark {
expr.visit_mut_children_with(self);
return;
}

if let Some(env) = EnvReplacer::get_env(&envs, sym) {
// replace with real value if env found
*expr = env;
return;
}
}

if let Expr::Member(MemberExpr { obj, prop, .. }) = expr {
if let Expr::Member(MemberExpr {
obj: first_obj,
prop:
MemberProp::Ident(Ident {
sym: js_word!("env"),
..
}),
..
}) = &**obj
{
// handle `env.XX`
let mut envs = EnvsType::Node(self.envs.clone());

if match &**first_obj {
Expr::Ident(Ident {
sym: js_word!("process"),
..
}) => true,
Expr::MetaProp(MetaPropExpr {
kind: MetaPropKind::ImportMeta,
match expr {
Expr::Member(MemberExpr { obj, prop, .. }) => {
let mut member_visit_path = match prop {
MemberProp::Ident(Ident { sym, .. }) => sym.to_string(),
MemberProp::Computed(ComputedPropName {
expr: expr_computed,
..
}) => {
envs = EnvsType::Browser(self.meta_envs.clone());
true
}
_ => false,
} {
// handle `process.env.XX` and `import.meta.env.XX`
}) => match expr_computed.as_ref() {
Expr::Lit(Lit::Str(Str { value, .. })) => value.to_string(),

Expr::Lit(Lit::Num(Number { value, .. })) => value.to_string(),
_ => return,
},
_ => return,
};

let mut current_member_obj = obj.as_ref();

while let Expr::Member(MemberExpr { obj, prop, .. }) = current_member_obj {
match prop {
MemberProp::Computed(ComputedPropName { expr: c, .. }) => {
if let Expr::Lit(Lit::Str(Str { value: sym, .. })) = &**c {
if let Some(env) = EnvReplacer::get_env(&envs, sym) {
// replace with real value if env found
*expr = env;
} else {
// replace with `undefined` if env not found
*expr = *Box::new(Expr::Ident(Ident::new(
js_word!("undefined"),
DUMMY_SP,
)));
}
}
MemberProp::Ident(Ident { sym, .. }) => {
member_visit_path.push('.');
member_visit_path.push_str(sym.as_ref());
}
MemberProp::Computed(ComputedPropName { expr, .. }) => {
match expr.as_ref() {
Expr::Lit(Lit::Str(Str { value, .. })) => {
member_visit_path.push('.');
member_visit_path.push_str(value.as_ref());
}

MemberProp::Ident(Ident { sym, .. }) => {
if let Some(env) = EnvReplacer::get_env(&envs, sym) {
// replace with real value if env found
*expr = env;
} else {
// replace with `undefined` if env not found
*expr = *Box::new(Expr::Ident(Ident::new(
js_word!("undefined"),
DUMMY_SP,
)));
Expr::Lit(Lit::Num(Number { value, .. })) => {
member_visit_path.push('.');
member_visit_path.push_str(&value.to_string());
}
_ => return,
}
}
_ => {}
_ => return,
}
current_member_obj = obj.as_ref();
}
} else if let Expr::Member(MemberExpr {
obj:
box Expr::MetaProp(MetaPropExpr {
kind: MetaPropKind::ImportMeta,
..
}),
prop:
MemberProp::Ident(Ident {
sym: js_word!("env"),
..
}),
..
}) = *expr
{
// replace independent `import.meta.env` to json object
let mut props = Vec::new();

// convert envs to object properties
for (k, v) in self.meta_envs.iter() {
props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(Ident::new(k.clone().into(), DUMMY_SP)),
value: Box::new(v.clone()),
}))));

if let Expr::Ident(Ident { sym, .. }) = current_member_obj {
member_visit_path.push('.');
member_visit_path.push_str(sym.as_ref());
}
let member_visit_path = member_visit_path
.split('.')
.rev()
.collect::<Vec<&str>>()
.join(".");

if let Some(env) = self.get_define_env(&member_visit_path) {
*expr = env
}
}

*expr = Expr::Object(ObjectLit {
span: DUMMY_SP,
props,
});
Expr::Ident(Ident { sym, .. }) => {
if let Some(env) = self.get_define_env(sym.as_ref()) {
*expr = env
}
}
_ => (),
}

expr.visit_mut_children_with(self);
Expand All @@ -179,11 +117,11 @@ impl VisitMut for EnvReplacer {
pub fn build_env_map(
env_map: HashMap<String, Value>,
context: &Arc<Context>,
) -> Result<AHashMap<JsWord, Expr>> {
let mut map = AHashMap::default();
) -> Result<HashMap<String, Expr>> {
let mut map = HashMap::new();
for (k, v) in env_map.into_iter() {
let expr = get_env_expr(v, context)?;
map.insert(k.into(), expr);
map.insert(k, expr);
}
Ok(map)
}
Expand Down Expand Up @@ -264,7 +202,6 @@ mod tests {

use maplit::hashmap;
use serde_json::{json, Value};
use swc_core::common::sync::Lrc;
use swc_core::common::GLOBALS;
use swc_core::ecma::visit::VisitMutWith;

Expand Down Expand Up @@ -359,28 +296,82 @@ log([
}

#[test]
fn test_undefined_env() {
fn test_stringified_env() {
assert_eq!(
run(
r#"if (process.env.UNDEFINED_ENV === "true") {}"#,
Default::default()
r#"log(A)"#,
hashmap! {
"A".to_string() => json!("{\"v\": 1}")
}
),
r#"if (undefined === "true") {}"#
r#"log(({
"v": 1
}));"#
);
}

#[test]
fn test_stringified_env() {
fn test_dot_key() {
assert_eq!(
run(
r#"log(A)"#,
r#"log(x.y)"#,
hashmap! {
"A".to_string() => json!("{\"v\": 1}")
"x.y".to_string() => json!(true)
}
),
r#"log(({
"v": 1
}));"#
r#"log(true);"#
);
}

#[test]
fn test_deep_dot_key() {
assert_eq!(
run(
r#"log(process.env.A)"#,
hashmap! {
"process.env.A".to_string() => json!(true)
}
),
r#"log(true);"#
);
}

#[test]
fn test_computed() {
assert_eq!(
run(
r#"log(A["B"])"#,
hashmap! {
"A.B".to_string() => json!(1)
}
),
r#"log(1);"#
);
}

#[test]
fn test_computed_number() {
assert_eq!(
run(
r#"log(A[1])"#,
hashmap! {
"A.1".to_string() => json!(1)
}
),
r#"log(1);"#
);
}

#[test]
fn test_complicated() {
assert_eq!(
run(
r#"log(A.v["v"])"#,
hashmap! {
"A.v.v".to_string() => json!(1)
}
),
r#"log(1);"#
);
}

Expand All @@ -389,7 +380,7 @@ log([
let envs = build_env_map(envs, &test_utils.context).unwrap();
let ast = test_utils.ast.js_mut();
GLOBALS.set(&test_utils.context.meta.script.globals, || {
let mut visitor = EnvReplacer::new(Lrc::new(envs), ast.unresolved_mark);
let mut visitor = EnvReplacer::new(envs, ast.unresolved_mark);
ast.ast.visit_mut_with(&mut visitor);
});
test_utils.js_ast_to_code()
Expand Down
Loading

0 comments on commit c9a8c59

Please sign in to comment.