diff --git a/crates/mako/src/generate/transform.rs b/crates/mako/src/generate/transform.rs index 138ed12dc..1470540fe 100644 --- a/crates/mako/src/generate/transform.rs +++ b/crates/mako/src/generate/transform.rs @@ -244,9 +244,7 @@ pub fn transform_js_generate(transform_js_param: TransformJsParam) -> Result<()> let mut meta_url_replacer = MetaUrlReplacer {}; ast.ast.visit_mut_with(&mut meta_url_replacer); - let mut dynamic_import = DynamicImport { - context: context.clone(), - }; + let mut dynamic_import = DynamicImport::new(context.clone(), dep_map); ast.ast.visit_mut_with(&mut dynamic_import); // replace require to __mako_require__ diff --git a/crates/mako/src/plugins/bundless_compiler.rs b/crates/mako/src/plugins/bundless_compiler.rs index f8722042e..413efc855 100644 --- a/crates/mako/src/plugins/bundless_compiler.rs +++ b/crates/mako/src/plugins/bundless_compiler.rs @@ -236,9 +236,7 @@ fn transform_js_generate( }; ast.ast.visit_mut_with(&mut dep_replacer); - let mut dynamic_import = DynamicImport { - context: context.clone(), - }; + let mut dynamic_import = DynamicImport::new(context.clone(), dep_map); ast.ast.visit_mut_with(&mut dynamic_import); ast.ast diff --git a/crates/mako/src/plugins/context_module.rs b/crates/mako/src/plugins/context_module.rs index e9c021a09..0a10c7750 100644 --- a/crates/mako/src/plugins/context_module.rs +++ b/crates/mako/src/plugins/context_module.rs @@ -166,7 +166,7 @@ impl VisitMut for ContextModuleVisitor { .as_callee(); // TODO: allow use await in args // eg: import(`./i18n${await xxx()}`) - expr.args = vec![quote_ident!("m") + expr.args = vec![member_expr!(DUMMY_SP, m.default) .as_call(DUMMY_SP, expr.args.clone()) .as_expr() .to_owned() diff --git a/crates/mako/src/visitors/dynamic_import.rs b/crates/mako/src/visitors/dynamic_import.rs index 1d70b0806..8db3fa1a4 100644 --- a/crates/mako/src/visitors/dynamic_import.rs +++ b/crates/mako/src/visitors/dynamic_import.rs @@ -1,20 +1,74 @@ use std::sync::Arc; use mako_core::swc_common::DUMMY_SP; -use mako_core::swc_ecma_ast::{ArrayLit, Expr, ExprOrSpread, Lit}; +use mako_core::swc_ecma_ast::{ArrayLit, Expr, ExprOrSpread, Lit, MemberExpr}; use mako_core::swc_ecma_visit::{VisitMut, VisitMutWith}; - -use crate::ast::utils::{ - id, is_dynamic_import, member_call, member_prop, promise_all, require_ensure, +use swc_core::ecma::ast::{Ident, Module, Stmt, VarDeclKind}; +use swc_core::ecma::utils::{ + member_expr, private_ident, quote_ident, quote_str, ExprFactory, IsDirective, }; + +use crate::ast::utils::{is_dynamic_import, member_call, member_prop, promise_all, require_ensure}; use crate::compiler::Context; use crate::generate::chunk::ChunkId; +use crate::visitors::dep_replacer::DependenciesToReplace; -pub struct DynamicImport { +pub struct DynamicImport<'a> { pub context: Arc, + interop: Ident, + changed: bool, + dep_to_replace: &'a DependenciesToReplace, +} + +impl<'a> DynamicImport<'a> { + pub fn new(context: Arc, dep_map: &'a DependenciesToReplace) -> Self { + let interop = private_ident!("interop"); + + Self { + context, + interop, + changed: false, + dep_to_replace: dep_map, + } + } } -impl VisitMut for DynamicImport { +impl<'a> VisitMut for DynamicImport<'a> { + fn visit_mut_module(&mut self, n: &mut Module) { + n.visit_mut_children_with(self); + + if self.changed { + let insert_at = n + .body + .iter() + .position(|module_item| { + !module_item + .as_stmt() + .map_or(false, |stmt| stmt.is_directive()) + }) + .unwrap(); + + let (id, _) = self + .dep_to_replace + .resolved + .get("@swc/helpers/_/_interop_require_wildcard") + .unwrap(); + + let require_interop = quote_ident!("__mako_require__") + .as_call(DUMMY_SP, vec![quote_str!(id.clone()).as_arg()]); + + let stmt: Stmt = Expr::Member(MemberExpr { + span: DUMMY_SP, + obj: require_interop.into(), + prop: quote_ident!("_").into(), + }) + .into_var_decl(VarDeclKind::Var, self.interop.clone().into()) + .into(); + + n.body.insert(insert_at, stmt.into()); + } + } + fn visit_mut_expr(&mut self, expr: &mut Expr) { if let Expr::Call(call_expr) = expr { if is_dynamic_import(call_expr) { @@ -59,6 +113,7 @@ impl VisitMut for DynamicImport { chunk_ids }; + self.changed = true; // build new expr // e.g. // Promise.all([ require.ensure("id") ]).then(require.bind(require, "id")) @@ -80,26 +135,24 @@ impl VisitMut for DynamicImport { elems: to_ensure_elems, })), }); - let require_call = member_call( - Expr::Ident(id("__mako_require__")), - member_prop("bind"), + + let require_call = member_expr!(DUMMY_SP, __mako_require__.dr).as_call( + DUMMY_SP, vec![ - ExprOrSpread { - spread: None, - expr: Box::new(Expr::Ident(id("__mako_require__"))), - }, + self.interop.clone().as_arg(), ExprOrSpread { spread: None, expr: Box::new(Expr::Lit(Lit::Str(resolved_source.into()))), }, ], ); + member_call( load_promise, member_prop("then"), vec![ExprOrSpread { spread: None, - expr: Box::new(require_call), + expr: require_call.into(), }], ) }; @@ -112,12 +165,15 @@ impl VisitMut for DynamicImport { #[cfg(test)] mod tests { + use std::collections::HashMap; + use mako_core::swc_common::GLOBALS; use mako_core::swc_ecma_visit::VisitMutWith; use super::DynamicImport; use crate::ast::tests::TestUtils; use crate::generate::chunk::{Chunk, ChunkType}; + use crate::visitors::dep_replacer::DependenciesToReplace; // TODO: add nested chunk test #[test] @@ -125,9 +181,10 @@ mod tests { assert_eq!( run(r#"import("foo");"#), r#" +var interop = __mako_require__("hashed_helper")._; Promise.all([ __mako_require__.ensure("foo") -]).then(__mako_require__.bind(__mako_require__, "foo")); +]).then(__mako_require__.dr(interop, "foo")); "# .trim() ); @@ -142,10 +199,17 @@ Promise.all([ cg.add_chunk(foo); } let ast = test_utils.ast.js_mut(); + + let dep_to_replace = DependenciesToReplace { + resolved: maplit::hashmap! { + "@swc/helpers/_/_interop_require_wildcard".to_string() => + ("hashed_helper".to_string(), "dummy".into()) + }, + missing: HashMap::new(), + }; + GLOBALS.set(&test_utils.context.meta.script.globals, || { - let mut visitor = DynamicImport { - context: test_utils.context.clone(), - }; + let mut visitor = DynamicImport::new(test_utils.context.clone(), &dep_to_replace); ast.ast.visit_mut_with(&mut visitor); }); let code = test_utils.js_ast_to_code(); diff --git a/crates/mako/templates/app_runtime.stpl b/crates/mako/templates/app_runtime.stpl index c18d9627a..502d7f671 100644 --- a/crates/mako/templates/app_runtime.stpl +++ b/crates/mako/templates/app_runtime.stpl @@ -72,6 +72,11 @@ function createRuntime(makoModules, entryModuleId, global) { }; <% } %> requireModule.d = Object.defineProperty.bind(Object); + requireModule.dr = function(interop, request) { + return function(){ + return interop(requireModule(request)); + } + }; <% if has_dynamic_chunks || has_hmr { %> /* mako/runtime/ensure chunk */ diff --git a/e2e/fixtures/javascript.require-dynamic/expect.js b/e2e/fixtures/javascript.require-dynamic/expect.js index 03a76b9ce..5aff33c90 100644 --- a/e2e/fixtures/javascript.require-dynamic/expect.js +++ b/e2e/fixtures/javascript.require-dynamic/expect.js @@ -37,7 +37,7 @@ assert.match( assert.match( asyncContent, - moduleReg("src/i18n\\?context&glob=\\*\\*/\\*.json&async", "'./zh-CN.json': ()=>Promise.all([\n.*__mako_require__.ensure(\"src/i18n/zh-CN.json\")\n.*]).then(__mako_require__.bind(__mako_require__, \"src/i18n/zh-CN.json\"))", true), + moduleReg("src/i18n\\?context&glob=\\*\\*/\\*.json&async", "'./zh-CN.json': ()=>Promise.all([\n.*__mako_require__.ensure(\"src/i18n/zh-CN.json\")\n.*]).then(__mako_require__.dr(interop, \"src/i18n/zh-CN.json\"))", true), "should generate context module with correct map in async chunk", ); @@ -49,7 +49,7 @@ assert.match( assert.match( asyncContent, - moduleReg("src/i18n\\?context&glob=\\*\\*/\\*.json&async", "'./en-US.json': ()=>Promise.all([\n.*__mako_require__.ensure(\"src/i18n/en-US.json\")\n.*]).then(__mako_require__.bind(__mako_require__, \"src/i18n/en-US.json\"))", true), + moduleReg("src/i18n\\?context&glob=\\*\\*/\\*.json&async", "'./en-US.json': ()=>Promise.all([\n.*__mako_require__.ensure(\"src/i18n/en-US.json\")\n.*]).then(__mako_require__.dr(interop, \"src/i18n/en-US.json\"))", true), "should generate context module with correct map in async chunk", ); diff --git a/e2e/fixtures/javascript.require-dynamic/src/index.ts b/e2e/fixtures/javascript.require-dynamic/src/index.ts index ab3d50ee6..e295a4af7 100644 --- a/e2e/fixtures/javascript.require-dynamic/src/index.ts +++ b/e2e/fixtures/javascript.require-dynamic/src/index.ts @@ -4,7 +4,9 @@ function loadLang(lang) { function loadLangExt(lang, ext) { // nested dynamic require + with then callback - return import(`./i18n/${lang}.${(require(`./ext/${ext}`)).default}`).then(m => m); + return import(`./i18n/${lang}.${(require(`./ext/${ext}`)).default}`) + + .then(m => m); } function loadFile(file) { @@ -15,7 +17,7 @@ function loadFile2(file) { return require('./fake.js/' + file); } -console.log(loadLang('zh-CN')); -console.log(loadLangExt('zh-CN', 'json')); +loadLang('zh-CN').then(console.log); +loadLangExt('zh-CN', 'json').then(console.log); console.log(loadFile('/zh-CN.json')); console.log(loadFile2('a.js')); diff --git a/e2e/fixtures/runtime.dynamic_import_interop/expect.js b/e2e/fixtures/runtime.dynamic_import_interop/expect.js new file mode 100644 index 000000000..3977891fe --- /dev/null +++ b/e2e/fixtures/runtime.dynamic_import_interop/expect.js @@ -0,0 +1,16 @@ +const assert = require("assert"); +const { + parseBuildResult, + moduleReg, + injectSimpleJest, +} = require("../../../scripts/test-utils"); +const { files } = parseBuildResult(__dirname); + +injectSimpleJest(); + +const index = files["index.js"]; + +expect(index).toContain( + 'var interop = __mako_require__("@swc/helpers/_/_interop_require_wildcard")._;', +); +expect(index).toContain('then(__mako_require__.dr(interop, "src/cjs.js"))'); diff --git a/e2e/fixtures/runtime.dynamic_import_interop/index.js b/e2e/fixtures/runtime.dynamic_import_interop/index.js new file mode 100644 index 000000000..fd39b6447 --- /dev/null +++ b/e2e/fixtures/runtime.dynamic_import_interop/index.js @@ -0,0 +1,5 @@ +it("should interop cjs module with default", async () => { + let cjs = await import("./src/cjs"); + + expect(cjs).toEqual({ default: { foo: 42 }, foo: 42 }); +}); diff --git a/e2e/fixtures/runtime.dynamic_import_interop/mako.config.json b/e2e/fixtures/runtime.dynamic_import_interop/mako.config.json new file mode 100644 index 000000000..7856f9a68 --- /dev/null +++ b/e2e/fixtures/runtime.dynamic_import_interop/mako.config.json @@ -0,0 +1,10 @@ +{ + "optimization": { + "skipModules": false, + "concatenateModules": false + }, + "entry": { + "index": "./index.js" + }, + "moduleIdStrategy": "named" +} diff --git a/e2e/fixtures/runtime.dynamic_import_interop/src/cjs.js b/e2e/fixtures/runtime.dynamic_import_interop/src/cjs.js new file mode 100644 index 000000000..0417930f1 --- /dev/null +++ b/e2e/fixtures/runtime.dynamic_import_interop/src/cjs.js @@ -0,0 +1,4 @@ +module.exports = { + foo: 42, + default: "ddd", +};