diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05bec3386..cfc073586 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ ubuntu-latest ] + os: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.os }} if: ${{ !startsWith(github.event.head_commit.message, 'release:') && !startsWith(github.event.head_commit.message, 'ci:') && !startsWith(github.event.head_commit.message, 'docs:') }} steps: @@ -90,7 +90,7 @@ jobs: fail-fast: false matrix: script: [ "test:e2e", "test:hmr", "test:umi" ] - os: [ ubuntu-latest ] + os: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/crates/binding/src/lib.rs b/crates/binding/src/lib.rs index 8be4ab76b..309064015 100644 --- a/crates/binding/src/lib.rs +++ b/crates/binding/src/lib.rs @@ -173,6 +173,7 @@ pub struct BuildParams { ignoredPaths?: string[]; _nodeModulesRegexes?: string[]; }; + caseSensitiveCheck?: boolean; }"#)] pub config: serde_json::Value, pub plugins: Vec, diff --git a/crates/mako/src/compiler.rs b/crates/mako/src/compiler.rs index b4be7457b..6c1afc48c 100644 --- a/crates/mako/src/compiler.rs +++ b/crates/mako/src/compiler.rs @@ -273,6 +273,10 @@ impl Compiler { }, ))); } + #[cfg(target_os = "macos")] + if config.case_sensitive_check { + plugins.push(Arc::new(plugins::case_sensitive::CaseSensitivePlugin::new())); + } if let Some(duplicate_package_checker) = &config.check_duplicate_package { plugins.push(Arc::new( diff --git a/crates/mako/src/config.rs b/crates/mako/src/config.rs index d554110a3..ac9181a18 100644 --- a/crates/mako/src/config.rs +++ b/crates/mako/src/config.rs @@ -220,6 +220,9 @@ pub struct Config { default )] pub check_duplicate_package: Option, + // 是否开启 case sensitive 检查,只有mac平台才需要开启 + #[serde(rename = "caseSensitiveCheck")] + pub case_sensitive_check: bool, } const CONFIG_FILE: &str = "mako.config.json"; diff --git a/crates/mako/src/config/mako.config.default.json b/crates/mako/src/config/mako.config.default.json index 4f629661c..55975ffee 100644 --- a/crates/mako/src/config/mako.config.default.json +++ b/crates/mako/src/config/mako.config.default.json @@ -78,5 +78,6 @@ "useDefineForClassFields": true, "emitDecoratorMetadata": false, "watch": { "ignorePaths": [], "_nodeModulesRegexes": [] }, - "devServer": { "host": "127.0.0.1", "port": 3000 } + "devServer": { "host": "127.0.0.1", "port": 3000 }, + "caseSensitiveCheck": false } diff --git a/crates/mako/src/plugins.rs b/crates/mako/src/plugins.rs index 53dc5f267..36de47d69 100644 --- a/crates/mako/src/plugins.rs +++ b/crates/mako/src/plugins.rs @@ -1,5 +1,6 @@ pub mod async_runtime; pub mod bundless_compiler; +pub mod case_sensitive; pub mod central_ensure; pub mod context_module; pub mod copy; diff --git a/crates/mako/src/plugins/case_sensitive.rs b/crates/mako/src/plugins/case_sensitive.rs new file mode 100644 index 000000000..cf8e649d2 --- /dev/null +++ b/crates/mako/src/plugins/case_sensitive.rs @@ -0,0 +1,95 @@ +use std::collections::HashMap; +use std::fs; +use std::path::Path; +use std::sync::{Arc, Mutex}; + +use anyhow::{anyhow, Result}; + +use crate::ast::file::Content; +use crate::compiler::Context; +use crate::plugin::{Plugin, PluginLoadParam}; + +pub struct CaseSensitivePlugin { + cache_map: Arc>>>, +} + +impl CaseSensitivePlugin { + pub fn new() -> Self { + CaseSensitivePlugin { + cache_map: Default::default(), + } + } + + pub fn is_checkable(&self, load_param: &PluginLoadParam, root: &String) -> bool { + let file_path = &load_param.file.path; + if !file_path.starts_with(root) { + return false; + } + for component in file_path.iter() { + if component.eq_ignore_ascii_case("node_modules") { + return false; + } + } + true + } + + pub fn check_case_sensitive(&self, file: &Path, root: &str) -> String { + // 可变变量,在循环内会被修改 + let mut file_path = file.to_path_buf(); + let mut case_name = String::new(); + // 缓存map,file path做为key存在对应路径下的文件名和文件夹名 + let mut cache_map = self.cache_map.lock().unwrap_or_else(|e| e.into_inner()); + while file_path.to_string_lossy().len() >= root.len() { + if let Some(current) = file_path.file_name() { + let current_str = current.to_string_lossy().to_string(); + file_path.pop(); // parent directory + let mut entries: Vec = Vec::new(); + if let Some(dir) = file_path.to_str() { + if let Some(i) = cache_map.get(dir as &str) { + entries = i.to_vec(); + } else if let Ok(files) = fs::read_dir(dir) { + files.for_each(|entry| { + entries.push(entry.unwrap().file_name().to_string_lossy().to_string()); + }); + cache_map.insert(dir.to_string(), entries.to_vec()); + } + } + if !entries.contains(¤t_str) { + if let Some(correct_name) = entries + .iter() + .find(|&x| x.to_lowercase() == current_str.to_lowercase()) + { + case_name = correct_name.to_string(); + break; + } + } + } + } + case_name + } +} + +impl Plugin for CaseSensitivePlugin { + fn name(&self) -> &str { + "case_sensitive_plugin" + } + + fn load( + &self, + load_param: &PluginLoadParam, + context: &Arc, + ) -> Result> { + let root = &context.root.to_string_lossy().to_string(); + if self.is_checkable(load_param, root) { + let dist_path = self.check_case_sensitive(load_param.file.path.as_path(), root); + if !dist_path.is_empty() { + return Err(anyhow!( + "{} does not match the corresponding path on disk [{}]", + load_param.file.path.to_string_lossy().to_string(), + dist_path + )); + } + } + Ok(None) + } +} diff --git a/docs/config.md b/docs/config.md index 06099781a..b60192e63 100644 --- a/docs/config.md +++ b/docs/config.md @@ -36,6 +36,22 @@ Whether to automatically enable CSS Modules. If not enabled, only files with `.module.css` or `.module.less` will be treated as CSS Modules; if enabled, named imports like `import styles from './a.css'` will also be treated as CSS Modules. +### caseSensitiveCheck + +- Type: `boolean` +- Default: `true` + +Whether to enable case-sensitive check. + +e.g. + +```ts +{ + caseSensitiveCheck: false, +} +``` + + ### clean - Type: `boolean` diff --git a/docs/config.zh-CN.md b/docs/config.zh-CN.md index a32f5ed1c..1e4ca1132 100644 --- a/docs/config.zh-CN.md +++ b/docs/config.zh-CN.md @@ -36,6 +36,22 @@ 如果未启用,只有 `.module.css` 或 `.module.less` 的文件会被视为 CSS Modules;如果启用,像 `import styles from './a.css'` 这样的命名导入也会被视为 CSS Modules。 +### caseSensitiveCheck + +- 类型:`boolean` +- 默认值:`true` + +是否启用大小写敏感检查。 + +e.g. + +```ts +{ + caseSensitiveCheck: false, +} +``` + + ### clean - 类型:`boolean` @@ -850,3 +866,4 @@ babel-plugin-import 的简化版本,仅支持三个配置项:libraryName,l - 默认值:`true` 是否在开发模式下将构建结果写入磁盘。 + diff --git a/e2e/fixtures/error.case-sensitive/assets/umi-logo.png b/e2e/fixtures/error.case-sensitive/assets/umi-logo.png new file mode 100644 index 000000000..f48e2ec78 Binary files /dev/null and b/e2e/fixtures/error.case-sensitive/assets/umi-logo.png differ diff --git a/e2e/fixtures/error.case-sensitive/expect.js b/e2e/fixtures/error.case-sensitive/expect.js new file mode 100644 index 000000000..c4a3e0464 --- /dev/null +++ b/e2e/fixtures/error.case-sensitive/expect.js @@ -0,0 +1,20 @@ +const assert = require("assert"); +const os = require("os"); + +module.exports = (err) => { + if (os.platform() === "darwin") { + assert( + err.stderr.includes( + `/Assets/umi-logo.png does not match the corresponding path on disk [assets]` + ), + "should throw error" + ); + } else { + assert( + err.stderr.includes( + `Module not found: Can't resolve './Assets/umi-logo.png'` + ), + "should throw error" + ); + } +}; diff --git a/e2e/fixtures/error.case-sensitive/index.ts b/e2e/fixtures/error.case-sensitive/index.ts new file mode 100644 index 000000000..047f9291f --- /dev/null +++ b/e2e/fixtures/error.case-sensitive/index.ts @@ -0,0 +1,4 @@ +import UmiLogo from "./Assets/umi-logo.png"; +console.log(UmiLogo); + +export default UmiLogo; diff --git a/e2e/fixtures/error.case-sensitive/mako.config.json b/e2e/fixtures/error.case-sensitive/mako.config.json new file mode 100644 index 000000000..79dde720d --- /dev/null +++ b/e2e/fixtures/error.case-sensitive/mako.config.json @@ -0,0 +1,4 @@ +{ + "mode": "production", + "caseSensitiveCheck": true +} diff --git a/e2e/fixtures/error.case-sensitive/package.json b/e2e/fixtures/error.case-sensitive/package.json new file mode 100644 index 000000000..baffb4cb3 --- /dev/null +++ b/e2e/fixtures/error.case-sensitive/package.json @@ -0,0 +1,8 @@ +{ + "name": "test", + "version": "1.0.0", + "dependencies": { + "a": "~1.0.0", + "b": "~1.0.0" + } +}