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

fix(css-module): dynamic import css module #891

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
57 changes: 29 additions & 28 deletions crates/mako/src/transformers/transform_dep_replacer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
}
}
Expand Down Expand Up @@ -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()) {
Expand Down
110 changes: 109 additions & 1 deletion crates/mako/src/transformers/transform_virtual_css_modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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)
}

Expand All @@ -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
Expand All @@ -40,10 +57,101 @@ 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;
*source = Str::from(to_replace);
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<Context> = 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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
autoCSSModules: false,
};
17 changes: 17 additions & 0 deletions e2e/fixtures/css.css-modules.dynamic-import-by-file-name/expect.js
Original file line number Diff line number Diff line change
@@ -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"
);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "autoCSSModules": false, "minify": false }
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import('./index.module.css');
3 changes: 0 additions & 3 deletions e2e/fixtures/css.css-modules.dynamic-import/config.ts

This file was deleted.

9 changes: 0 additions & 9 deletions e2e/fixtures/css.css-modules.dynamic-import/expect.js

This file was deleted.

This file was deleted.

1 change: 0 additions & 1 deletion e2e/fixtures/css.css-modules.dynamic-import/src/index.tsx

This file was deleted.

3 changes: 3 additions & 0 deletions e2e/fixtures/css.css-modules.required-by-file-name/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
autoCSSModules: false,
};
17 changes: 17 additions & 0 deletions e2e/fixtures/css.css-modules.required-by-file-name/expect.js
Original file line number Diff line number Diff line change
@@ -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"
);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "autoCSSModules": false, "minify": false }
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.container {
padding-top: 80px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log(require('./index.module.css').default);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module "*.css";
5 changes: 5 additions & 0 deletions e2e/fixtures/css.css-modules/expect.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
);
15 changes: 9 additions & 6 deletions e2e/fixtures/css.css-modules/src/index.tsx
Original file line number Diff line number Diff line change
@@ -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);