diff --git a/crates/mako/src/transformers/transform_dep_replacer.rs b/crates/mako/src/transformers/transform_dep_replacer.rs index b5706d945..da95f70f1 100644 --- a/crates/mako/src/transformers/transform_dep_replacer.rs +++ b/crates/mako/src/transformers/transform_dep_replacer.rs @@ -14,7 +14,7 @@ use crate::module::{Dependency, ModuleId}; use crate::plugins::css::is_url_ignored; use crate::plugins::javascript::{is_commonjs_require, is_dynamic_import}; use crate::task::parse_path; -use crate::transformers::transform_virtual_css_modules::is_css_path; +use crate::transformers::transform_virtual_css_modules::{is_css_modules_path, is_css_path}; pub struct DepReplacer<'a> { pub module_id: &'a ModuleId, @@ -111,45 +111,35 @@ impl VisitMut for DepReplacer<'_> { } } - let is_dep_replaceable = if let Some((_, raw_id)) = - self.to_replace.resolved.get(&source_string) - { + if let Some((_, raw_id)) = self.to_replace.resolved.get(&source_string) { let file_request = parse_path(raw_id).unwrap(); - is_css_path(&file_request.path) - && (file_request.query.is_empty() || file_request.has_query("modules")) - } else { - false - }; - - if is_dep_replaceable { - // remove `require('./xxx.css');` - if is_commonjs_require_flag { - *expr = Expr::Lit(quote_str!("").into()); - return; - } else { - // `import('./xxx.css')` 中的 css 模块会被拆分到单独的 chunk, 这里需要改为加载 css chunk - let module_graph = self.context.module_graph.read().unwrap(); - let dep_module_id = module_graph - .get_dependency_module_by_source(self.module_id, &source_string); + let will_replace = is_css_path(&file_request.path) + && (file_request.query.is_empty() || file_request.has_query("modules")); + + if will_replace { + // remove `require('./xxx.css');` + if is_commonjs_require_flag { + *expr = Expr::Lit(quote_str!("").into()); + return; + } - if let Some(dep_module_id) = dep_module_id { + // replace `import("./xxx.css")` + let is_css_module = + file_request.has_query("asmodule") || is_css_modules_path(raw_id); + if !is_css_module { let chunk_graph = self.context.chunk_graph.read().unwrap(); let chunk = - chunk_graph.get_chunk_for_module(&dep_module_id.clone()); + chunk_graph.get_chunk_for_module(&raw_id.clone().into()); if let Some(chunk) = chunk { - let chunk_id = chunk.id.id.clone(); - // `import('./xxx.css')` => `__mako_require__.ensure('./xxx.css')` - *expr = member_expr!(DUMMY_SP, __mako_require__.ensure) - .as_call(DUMMY_SP, vec![quote_str!(chunk_id).as_arg()]); + *expr = Self::dynamic_css_import_replacement(&chunk.id.id); return; } else { *expr = Expr::Lit(quote_str!("").into()); return; } } else { - *expr = Expr::Lit(quote_str!("").into()); - return; + // dynamic css module import will be handled by DynamicImport transformer } } } @@ -179,6 +169,17 @@ impl VisitMut for DepReplacer<'_> { } impl DepReplacer<'_> { + /* + import('./xxx.css') + || + \/ + __mako_require__.ensure('chunk_id') + */ + fn dynamic_css_import_replacement(chunk_id: &str) -> Expr { + member_expr!(DUMMY_SP, __mako_require__.ensure) + .as_call(DUMMY_SP, vec![quote_str!(chunk_id.to_owned()).as_arg()]) + } + fn replace_source(&mut self, source: &mut Str) { let to_replace = if let Some(replacement) = self.to_replace.resolved.get(&source.value.to_string()) { diff --git a/crates/mako/src/transformers/transform_virtual_css_modules.rs b/crates/mako/src/transformers/transform_virtual_css_modules.rs index b53f202a3..dfc48a2c2 100644 --- a/crates/mako/src/transformers/transform_virtual_css_modules.rs +++ b/crates/mako/src/transformers/transform_virtual_css_modules.rs @@ -5,6 +5,8 @@ use mako_core::regex::Regex; use mako_core::swc_common::Mark; use mako_core::swc_ecma_ast::{ImportDecl, Str}; use mako_core::swc_ecma_visit::{VisitMut, VisitMutWith}; +use swc_core::ecma::ast::{CallExpr, ExprOrSpread, Lit}; +use swc_core::ecma::utils::{quote_str, ExprFactory}; use crate::compiler::Context; @@ -18,7 +20,7 @@ lazy_static! { static ref CSS_PATH_REGEX: Regex = Regex::new(r#"\.(css|less)$"#).unwrap(); } -fn is_css_modules_path(path: &str) -> bool { +pub fn is_css_modules_path(path: &str) -> bool { CSS_MODULES_PATH_REGEX.is_match(path) } @@ -27,6 +29,21 @@ pub fn is_css_path(path: &str) -> bool { } impl VisitMut for VirtualCSSModules<'_> { + fn visit_mut_call_expr(&mut self, n: &mut CallExpr) { + if n.args.len() == 1 { + if n.callee.is_import() { + self.replace_first_arg(&mut n.args); + } else if let Some(call_expr) = n.callee.as_expr() + && let Some(callee_ident) = call_expr.as_ident() + && callee_ident.span.ctxt.outer() == self.unresolved_mark + && callee_ident.sym.as_str() == "require" + { + self.replace_first_arg(&mut n.args); + } + } + + n.visit_mut_children_with(self); + } fn visit_mut_import_decl(&mut self, import_decl: &mut ImportDecl) { if is_css_modules_path(&import_decl.src.value) || (self.context.config.auto_css_modules @@ -40,6 +57,21 @@ impl VisitMut for VirtualCSSModules<'_> { } impl VirtualCSSModules<'_> { + fn replace_first_arg(&mut self, args: &mut [ExprOrSpread]) { + if let Some(first_arg) = args.get_mut(0) { + if let Some(lit) = first_arg.expr.as_lit() + && let Lit::Str(import_str) = lit + { + let origin_import_str = import_str.value.as_str(); + + if is_css_modules_path(origin_import_str) { + let replaced = format!("{}?asmodule", origin_import_str); + *first_arg = quote_str!(replaced).as_arg(); + } + } + } + } + fn replace_source(&mut self, source: &mut Str) { let to_replace = format!("{}?asmodule", &source.value.to_string()); let span = source.span; @@ -47,3 +79,79 @@ impl VirtualCSSModules<'_> { source.span = span; } } + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use swc_core::common::GLOBALS; + use swc_core::ecma::transforms::base::resolver; + use swc_core::ecma::visit::VisitMutWith; + + use crate::ast::{build_js_ast, js_ast_to_code}; + use crate::compiler::Context; + use crate::transformers::transform_virtual_css_modules::VirtualCSSModules; + + #[test] + fn test_dynamic_import_css_module() { + let code = act_replace( + r#" + import('./styles.module.css').then(); + "#, + ); + + assert_eq!( + code.trim(), + r#"import("./styles.module.css?asmodule").then();"# + ) + } + + #[test] + fn test_dynamic_import_non_css_module() { + let code = act_replace( + r#" + import("./styles.css").then(); + "#, + ); + + assert_eq!(code.trim(), r#"import("./styles.css").then();"#) + } + + #[test] + fn test_require_css_module() { + let code = act_replace(r#"require("./style.module.css")"#); + + assert_eq!(code.trim(), r#"require("./style.module.css?asmodule");"#) + } + + #[test] + fn test_require_no_css_module() { + let code = act_replace(r#"require("./style.css")"#); + + assert_eq!(code.trim(), r#"require("./style.css");"#) + } + + fn act_replace(code: &str) -> String { + let mut context: Context = Default::default(); + context.config.devtool = None; + let context: Arc = Arc::new(context); + + let mut ast = build_js_ast("sut.js", code, &context).unwrap(); + + GLOBALS.set(&context.meta.script.globals, || { + ast.ast.visit_mut_with(&mut resolver( + ast.unresolved_mark, + ast.top_level_mark, + false, + )); + + ast.ast.visit_mut_with(&mut VirtualCSSModules { + context: &context, + unresolved_mark: ast.unresolved_mark, + }); + }); + + let (code, _) = js_ast_to_code(&ast.ast, &context, "sut.js").unwrap(); + code + } +} diff --git a/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/config.ts b/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/config.ts new file mode 100644 index 000000000..01cd91a3e --- /dev/null +++ b/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/config.ts @@ -0,0 +1,3 @@ +export default { + autoCSSModules: false, +}; diff --git a/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/expect.js b/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/expect.js new file mode 100644 index 000000000..5548ad2c8 --- /dev/null +++ b/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/expect.js @@ -0,0 +1,17 @@ +const assert = require("assert"); + +const { parseBuildResult, trim } = require("../../../scripts/test-utils"); +const { files } = parseBuildResult(__dirname); + +let cssFile = Object.keys(files).filter(k=> k.endsWith(".css"))[0]; +assert(files[cssFile].match(/\.container-\S*/), 'should contains postfixed classname') + +assert( + trim(files["index.js"]).includes(`__mako_require__.ensure("src/index.module.css?asmodule")`), + "should find ensure chunk" +); + +assert( + files["index.js"].includes(`__mako_require__.bind(__mako_require__, "src/index.module.css?asmodule")`), + "should find reuquire module" +); diff --git a/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/mako.config.json b/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/mako.config.json new file mode 100644 index 000000000..2c2f75b61 --- /dev/null +++ b/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/mako.config.json @@ -0,0 +1 @@ +{ "autoCSSModules": false, "minify": false } diff --git a/e2e/fixtures/css.css-modules.dynamic-import/src/index.css b/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/src/index.module.css similarity index 100% rename from e2e/fixtures/css.css-modules.dynamic-import/src/index.css rename to e2e/fixtures/css.css-modules.dynamic-import-by-file-name/src/index.module.css diff --git a/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/src/index.tsx b/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/src/index.tsx new file mode 100644 index 000000000..ae5fd5e8c --- /dev/null +++ b/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/src/index.tsx @@ -0,0 +1 @@ +import('./index.module.css'); diff --git a/e2e/fixtures/css.css-modules.dynamic-import/typings.d.ts b/e2e/fixtures/css.css-modules.dynamic-import-by-file-name/typings.d.ts similarity index 100% rename from e2e/fixtures/css.css-modules.dynamic-import/typings.d.ts rename to e2e/fixtures/css.css-modules.dynamic-import-by-file-name/typings.d.ts diff --git a/e2e/fixtures/css.css-modules.dynamic-import/config.ts b/e2e/fixtures/css.css-modules.dynamic-import/config.ts deleted file mode 100644 index 4a636e84b..000000000 --- a/e2e/fixtures/css.css-modules.dynamic-import/config.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default { - autoCSSModules: {}, -}; diff --git a/e2e/fixtures/css.css-modules.dynamic-import/expect.js b/e2e/fixtures/css.css-modules.dynamic-import/expect.js deleted file mode 100644 index 5f2b7cf4c..000000000 --- a/e2e/fixtures/css.css-modules.dynamic-import/expect.js +++ /dev/null @@ -1,9 +0,0 @@ -const assert = require("assert"); - -const { parseBuildResult, trim } = require("../../../scripts/test-utils"); -const { files } = parseBuildResult(__dirname); - -assert( - trim(files["src_index_css-async.css"]).includes(`.container{`), - "import('./index.css') should not be css modules" -); diff --git a/e2e/fixtures/css.css-modules.dynamic-import/mako.config.json b/e2e/fixtures/css.css-modules.dynamic-import/mako.config.json deleted file mode 100644 index b1bec669b..000000000 --- a/e2e/fixtures/css.css-modules.dynamic-import/mako.config.json +++ /dev/null @@ -1 +0,0 @@ -{ "autoCSSModules": true, "minify": false } diff --git a/e2e/fixtures/css.css-modules.dynamic-import/src/index.tsx b/e2e/fixtures/css.css-modules.dynamic-import/src/index.tsx deleted file mode 100644 index c9944da27..000000000 --- a/e2e/fixtures/css.css-modules.dynamic-import/src/index.tsx +++ /dev/null @@ -1 +0,0 @@ -import('./index.css'); diff --git a/e2e/fixtures/css.css-modules.required-by-file-name/config.ts b/e2e/fixtures/css.css-modules.required-by-file-name/config.ts new file mode 100644 index 000000000..01cd91a3e --- /dev/null +++ b/e2e/fixtures/css.css-modules.required-by-file-name/config.ts @@ -0,0 +1,3 @@ +export default { + autoCSSModules: false, +}; diff --git a/e2e/fixtures/css.css-modules.required-by-file-name/expect.js b/e2e/fixtures/css.css-modules.required-by-file-name/expect.js new file mode 100644 index 000000000..ad0238da9 --- /dev/null +++ b/e2e/fixtures/css.css-modules.required-by-file-name/expect.js @@ -0,0 +1,17 @@ +const assert = require("assert"); + +const { parseBuildResult, trim } = require("../../../scripts/test-utils"); +const { files } = parseBuildResult(__dirname); + +let cssFile = Object.keys(files).filter(k=> k.endsWith(".css"))[0]; +assert(files[cssFile].match(/\.container-\S*/), 'should contains postfixed classname') + +assert( + files["index.js"].includes(`"src/index.module.css?asmodule":`), + "should tree css file as css module" +); + +assert( + files["index.js"].includes(`__mako_require__("src/index.module.css?asmodule")`), + "should reuquire css module with query" +); diff --git a/e2e/fixtures/css.css-modules.required-by-file-name/mako.config.json b/e2e/fixtures/css.css-modules.required-by-file-name/mako.config.json new file mode 100644 index 000000000..2c2f75b61 --- /dev/null +++ b/e2e/fixtures/css.css-modules.required-by-file-name/mako.config.json @@ -0,0 +1 @@ +{ "autoCSSModules": false, "minify": false } diff --git a/e2e/fixtures/css.css-modules.required-by-file-name/src/index.module.css b/e2e/fixtures/css.css-modules.required-by-file-name/src/index.module.css new file mode 100644 index 000000000..06fdb679d --- /dev/null +++ b/e2e/fixtures/css.css-modules.required-by-file-name/src/index.module.css @@ -0,0 +1,3 @@ +.container { + padding-top: 80px; +} diff --git a/e2e/fixtures/css.css-modules.required-by-file-name/src/index.tsx b/e2e/fixtures/css.css-modules.required-by-file-name/src/index.tsx new file mode 100644 index 000000000..3e55eb246 --- /dev/null +++ b/e2e/fixtures/css.css-modules.required-by-file-name/src/index.tsx @@ -0,0 +1 @@ +console.log(require('./index.module.css').default); diff --git a/e2e/fixtures/css.css-modules.required-by-file-name/typings.d.ts b/e2e/fixtures/css.css-modules.required-by-file-name/typings.d.ts new file mode 100644 index 000000000..cbe652dbe --- /dev/null +++ b/e2e/fixtures/css.css-modules.required-by-file-name/typings.d.ts @@ -0,0 +1 @@ +declare module "*.css"; diff --git a/e2e/fixtures/css.css-modules/expect.js b/e2e/fixtures/css.css-modules/expect.js index 9e9b146a2..06ada4a7f 100644 --- a/e2e/fixtures/css.css-modules/expect.js +++ b/e2e/fixtures/css.css-modules/expect.js @@ -28,3 +28,8 @@ assert( trim(files["index.css"]).includes(`.e{`), "const e = require('./e.css') should not be css modules" ); + +assert( + files["index.js"].includes(`__mako_require__.ensure("src/f.css").then((f)=>f);`), + "import('./f.css') should ensure chunk first" +); diff --git a/e2e/fixtures/css.css-modules/src/index.tsx b/e2e/fixtures/css.css-modules/src/index.tsx index 9bbe1f77c..1d66c70d9 100644 --- a/e2e/fixtures/css.css-modules/src/index.tsx +++ b/e2e/fixtures/css.css-modules/src/index.tsx @@ -1,13 +1,16 @@ +// css module import styles from "./index.css"; console.log(styles); + +// non css module import('./a.css'); require('./b.css'); import "./c.css"; - -// css modules -import d from './d.css'; -d; const e = require('./e.css'); -e; -// import('./f.css').then(f => f); +import('./f.css').then(f => f); + +console.log (e); + + +