Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reapply 1499 and fix import meta env #1505

Merged
merged 4 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
254 changes: 122 additions & 132 deletions crates/mako/src/visitors/env_replacer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,172 +4,122 @@ 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"),
..
}),
match expr {
Expr::Member(MemberExpr {
obj,
prop: MemberProp::Ident(Ident { sym, .. }),
..
}) = &**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,
..
}) => {
envs = EnvsType::Browser(self.meta_envs.clone());
true
}
_ => false,
} {
// handle `process.env.XX` and `import.meta.env.XX`
}) => {
let mut member_visit_path = sym.to_string();
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());
}
_ => (),
}
}
_ => {}
}
current_member_obj = obj.as_ref();
}
} else if let Expr::Member(MemberExpr {
obj:
box Expr::MetaProp(MetaPropExpr {
kind: MetaPropKind::ImportMeta,
..
}),

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::Member(MemberExpr {
xusd320 marked this conversation as resolved.
Show resolved Hide resolved
obj: box Expr::Ident(Ident { sym, .. }),
prop:
MemberProp::Ident(Ident {
sym: js_word!("env"),
MemberProp::Computed(ComputedPropName {
expr: expr_computed,
..
}),
..
}) = *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()),
}))));
}) => match expr_computed.as_ref() {
Expr::Lit(Lit::Str(Str { value, .. })) => {
if let Some(env) = self.get_define_env(&format!("{}.{}", sym, value)) {
*expr = env
}
}

*expr = Expr::Object(ObjectLit {
span: DUMMY_SP,
props,
});
Expr::Lit(Lit::Num(Number { value, .. })) => {
if let Some(env) = self.get_define_env(&format!("{}.{}", sym, value)) {
*expr = env
}
}
_ => (),
},
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 +129,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 +214,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 +308,69 @@ 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);"#
);
}

Expand All @@ -389,7 +379,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