From 76862688e92a9e22b439da146d2087bd26a266ea Mon Sep 17 00:00:00 2001
From: ClarkXia <xiawenwu41@gmail.com>
Date: Tue, 5 Dec 2023 13:44:46 +0800
Subject: [PATCH 1/2] feat: support env replacement

---
 crates/loader_compilation/src/lib.rs          |  10 +-
 .../src/transform/env_replacement.rs          | 384 ++++++++++++++++++
 .../loader_compilation/src/transform/mod.rs   |  17 +-
 crates/loader_compilation/tests/fixtures.rs   |  51 ++-
 .../fixtures/basic/.ice/route-manifest.json   |  22 -
 .../tests/fixtures/basic/input.js             |  11 +-
 .../tests/fixtures/basic/output.js            |   7 +-
 .../tests/fixtures/default/input.js           |   5 +
 .../tests/fixtures/default/output.js          |  19 +
 .../tests/fixtures/multiple/input.js          |   5 +
 .../tests/fixtures/multiple/output.js         |   5 +
 .../tests/fixtures/named/input.js             |   5 +
 .../tests/fixtures/named/output.js            |   4 +
 .../tests/fixtures/require_basic/input.js     |   5 +
 .../tests/fixtures/require_basic/output.js    |   4 +
 .../tests/fixtures/require_default/input.js   |   5 +
 .../tests/fixtures/require_default/output.js  |  19 +
 .../tests/fixtures/require_named/input.js     |   5 +
 .../tests/fixtures/require_named/output.js    |   4 +
 .../tests/fixtures/require_rest/input.js      |   5 +
 .../tests/fixtures/require_rest/output.js     |  20 +
 .../tests/fixtures/require_scoped/input.js    |   8 +
 .../tests/fixtures/require_scoped/output.js   |   9 +
 23 files changed, 586 insertions(+), 43 deletions(-)
 create mode 100644 crates/loader_compilation/src/transform/env_replacement.rs
 delete mode 100644 crates/loader_compilation/tests/fixtures/basic/.ice/route-manifest.json
 create mode 100644 crates/loader_compilation/tests/fixtures/default/input.js
 create mode 100644 crates/loader_compilation/tests/fixtures/default/output.js
 create mode 100644 crates/loader_compilation/tests/fixtures/multiple/input.js
 create mode 100644 crates/loader_compilation/tests/fixtures/multiple/output.js
 create mode 100644 crates/loader_compilation/tests/fixtures/named/input.js
 create mode 100644 crates/loader_compilation/tests/fixtures/named/output.js
 create mode 100644 crates/loader_compilation/tests/fixtures/require_basic/input.js
 create mode 100644 crates/loader_compilation/tests/fixtures/require_basic/output.js
 create mode 100644 crates/loader_compilation/tests/fixtures/require_default/input.js
 create mode 100644 crates/loader_compilation/tests/fixtures/require_default/output.js
 create mode 100644 crates/loader_compilation/tests/fixtures/require_named/input.js
 create mode 100644 crates/loader_compilation/tests/fixtures/require_named/output.js
 create mode 100644 crates/loader_compilation/tests/fixtures/require_rest/input.js
 create mode 100644 crates/loader_compilation/tests/fixtures/require_rest/output.js
 create mode 100644 crates/loader_compilation/tests/fixtures/require_scoped/input.js
 create mode 100644 crates/loader_compilation/tests/fixtures/require_scoped/output.js

diff --git a/crates/loader_compilation/src/lib.rs b/crates/loader_compilation/src/lib.rs
index 5bcb3c9..197808d 100644
--- a/crates/loader_compilation/src/lib.rs
+++ b/crates/loader_compilation/src/lib.rs
@@ -1,3 +1,4 @@
+#![feature(box_patterns)]
 use std::{path::Path, collections::HashMap, sync::Mutex};
 use lazy_static::lazy_static;
 use rspack_ast::RspackAst;
@@ -163,8 +164,11 @@ impl Loader<LoaderRunnerContext> for CompilationLoader {
     if routes_config.is_none() || file_accessed {
       // Load routes config for transform.
       let routes_config_path: std::path::PathBuf = Path::new(compiler_context).join(".ice/route-manifest.json");
-      *routes_config = Some(load_routes_config(&routes_config_path).unwrap());
-
+      let routes_content = load_routes_config(&routes_config_path);
+      if routes_content.is_ok() {
+        *routes_config = Some(routes_content?);
+      }
+      
       if file_accessed {
         // If file accessed, then we need to clear the map for the current compilation.
         file_access.clear();
@@ -175,7 +179,7 @@ impl Loader<LoaderRunnerContext> for CompilationLoader {
     let built = compiler.parse(None, |_| {
       transform(
         &resource_path,
-        routes_config.as_ref().unwrap(),
+        routes_config.as_ref(),
         transform_options
       )
     })?;
diff --git a/crates/loader_compilation/src/transform/env_replacement.rs b/crates/loader_compilation/src/transform/env_replacement.rs
new file mode 100644
index 0000000..ef9e597
--- /dev/null
+++ b/crates/loader_compilation/src/transform/env_replacement.rs
@@ -0,0 +1,384 @@
+use swc_core::{
+  ecma::{
+    ast::*,
+    visit::{Fold, FoldWith},
+  },
+  common::DUMMY_SP,
+};
+struct EnvReplacement {
+  sources: Vec<String>,
+}
+
+fn create_check_expr(meta_value: &str, renderer: &str) -> Expr {
+  Expr::Bin(BinExpr {
+    span: DUMMY_SP,
+    op: BinaryOp::EqEqEq,
+    left: Box::new(Expr::Member(MemberExpr {
+      span: DUMMY_SP,
+      obj: Box::new(Expr::MetaProp(MetaPropExpr { span: DUMMY_SP, kind: MetaPropKind::ImportMeta })),
+      prop: Ident::new(meta_value.into(), DUMMY_SP).into(),
+    })),
+    right: Box::new(Expr::Lit(Lit::Str(Str {
+      value: renderer.into(),
+      span: DUMMY_SP,
+      raw: None,
+    }))),
+  })
+}
+
+fn create_typeof_check(expr: Expr, check_value: &str, op: BinaryOp) -> Expr {
+  // Create `typeof pha` unary expression
+  let typeof_expr = Expr::Unary(UnaryExpr {
+    op: UnaryOp::TypeOf,
+    arg: Box::new(expr),
+    span: DUMMY_SP,
+  });
+
+  // Create `typeof pha === 'object'` binary expression
+  Expr::Bin(BinExpr {
+    left: Box::new(typeof_expr),
+    op,
+    right: Box::new(Expr::Lit(Lit::Str(Str {
+        value: check_value.into(),
+        span: DUMMY_SP,
+        raw: None,
+    }))),
+    span: DUMMY_SP,
+  })
+}
+
+fn combine_check_exprs(exprs: Vec<Expr>, op: BinaryOp) -> Expr {
+  let mut result = exprs[0].clone();
+  for expr in exprs[1..].iter() {
+    result = Expr::Bin(BinExpr {
+      span: DUMMY_SP,
+      op,
+      left: Box::new(result),
+      right: Box::new(expr.clone()),
+    });
+  }
+  result
+}
+
+fn build_regex_test_expression() -> Expr {
+  // Create the regex literal
+  let regex_pattern = Expr::Lit(Lit::Regex(Regex {
+      exp: ".+AliApp\\((\\w+)\\/((?:\\d+\\.)+\\d+)\\).* .*(WindVane)(?:\\/((?:\\d+\\.)+\\d+))?.*".into(),
+      flags: "".into(),
+      span: DUMMY_SP,
+  }));
+
+  // Create the typeof expression for `navigator`
+  let typeof_navigator = Expr::Unary(UnaryExpr {
+      op: UnaryOp::TypeOf,
+      arg: Box::new(Expr::Ident(Ident::new("navigator".into(), DUMMY_SP))),
+      span: DUMMY_SP,
+  });
+
+  // Create the conditional expression
+  let conditional = Expr::Cond(CondExpr {
+      test: Box::new(typeof_navigator),
+      cons: Box::new(Expr::Bin(BinExpr {
+          left: Box::new(Expr::Member(MemberExpr {
+              obj: Box::new(Expr::Ident(Ident::new("navigator".into(), DUMMY_SP))),
+              prop: MemberProp::Ident(Ident::new("userAgent".into(), DUMMY_SP)),
+              span: DUMMY_SP,
+          })),
+          op: BinaryOp::LogicalOr,
+          right: Box::new(Expr::Member(MemberExpr {
+              obj: Box::new(Expr::Ident(Ident::new("navigator".into(), DUMMY_SP))),
+              prop: MemberProp::Ident(Ident::new("swuserAgent".into(), DUMMY_SP)),
+              span: DUMMY_SP,
+          })),
+          span: DUMMY_SP,
+      })),
+      alt: Box::new(Expr::Lit(Lit::Str(Str {
+          value: "".into(),
+          span: DUMMY_SP,
+          raw: None,
+      }))),
+      span: DUMMY_SP,
+  });
+
+  // Create the 'test' method call on the regex pattern
+  let test_call = Expr::Call(CallExpr {
+      callee: Callee::Expr(Box::new(Expr::Member(MemberExpr {
+          obj: Box::new(regex_pattern),
+          prop: MemberProp::Ident(Ident::new("test".into(), DUMMY_SP)),
+          span: DUMMY_SP,
+      }))),
+      args: vec![ExprOrSpread {
+          spread: None,
+          expr: Box::new(conditional),
+      }],
+      span: DUMMY_SP,
+      type_args: None,
+  });
+
+  test_call
+}
+
+fn get_env_expr(specifier: &Ident) -> Expr {
+  match specifier.sym.as_ref() {
+    "isClient" => {
+      create_check_expr("renderer", "client")
+    },
+    "isServer" => {
+      create_check_expr("renderer", "server")
+    },
+    "isWeb" => {
+      combine_check_exprs(vec![
+        create_check_expr("renderer", "client"),
+        create_check_expr("target", "web"),
+      ], BinaryOp::LogicalAnd)
+    },
+    "isNode" => {
+      create_check_expr("renderer", "server")
+    },
+    "isWeex" => {
+      combine_check_exprs(vec![
+        create_check_expr("renderer", "client"),
+        create_check_expr("target", "weex"),
+      ], BinaryOp::LogicalAnd)
+    },
+    "isKraken" => {
+      combine_check_exprs(vec![
+        create_check_expr("renderer", "client"),
+        create_check_expr("target", "kraken"),
+      ], BinaryOp::LogicalAnd)
+    },
+    "isPHA" => {
+      combine_check_exprs(vec![
+        create_check_expr("renderer", "client"),
+        create_check_expr("target", "web"),
+        create_typeof_check(
+          Expr::Ident(Ident::new("pha".into(), DUMMY_SP)),
+          "object",
+          BinaryOp::EqEqEq,
+        ),
+      ], BinaryOp::LogicalAnd)
+    },
+    "isWindVane" => {
+      combine_check_exprs(vec![
+        create_check_expr("renderer", "client"),
+        build_regex_test_expression(),
+        create_typeof_check(
+          Expr::Ident(Ident::new("WindVane".into(), DUMMY_SP)),
+          "undefined",
+          BinaryOp::NotEqEq,
+        ),
+        create_typeof_check(
+          Expr::Member(MemberExpr {
+            span: DUMMY_SP,
+            obj: Box::new(Expr::Ident(Ident::new("WindVane".into(), DUMMY_SP))),
+            prop: Ident::new("call".into(), DUMMY_SP).into(),
+          }),
+          "undefined",
+          BinaryOp::NotEqEq,
+        ),
+      ], BinaryOp::LogicalAnd)
+    },
+    _ => {
+      // Do not support miniapp env.
+      Expr::Lit(Lit::Bool(Bool {
+        span: DUMMY_SP,
+        value: false,
+      }))
+    }
+  }
+}
+
+fn create_env_declare(specifier: &Ident, imported: &Ident) -> Stmt {
+  let expr = get_env_expr(&specifier);
+
+  return Stmt::Decl(Decl::Var(Box::new(VarDecl {
+    span: DUMMY_SP,
+    kind: VarDeclKind::Var,
+    declare: false,
+    decls: vec![VarDeclarator {
+      span: DUMMY_SP,
+      name: Pat::Ident(BindingIdent {
+        id: imported.clone(),
+        type_ann: Default::default(),
+      }),
+      init: Some(Box::new(expr)),
+      definite: false,
+    }],
+  })));
+}
+
+fn create_env_default_export(export_name: Ident) -> Stmt {
+  Stmt::Decl(Decl::Var(Box::new(VarDecl {
+    span: DUMMY_SP,
+    kind: VarDeclKind::Const,
+    declare: false,
+    decls: vec![VarDeclarator {
+      span: DUMMY_SP,
+      name: Pat::Ident(BindingIdent {
+        id: export_name.clone(),
+        type_ann: Default::default(),
+      }),
+      init: Some(Box::new(Expr::Object(ObjectLit {
+        span: DUMMY_SP,
+        props: vec![
+          "isWeb",
+          "isClient",
+          "isNode",
+          "isWeex",
+          "isKraken",
+          "isMiniApp",
+          "isByteDanceMicroApp",
+          "isBaiduSmartProgram",
+          "isKuaiShouMiniProgram",
+          "isWeChatMiniProgram",
+          "isQuickApp",
+          "isPHA",
+          "isWindVane",
+          "isFRM",
+        ].into_iter().map(|target| PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
+          key: PropName::Ident(Ident::new(target.into(), DUMMY_SP)),
+          value: Box::new(get_env_expr(&Ident::new(target.into(), DUMMY_SP))),
+        })))).collect(),
+      }))),
+      definite: false,
+    }],
+  })))
+}
+
+fn get_env_stmt(sources: &Vec<String>, decls: Vec<VarDeclarator>) -> Vec<Stmt> {
+  let mut stmts = vec![];
+  for decl in decls {
+    if let Some(init) = decl.init {
+      if let Expr::Call(CallExpr {
+        args: ref call_args,
+        callee: Callee::Expr(box Expr::Ident(Ident { ref sym, .. })),
+        ..
+      }) = *init {
+        if sym == "require" && call_args.len() == 1 {
+          // Case const env = require('env');
+          if let ExprOrSpread { expr: box Expr::Lit(Lit::Str(Str { ref value, .. })), .. } = call_args[0] {
+            if sources.iter().any(|s| value == s) {
+              match &decl.name {
+                Pat::Ident(BindingIdent { id, .. }) => {
+                  stmts.push(create_env_default_export(id.clone()));
+                }
+                Pat::Object(ObjectPat { props, .. }) => {
+                  props.iter().for_each(|prop| {
+                    match prop {
+                      ObjectPatProp::Assign(AssignPatProp { key, value, .. }) => {
+                        if value.is_some() {
+                          if let box Expr::Ident(ident) = &value.as_ref().unwrap() {
+                            stmts.push(create_env_declare(key, &ident));
+                          }
+                        } else {
+                          stmts.push(create_env_declare(key, key));
+                        }
+                      }
+                      ObjectPatProp::KeyValue(KeyValuePatProp { key, value, .. }) => {
+                        if let box Pat::Ident(BindingIdent { id , ..}) = &value {
+                          if let PropName::Ident(i) = key {
+                            stmts.push(create_env_declare(i, &id));
+                          }
+                        }
+                      }
+                      ObjectPatProp::Rest(RestPat {arg, ..}) => {
+                        if let box Pat::Ident(BindingIdent { id , ..}) = arg {
+                          stmts.push(create_env_default_export(id.clone()));
+                        }
+                      }
+                    }
+                  });
+                }
+                _ => {}
+              }
+              continue;
+            }
+          }
+        }
+      }
+    }
+  }
+  stmts
+}
+
+impl Fold for EnvReplacement {
+  fn fold_module_items(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> {
+    let mut new_module_items: Vec<ModuleItem> = vec![];
+    for item in items.iter() {
+      match &item {
+        // Import declaration.
+        ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) => {
+          let src = &import_decl.src.value;
+          if self.sources.iter().any(|s| src == s) {
+            // Collect all specifiers.
+            import_decl.specifiers.iter().for_each(|specifier| {
+              match specifier {
+                ImportSpecifier::Named(named_specifier) => {
+                  let imported = match &named_specifier.imported {
+                    Some(ModuleExportName::Ident(ident)) => Some(ident),
+                    _ => None,
+                  };
+                  let s = if imported.is_some() {
+                    imported.unwrap()
+                  } else {
+                    &named_specifier.local
+                  };
+                  new_module_items.push(ModuleItem::Stmt(create_env_declare(s, &named_specifier.local)));
+                }
+                ImportSpecifier::Default(default_specifier) => {
+                  new_module_items.push(ModuleItem::Stmt(create_env_default_export(default_specifier.local.clone())));
+                }
+                ImportSpecifier::Namespace(namespace_specifier) => {
+                  new_module_items.push(ModuleItem::Stmt(create_env_default_export(namespace_specifier.local.clone())));
+                }
+              }
+            });
+            
+          } else {
+            new_module_items.push(item.clone());
+          }
+        }
+        ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))) => {
+          let stmt = get_env_stmt(&self.sources, var_decl.decls.clone());
+          if stmt.len() > 0 {
+            let module_stmts = stmt.into_iter().map(|s| ModuleItem::Stmt(s)).collect::<Vec<ModuleItem>>();
+            new_module_items.extend_from_slice(&module_stmts);
+          } else {
+            new_module_items.push(item.clone());
+          }
+        }
+        _ => {
+          new_module_items.push(item.clone().fold_children_with(self));
+        }
+      }
+    }
+    new_module_items
+  }
+
+  fn fold_block_stmt(&mut self, block: BlockStmt) -> BlockStmt {
+    let mut new_stmts: Vec<Stmt> = vec![];
+    block.stmts.clone().into_iter().for_each(|stmt| {
+      match &stmt {
+        Stmt::Decl(Decl::Var(var_decl)) => {
+          let env_stmts = get_env_stmt(&self.sources, var_decl.decls.clone());
+          if env_stmts.len() > 0 {
+            new_stmts.extend_from_slice(&env_stmts);
+          } else {
+            new_stmts.push(stmt);
+          }
+        }
+        _ => {
+          new_stmts.push(stmt.fold_children_with(self));
+        }
+      }
+    });
+    BlockStmt {
+      stmts: new_stmts,
+      ..block
+    }
+  }
+}
+
+pub fn env_replacement(sources: Vec<String>) -> impl Fold {
+  EnvReplacement { sources }
+}
\ No newline at end of file
diff --git a/crates/loader_compilation/src/transform/mod.rs b/crates/loader_compilation/src/transform/mod.rs
index 9bc09dd..34a69d4 100644
--- a/crates/loader_compilation/src/transform/mod.rs
+++ b/crates/loader_compilation/src/transform/mod.rs
@@ -9,9 +9,11 @@ use swc_core::ecma::{
 
 mod keep_export;
 mod remove_export;
+mod env_replacement;
 
 use keep_export::keep_export;
 use remove_export::remove_export;
+use env_replacement::env_replacement;
 
 macro_rules! either {
   ($config:expr, $f:expr) => {
@@ -63,11 +65,13 @@ pub(crate) fn load_routes_config(path: &Path) -> Result<Vec<String>, Error> {
   parse_routes_config(content)
 }
 
-fn match_route_entry(resource_path: &Path, routes: &Vec<String>) -> bool {
+fn match_route_entry(resource_path: &Path, routes: Option<&Vec<String>>) -> bool {
   let resource_path_str = resource_path.to_str().unwrap();
-  for route in routes {
-    if resource_path_str.ends_with(&route.to_string()) {
-      return true;
+  if let Some(routes) = routes {
+    for route in routes {
+      if resource_path_str.ends_with(&route.to_string()) {
+        return true;
+      }
     }
   }
   false
@@ -89,10 +93,13 @@ pub struct TransformFeatureOptions {
 
 pub(crate) fn transform<'a>(
   resource_path: &'a Path,
-  routes_config: &Vec<String>,
+  routes_config: Option<&Vec<String>>,
   feature_options: &TransformFeatureOptions,
 ) -> impl Fold + 'a {
   chain!(
+    either!(Some(&vec!["@uni/env".to_string(), "universal-env".to_string()]), |options: &Vec<String>| {
+      env_replacement(options.clone())
+    }),
     either!(feature_options.keep_export, |options: &Vec<String>| {
       let mut exports_name = options.clone();
       // Special case for app entry.
diff --git a/crates/loader_compilation/tests/fixtures.rs b/crates/loader_compilation/tests/fixtures.rs
index ae7386a..23ea4b3 100644
--- a/crates/loader_compilation/tests/fixtures.rs
+++ b/crates/loader_compilation/tests/fixtures.rs
@@ -3,8 +3,12 @@ use loader_compilation::{CompilationLoader, LoaderOptions};
 use rspack_core::{
   run_loaders, CompilerContext, CompilerOptions, Loader, LoaderRunnerContext, ResourceData, SideEffectOption,
 };
+use swc_core::ecma::ast::EsVersion;
 use swc_core::base::config::Config;
 
+use rspack_ast::RspackAst;
+use rspack_plugin_javascript::ast;
+
 async fn loader_test(actual: impl AsRef<Path>, expected: impl AsRef<Path>) {
   let tests_path = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"))).join("tests");
   let expected_path = tests_path.join(expected);
@@ -12,6 +16,7 @@ async fn loader_test(actual: impl AsRef<Path>, expected: impl AsRef<Path>) {
   let parent_path = actual_path.parent().unwrap().to_path_buf();
 
   let mut options = Config::default();
+  options.jsc.target = Some(EsVersion::Es2020);
   let (result, _) = run_loaders(
     &[Arc::new(CompilationLoader::new(LoaderOptions {
       swc_options: options,
@@ -92,17 +97,57 @@ async fn loader_test(actual: impl AsRef<Path>, expected: impl AsRef<Path>) {
   .await
   .expect("TODO:")
   .split_into_parts();
-
-  let result = result.content.try_into_string().expect("TODO:");
+  let code: String;
+  let code_ast = result.additional_data.get::<RspackAst>().unwrap();
+  let code_gen_options = result.additional_data.get::<ast::CodegenOptions>().unwrap();
+  if let RspackAst::JavaScript(code_ast) = code_ast {
+    code = ast::stringify(code_ast, code_gen_options.clone()).unwrap().code;
+  } else {
+    panic!("TODO:");
+  }
+  
   if env::var("UPDATE").is_ok() {
+    let result = code.into_bytes();
     fs::write(expected_path, result).expect("TODO:");
   } else {
     let expected = fs::read_to_string(expected_path).expect("TODO:");
-    assert_eq!(result, expected);
+    assert_eq!(code, expected);
   }
 }
 
 #[tokio::test]
 async fn basic() {
   loader_test("fixtures/basic/input.js", "fixtures/basic/output.js").await;
+}
+#[tokio::test]
+async fn named() {
+  loader_test("fixtures/named/input.js", "fixtures/named/output.js").await;
+}
+#[tokio::test]
+async fn multiple() {
+  loader_test("fixtures/multiple/input.js", "fixtures/multiple/output.js").await;
+}
+#[tokio::test]
+async fn default() {
+  loader_test("fixtures/default/input.js", "fixtures/default/output.js").await;
+}
+#[tokio::test]
+async fn require_basic() {
+  loader_test("fixtures/require_basic/input.js", "fixtures/require_basic/output.js").await;
+}
+#[tokio::test]
+async fn require_named() {
+  loader_test("fixtures/require_named/input.js", "fixtures/require_named/output.js").await;
+}
+#[tokio::test]
+async fn require_default() {
+  loader_test("fixtures/require_default/input.js", "fixtures/require_default/output.js").await;
+}
+#[tokio::test]
+async fn require_rest() {
+  loader_test("fixtures/require_rest/input.js", "fixtures/require_rest/output.js").await;
+}
+#[tokio::test]
+async fn require_scoped() {
+  loader_test("fixtures/require_scoped/input.js", "fixtures/require_scoped/output.js").await;
 }
\ No newline at end of file
diff --git a/crates/loader_compilation/tests/fixtures/basic/.ice/route-manifest.json b/crates/loader_compilation/tests/fixtures/basic/.ice/route-manifest.json
deleted file mode 100644
index cfcf422..0000000
--- a/crates/loader_compilation/tests/fixtures/basic/.ice/route-manifest.json
+++ /dev/null
@@ -1,22 +0,0 @@
-[
-  {
-    "path": "error",
-    "id": "error",
-    "file": "error.tsx",
-    "componentName": "error",
-    "layout": false,
-    "exports": [
-      "default"
-    ]
-  },
-  {
-    "index": true,
-    "id": "/",
-    "file": "index.tsx",
-    "componentName": "index",
-    "layout": false,
-    "exports": [
-      "default"
-    ]
-  }
-]
\ No newline at end of file
diff --git a/crates/loader_compilation/tests/fixtures/basic/input.js b/crates/loader_compilation/tests/fixtures/basic/input.js
index f11cfe5..a5f980a 100644
--- a/crates/loader_compilation/tests/fixtures/basic/input.js
+++ b/crates/loader_compilation/tests/fixtures/basic/input.js
@@ -1,8 +1,5 @@
-const a = 1;
-const b = 2;
+import { isWeb } from "@uni/env";
 
-export const dataLoader = {
-  b,
-};
-
-export default a;
\ No newline at end of file
+if (isWeb) {
+  console.log("test");
+}
\ No newline at end of file
diff --git a/crates/loader_compilation/tests/fixtures/basic/output.js b/crates/loader_compilation/tests/fixtures/basic/output.js
index 5442d74..71a9e11 100644
--- a/crates/loader_compilation/tests/fixtures/basic/output.js
+++ b/crates/loader_compilation/tests/fixtures/basic/output.js
@@ -1,3 +1,4 @@
-const a = 1;
-
-window.__custom_code__ = true;
+var isWeb = import.meta.renderer === "client" && import.meta.target === "web";
+if (isWeb) {
+    console.log("test");
+}
diff --git a/crates/loader_compilation/tests/fixtures/default/input.js b/crates/loader_compilation/tests/fixtures/default/input.js
new file mode 100644
index 0000000..353670a
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/default/input.js
@@ -0,0 +1,5 @@
+import env from "@uni/env";
+
+if (env.isWeb) {
+  console.log("test");
+}
\ No newline at end of file
diff --git a/crates/loader_compilation/tests/fixtures/default/output.js b/crates/loader_compilation/tests/fixtures/default/output.js
new file mode 100644
index 0000000..849bffa
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/default/output.js
@@ -0,0 +1,19 @@
+const env = {
+    isWeb: import.meta.renderer === "client" && import.meta.target === "web",
+    isClient: import.meta.renderer === "client",
+    isNode: import.meta.renderer === "server",
+    isWeex: import.meta.renderer === "client" && import.meta.target === "weex",
+    isKraken: import.meta.renderer === "client" && import.meta.target === "kraken",
+    isMiniApp: false,
+    isByteDanceMicroApp: false,
+    isBaiduSmartProgram: false,
+    isKuaiShouMiniProgram: false,
+    isWeChatMiniProgram: false,
+    isQuickApp: false,
+    isPHA: import.meta.renderer === "client" && import.meta.target === "web" && typeof pha === "object",
+    isWindVane: import.meta.renderer === "client" && /.+AliApp\((\w+)\/((?:\d+\.)+\d+)\).* .*(WindVane)(?:\/((?:\d+\.)+\d+))?.*/.test(typeof navigator ? navigator.userAgent || navigator.swuserAgent : "") && typeof WindVane !== "undefined" && typeof WindVane.call !== "undefined",
+    isFRM: false
+};
+if (env.isWeb) {
+    console.log("test");
+}
diff --git a/crates/loader_compilation/tests/fixtures/multiple/input.js b/crates/loader_compilation/tests/fixtures/multiple/input.js
new file mode 100644
index 0000000..e740f1e
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/multiple/input.js
@@ -0,0 +1,5 @@
+import { isWeb, isPHA } from "@uni/env";
+
+if (isWeb && isPHA) {
+  console.log("test");
+}
\ No newline at end of file
diff --git a/crates/loader_compilation/tests/fixtures/multiple/output.js b/crates/loader_compilation/tests/fixtures/multiple/output.js
new file mode 100644
index 0000000..bd9c688
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/multiple/output.js
@@ -0,0 +1,5 @@
+var isWeb = import.meta.renderer === "client" && import.meta.target === "web";
+var isPHA = import.meta.renderer === "client" && import.meta.target === "web" && typeof pha === "object";
+if (isWeb && isPHA) {
+    console.log("test");
+}
diff --git a/crates/loader_compilation/tests/fixtures/named/input.js b/crates/loader_compilation/tests/fixtures/named/input.js
new file mode 100644
index 0000000..2b6908e
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/named/input.js
@@ -0,0 +1,5 @@
+import { isWeb as web } from "@uni/env";
+
+if (web) {
+  console.log("test");
+}
\ No newline at end of file
diff --git a/crates/loader_compilation/tests/fixtures/named/output.js b/crates/loader_compilation/tests/fixtures/named/output.js
new file mode 100644
index 0000000..1ac586d
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/named/output.js
@@ -0,0 +1,4 @@
+var web = import.meta.renderer === "client" && import.meta.target === "web";
+if (web) {
+    console.log("test");
+}
diff --git a/crates/loader_compilation/tests/fixtures/require_basic/input.js b/crates/loader_compilation/tests/fixtures/require_basic/input.js
new file mode 100644
index 0000000..cdd4e60
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/require_basic/input.js
@@ -0,0 +1,5 @@
+const { isWeb } = require("@uni/env");
+
+if (isWeb) {
+  console.log("test");
+}
\ No newline at end of file
diff --git a/crates/loader_compilation/tests/fixtures/require_basic/output.js b/crates/loader_compilation/tests/fixtures/require_basic/output.js
new file mode 100644
index 0000000..71a9e11
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/require_basic/output.js
@@ -0,0 +1,4 @@
+var isWeb = import.meta.renderer === "client" && import.meta.target === "web";
+if (isWeb) {
+    console.log("test");
+}
diff --git a/crates/loader_compilation/tests/fixtures/require_default/input.js b/crates/loader_compilation/tests/fixtures/require_default/input.js
new file mode 100644
index 0000000..02f68f4
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/require_default/input.js
@@ -0,0 +1,5 @@
+const env = require("@uni/env");
+
+if (env.isWeb) {
+  console.log("test");
+}
\ No newline at end of file
diff --git a/crates/loader_compilation/tests/fixtures/require_default/output.js b/crates/loader_compilation/tests/fixtures/require_default/output.js
new file mode 100644
index 0000000..849bffa
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/require_default/output.js
@@ -0,0 +1,19 @@
+const env = {
+    isWeb: import.meta.renderer === "client" && import.meta.target === "web",
+    isClient: import.meta.renderer === "client",
+    isNode: import.meta.renderer === "server",
+    isWeex: import.meta.renderer === "client" && import.meta.target === "weex",
+    isKraken: import.meta.renderer === "client" && import.meta.target === "kraken",
+    isMiniApp: false,
+    isByteDanceMicroApp: false,
+    isBaiduSmartProgram: false,
+    isKuaiShouMiniProgram: false,
+    isWeChatMiniProgram: false,
+    isQuickApp: false,
+    isPHA: import.meta.renderer === "client" && import.meta.target === "web" && typeof pha === "object",
+    isWindVane: import.meta.renderer === "client" && /.+AliApp\((\w+)\/((?:\d+\.)+\d+)\).* .*(WindVane)(?:\/((?:\d+\.)+\d+))?.*/.test(typeof navigator ? navigator.userAgent || navigator.swuserAgent : "") && typeof WindVane !== "undefined" && typeof WindVane.call !== "undefined",
+    isFRM: false
+};
+if (env.isWeb) {
+    console.log("test");
+}
diff --git a/crates/loader_compilation/tests/fixtures/require_named/input.js b/crates/loader_compilation/tests/fixtures/require_named/input.js
new file mode 100644
index 0000000..9368250
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/require_named/input.js
@@ -0,0 +1,5 @@
+const { isWeb: web } = require("@uni/env");
+
+if (web) {
+  console.log("test");
+}
\ No newline at end of file
diff --git a/crates/loader_compilation/tests/fixtures/require_named/output.js b/crates/loader_compilation/tests/fixtures/require_named/output.js
new file mode 100644
index 0000000..1ac586d
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/require_named/output.js
@@ -0,0 +1,4 @@
+var web = import.meta.renderer === "client" && import.meta.target === "web";
+if (web) {
+    console.log("test");
+}
diff --git a/crates/loader_compilation/tests/fixtures/require_rest/input.js b/crates/loader_compilation/tests/fixtures/require_rest/input.js
new file mode 100644
index 0000000..3c9be22
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/require_rest/input.js
@@ -0,0 +1,5 @@
+const { isNode, ...rest } = require("@uni/env");
+
+if (rest.isWeb || isNode) {
+  console.log("test");
+}
\ No newline at end of file
diff --git a/crates/loader_compilation/tests/fixtures/require_rest/output.js b/crates/loader_compilation/tests/fixtures/require_rest/output.js
new file mode 100644
index 0000000..262d928
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/require_rest/output.js
@@ -0,0 +1,20 @@
+var isNode = import.meta.renderer === "server";
+const rest = {
+    isWeb: import.meta.renderer === "client" && import.meta.target === "web",
+    isClient: import.meta.renderer === "client",
+    isNode: import.meta.renderer === "server",
+    isWeex: import.meta.renderer === "client" && import.meta.target === "weex",
+    isKraken: import.meta.renderer === "client" && import.meta.target === "kraken",
+    isMiniApp: false,
+    isByteDanceMicroApp: false,
+    isBaiduSmartProgram: false,
+    isKuaiShouMiniProgram: false,
+    isWeChatMiniProgram: false,
+    isQuickApp: false,
+    isPHA: import.meta.renderer === "client" && import.meta.target === "web" && typeof pha === "object",
+    isWindVane: import.meta.renderer === "client" && /.+AliApp\((\w+)\/((?:\d+\.)+\d+)\).* .*(WindVane)(?:\/((?:\d+\.)+\d+))?.*/.test(typeof navigator ? navigator.userAgent || navigator.swuserAgent : "") && typeof WindVane !== "undefined" && typeof WindVane.call !== "undefined",
+    isFRM: false
+};
+if (rest.isWeb || isNode) {
+    console.log("test");
+}
diff --git a/crates/loader_compilation/tests/fixtures/require_scoped/input.js b/crates/loader_compilation/tests/fixtures/require_scoped/input.js
new file mode 100644
index 0000000..369dd9c
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/require_scoped/input.js
@@ -0,0 +1,8 @@
+const { isNode } = require("@uni/env");
+function check() {
+  const { isWeb } = require("@uni/env");
+  if (isWeb) {
+    const { isServer, isClient } = require("@uni/env");
+    console.log("test");
+  }
+}
\ No newline at end of file
diff --git a/crates/loader_compilation/tests/fixtures/require_scoped/output.js b/crates/loader_compilation/tests/fixtures/require_scoped/output.js
new file mode 100644
index 0000000..84f6294
--- /dev/null
+++ b/crates/loader_compilation/tests/fixtures/require_scoped/output.js
@@ -0,0 +1,9 @@
+var isNode = import.meta.renderer === "server";
+function check() {
+    var isWeb = import.meta.renderer === "client" && import.meta.target === "web";
+    if (isWeb) {
+        var isServer = import.meta.renderer === "server";
+        var isClient = import.meta.renderer === "client";
+        console.log("test");
+    }
+}

From b6147aff73be0ad4fd4dc21eca712a84fbb18b32 Mon Sep 17 00:00:00 2001
From: ClarkXia <xiawenwu41@gmail.com>
Date: Tue, 5 Dec 2023 13:47:13 +0800
Subject: [PATCH 2/2] 0.0.1

---
 crates/loader_compilation/src/lib.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/crates/loader_compilation/src/lib.rs b/crates/loader_compilation/src/lib.rs
index 197808d..5bfb2dc 100644
--- a/crates/loader_compilation/src/lib.rs
+++ b/crates/loader_compilation/src/lib.rs
@@ -168,7 +168,6 @@ impl Loader<LoaderRunnerContext> for CompilationLoader {
       if routes_content.is_ok() {
         *routes_config = Some(routes_content?);
       }
-      
       if file_accessed {
         // If file accessed, then we need to clear the map for the current compilation.
         file_access.clear();