diff --git a/.github/workflows/node-bind-build.yml b/.github/workflows/node-bind-build.yml index 3915cac31..55570f8cb 100644 --- a/.github/workflows/node-bind-build.yml +++ b/.github/workflows/node-bind-build.yml @@ -30,6 +30,12 @@ jobs: rustup target add x86_64-apple-darwin pnpm --filter @umijs/mako build --target x86_64-apple-darwin strip -x ./packages/mako/*.node + - host: macos-latest + target: aarch64-apple-darwin + build: | + rustup target add aarch64-apple-darwin + pnpm --filter @umijs/mako build --target aarch64-apple-darwin + strip -x ./packages/mako/*.node - host: ubuntu-latest target: x86_64-unknown-linux-gnu docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian @@ -37,18 +43,21 @@ jobs: set -e && pnpm --filter @umijs/mako build --target x86_64-unknown-linux-gnu && strip ./packages/mako/*.node - - host: macos-latest - target: aarch64-apple-darwin - build: | - rustup target add aarch64-apple-darwin - pnpm --filter @umijs/mako build --target aarch64-apple-darwin - strip -x ./packages/mako/*.node - host: ubuntu-latest target: x86_64-unknown-linux-musl docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine build: | pnpm --filter @umijs/mako build --target x86_64-unknown-linux-musl strip -x ./packages/mako/*.node + - host: ubuntu-latest + target: aarch64-unknown-linux-gnu + docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64 + build: |- + set -e && + export JEMALLOC_SYS_WITH_LG_PAGE=16 && + export CC_aarch64_unknown_linux_gnu=/usr/aarch64-unknown-linux-gnu/bin/aarch64-unknown-linux-gnu-gcc && + rustup target add aarch64-unknown-linux-gnu && + pnpm --filter @umijs/mako build --target aarch64-unknown-linux-gnu - host: ubuntu-latest target: aarch64-unknown-linux-musl docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine diff --git a/CHANGELOG.md b/CHANGELOG.md index 87e5f62e9..8d2d63679 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ `2024-09-05` -* perf: group chunks with post order dfs by [@xusd320](https://github.com/xusd320) in [#1554](https://github.com/umijs/mako/pull/1554) +* perf: group chunks with right first dfs by [@xusd320](https://github.com/xusd320) in [#1554](https://github.com/umijs/mako/pull/1554) * refactor: unify base64 utils by [@xusd320](https://github.com/xusd320) in [#1557](https://github.com/umijs/mako/pull/1557) * Revert "refactor: Unify the static server in bundler-mako and devServer" by [@stormslowly](https://github.com/stormslowly) in [#1556](https://github.com/umijs/mako/pull/1556) * fix: define env by [@xusd320](https://github.com/xusd320) in [#1551](https://github.com/umijs/mako/pull/1551) diff --git a/CHANGELOG_zh-CN.md b/CHANGELOG_zh-CN.md index fe72f496a..a6c5606b0 100644 --- a/CHANGELOG_zh-CN.md +++ b/CHANGELOG_zh-CN.md @@ -2,7 +2,7 @@ `2024-09-05` -* 优化 group chunks 的性能,基于 post order dfs by [@xusd320](https://github.com/xusd320) in [#1554](https://github.com/umijs/mako/pull/1554) +* 优化 group chunks 的性能,基于 right first dfs by [@xusd320](https://github.com/xusd320) in [#1554](https://github.com/umijs/mako/pull/1554) * 重构 base64 utils by [@xusd320](https://github.com/xusd320) in [#1557](https://github.com/umijs/mako/pull/1557) * 回滚 "refactor: Unify the static server in bundler-mako and devServer" by [@stormslowly](https://github.com/stormslowly) in [#1556](https://github.com/umijs/mako/pull/1556) * 修复 define env by [@xusd320](https://github.com/xusd320) in [#1551](https://github.com/umijs/mako/pull/1551) diff --git a/crates/mako/src/ast/file.rs b/crates/mako/src/ast/file.rs index f84f82e1d..981a39826 100644 --- a/crates/mako/src/ast/file.rs +++ b/crates/mako/src/ast/file.rs @@ -330,21 +330,47 @@ type Search = String; type Params = Vec<(String, String)>; type Fragment = Option; +fn has_hash_without_dot(input: &str) -> bool { + if let Some(pos) = input.find('#') { + let after_hash = &input[pos + 1..]; + !after_hash.contains('.') + } else { + false + } +} + pub fn parse_path(path: &str) -> Result<(PathName, Search, Params, Fragment)> { - let base = "http://a.com/"; - let base_url = Url::parse(base)?; - let full_url = base_url.join(path)?; - let path = full_url.path().to_string(); - let fragment = full_url.fragment().map(|s| s.to_string()); - let search = full_url.query().unwrap_or("").to_string(); - let query_vec = full_url - .query_pairs() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(); - // dir or filename may contains space or other special characters - // so we need to decode it, e.g. "a%20b" -> "a b" - let path = percent_decode_str(&path).decode_utf8()?; - Ok((path.to_string(), search, query_vec, fragment)) + #[cfg(target_os = "windows")] + let path = { + let prefix = "\\\\?\\"; + let path = path.trim_start_matches(prefix); + path.replace('\\', "/") + }; + #[cfg(not(target_os = "windows"))] + let path = path.to_string(); + if path.contains('?') || has_hash_without_dot(path.as_str()) { + let (path, search) = if path.contains('?') { + path.split_once('?').unwrap_or((path.as_str(), "")) + } else { + path.split_once('#').unwrap_or((path.as_str(), "")) + }; + let base = "http://a.com/"; + let base_url = Url::parse(base)?; + let full_url = base_url.join(format!("?{}", search).as_str())?; + let fragment = full_url.fragment().map(|s| s.to_string()); + let search = full_url.query().unwrap_or("").to_string(); + let query_vec = full_url + .query_pairs() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + // dir or filename may contains space or other special characters + // so we need to decode it, e.g. "a%20b" -> "a b" + let path = percent_decode_str(path).decode_utf8()?; + Ok((path.to_string(), search.to_string(), query_vec, fragment)) + } else { + let path = percent_decode_str(&path).decode_utf8()?; + Ok((path.to_string(), "".to_string(), vec![], None)) + } } #[cfg(test)] @@ -371,4 +397,29 @@ mod tests { ); assert_eq!(f.path(), Some("/root/d.js".to_string())); } + + #[test] + fn test_parse_path_support_windows() { + let path = "C:\\a\\b\\c?foo"; + let (path, search, params, fragment) = parse_path(path).unwrap(); + assert_eq!(path, "C:\\a\\b\\c"); + assert_eq!(search, "foo"); + assert_eq!(params, vec![("foo".to_string(), "".to_string())]); + assert_eq!(fragment, None); + } + + #[test] + fn test_parse_path_with_fragment() { + assert_eq!(parse_path("foo.ts#bar").unwrap().0, "foo.ts"); + assert_eq!(parse_path("foo#bar.ts").unwrap().0, "foo#bar.ts"); + } + + #[test] + fn test_has_hash_without_dot() { + assert_eq!(has_hash_without_dot("foo.ts#world"), true); + assert_eq!(has_hash_without_dot("foo#bar.ts"), false); + assert_eq!(has_hash_without_dot("#no_dot"), true); + assert_eq!(has_hash_without_dot("no_hash"), false); + assert_eq!(has_hash_without_dot("#.dot_after_hash"), false); + } } diff --git a/crates/mako/src/config/config.rs b/crates/mako/src/config/config.rs index 443f3f37c..e3a5d2fe0 100644 --- a/crates/mako/src/config/config.rs +++ b/crates/mako/src/config/config.rs @@ -122,6 +122,7 @@ pub struct OutputConfig { pub skip_write: bool, #[serde(deserialize_with = "deserialize_cross_origin_loading")] pub cross_origin_loading: Option, + pub global_module_registry: bool, } #[derive(Deserialize, Serialize, Debug, Clone)] @@ -722,7 +723,8 @@ const DEFAULT_CONFIG: &str = r#" "preserveModules": false, "preserveModulesRoot": "", "skipWrite": false, - "crossOriginLoading": false + "crossOriginLoading": false, + "globalModuleRegistry": false, }, "resolve": { "alias": [], "extensions": ["js", "jsx", "ts", "tsx"] }, "mode": "development", diff --git a/crates/mako/src/generate/chunk_pot/util.rs b/crates/mako/src/generate/chunk_pot/util.rs index d974c80bd..7ed255edb 100644 --- a/crates/mako/src/generate/chunk_pot/util.rs +++ b/crates/mako/src/generate/chunk_pot/util.rs @@ -117,6 +117,7 @@ pub(crate) fn runtime_code(context: &Arc) -> Result { .optimization .as_ref() .map_or(false, |o| o.concatenate_modules.unwrap_or(false)), + global_module_registry: context.config.output.global_module_registry, }; let app_runtime = app_runtime.render_once()?; let app_runtime = app_runtime.replace( diff --git a/crates/mako/src/generate/group_chunk.rs b/crates/mako/src/generate/group_chunk.rs index 40194c08d..80ba1d5fb 100644 --- a/crates/mako/src/generate/group_chunk.rs +++ b/crates/mako/src/generate/group_chunk.rs @@ -483,7 +483,7 @@ impl Compiler { } /* -* Visit dependencies by post order DFS. The reason for this is that +* Visit dependencies by right first DFS. The reason for this is that * the rightmost and deepest css dependence should have the highest priority. * For example, the dependencies graph is: * @@ -499,19 +499,19 @@ impl Compiler { * the final dependencies orders in chunk should be: * * ---------- -* a.css -* c.css -* b.css * index.css +* b.css +* c.css +* a.css * ---------- -* note that c.css, b.css, c.css before a.css will be deduplicated. +* note that c.css, b.css, c.css after a.css will be deduplicated. */ fn visit_modules(mut queue: Vec, visited: Option>, mut callback: F) -> Vec where F: FnMut(&T) -> Vec, T: Hash + Eq + Clone, { - let mut post_order_dfs_ret: Vec = Vec::new(); + let mut right_firtst_dfs_ret: Vec = Vec::new(); let mut visited = visited.unwrap_or_default(); @@ -520,12 +520,12 @@ where continue; } - post_order_dfs_ret.push(id.clone()); + right_firtst_dfs_ret.push(id.clone()); visited.insert(id.clone()); queue.extend(callback(&id)); } - post_order_dfs_ret + right_firtst_dfs_ret } diff --git a/crates/mako/src/generate/runtime.rs b/crates/mako/src/generate/runtime.rs index f18c8589b..1a80cd306 100644 --- a/crates/mako/src/generate/runtime.rs +++ b/crates/mako/src/generate/runtime.rs @@ -12,4 +12,5 @@ pub struct AppRuntimeTemplate { pub is_browser: bool, pub concatenate_enabled: bool, pub cross_origin_loading: Option, + pub global_module_registry: bool, } diff --git a/crates/mako/templates/app_runtime.stpl b/crates/mako/templates/app_runtime.stpl index 7d2a870f0..e3b00e912 100644 --- a/crates/mako/templates/app_runtime.stpl +++ b/crates/mako/templates/app_runtime.stpl @@ -1,5 +1,11 @@ function createRuntime(makoModules, entryModuleId, global) { +<% if global_module_registry { %> + var modulesRegistry = ( + (typeof globalThis !== "undefined" ? globalThis : self).__mako_module_registry = + ((typeof globalThis !== "undefined" ? globalThis : self).__mako_module_registry || {})); +<% } else { %> var modulesRegistry = {}; +<% } %> function requireModule(moduleId) { var cachedModule = modulesRegistry[moduleId]; diff --git a/docs/config.md b/docs/config.md index 2a2cab624..a29061414 100644 --- a/docs/config.md +++ b/docs/config.md @@ -484,6 +484,7 @@ Output related configuration. - `preserveModules`, whether to preserve the module directory structure (Bundless Only) - `preserveModulesRoot`, preserve the root directory of the module directory structure (Bundless Only) - `crossOriginLoading`, control the `crossorigin` attribute of the `script` tag and `link` tag for load async chunks +- `globalModuleRegistry`, whether enable shared module registry across multi entries ### optimization diff --git a/docs/config.zh-CN.md b/docs/config.zh-CN.md index 261dd42f1..819f92a78 100644 --- a/docs/config.zh-CN.md +++ b/docs/config.zh-CN.md @@ -485,6 +485,7 @@ e.g. - `preserveModules`,是否保留模块目录结构(仅适用于 Bundless) - `preserveModulesRoot`,是否保留模块目录结构的根目录(仅限 Bundless) - `crossOriginLoading`,控制异步 chunk 加载时 `script` 及 `link` 标签的 `crossorigin` 属性值 +- `globalModuleRegistry`,是否允许在多 entry 之间共享模块注册中心 ### optimization diff --git a/e2e/fixtures/config.global-module-registry/expect.js b/e2e/fixtures/config.global-module-registry/expect.js new file mode 100644 index 000000000..67207a25f --- /dev/null +++ b/e2e/fixtures/config.global-module-registry/expect.js @@ -0,0 +1,4 @@ +const assert = require("assert"); + +require('./dist/common'); +assert(require('./dist/A').common === require('./dist/B').common, 'global module registry should work'); diff --git a/e2e/fixtures/config.global-module-registry/mako.config.json b/e2e/fixtures/config.global-module-registry/mako.config.json new file mode 100644 index 000000000..222ecfe19 --- /dev/null +++ b/e2e/fixtures/config.global-module-registry/mako.config.json @@ -0,0 +1,25 @@ +{ +"entry": { + "A": "./src/entryA.ts", + "B": "./src/entryB.ts" + }, + "minify": false, + "platform": "node", + "cjs": true, + "codeSplitting": { + "strategy": "advanced", + "options": { + "groups": [ + { + "name": "common", + "allowChunks": "all", + "minSize": 1, + "minChunks": 2 + } + ] + } + }, + "output": { + "globalModuleRegistry": true + } +} diff --git a/e2e/fixtures/config.global-module-registry/src/common.ts b/e2e/fixtures/config.global-module-registry/src/common.ts new file mode 100644 index 000000000..71090afa0 --- /dev/null +++ b/e2e/fixtures/config.global-module-registry/src/common.ts @@ -0,0 +1,3 @@ +export const common = { + value: 1 +} diff --git a/e2e/fixtures/config.global-module-registry/src/entryA.ts b/e2e/fixtures/config.global-module-registry/src/entryA.ts new file mode 100644 index 000000000..1cbd0c274 --- /dev/null +++ b/e2e/fixtures/config.global-module-registry/src/entryA.ts @@ -0,0 +1,2 @@ +export { common } from "./common"; + diff --git a/e2e/fixtures/config.global-module-registry/src/entryB.ts b/e2e/fixtures/config.global-module-registry/src/entryB.ts new file mode 100644 index 000000000..1cbd0c274 --- /dev/null +++ b/e2e/fixtures/config.global-module-registry/src/entryB.ts @@ -0,0 +1,2 @@ +export { common } from "./common"; + diff --git a/e2e/fixtures/os.file_name_too_long/expect.js b/e2e/fixtures/os.file_name_too_long.failed/expect.js similarity index 100% rename from e2e/fixtures/os.file_name_too_long/expect.js rename to e2e/fixtures/os.file_name_too_long.failed/expect.js diff --git a/e2e/fixtures/os.file_name_too_long/mako.config.json b/e2e/fixtures/os.file_name_too_long.failed/mako.config.json similarity index 100% rename from e2e/fixtures/os.file_name_too_long/mako.config.json rename to e2e/fixtures/os.file_name_too_long.failed/mako.config.json diff --git a/e2e/fixtures/os.file_name_too_long/src/index.ts b/e2e/fixtures/os.file_name_too_long.failed/src/index.ts similarity index 100% rename from e2e/fixtures/os.file_name_too_long/src/index.ts rename to e2e/fixtures/os.file_name_too_long.failed/src/index.ts diff --git a/e2e/fixtures/os.file_name_too_long/src/abcdefghigklmnupqlmfdfsdfjsdfijsdfiujsdfjsdifjsdfjsdfabcdefghigklmnupqlmfdfsdfjsdfijsdfiujsdfjsdifjsdfjsdfabcdefghigklmnupqlmfdfsdfjsdfijsdfiujsdfjsdifjsdfjsdfabcdefghigklmnupqlmfdfsdfjsdfijsdfiujsdfjsdifjsdfjsdfabcdefghigklmnupqlmfdfsdfjsdfijsdfiujs.js b/e2e/fixtures/os.file_name_too_long/src/abcdefghigklmnupqlmfdfsdfjsdfijsdfiujsdfjsdifjsdfjsdfabcdefghigklmnupqlmfdfsdfjsdfijsdfiujsdfjsdifjsdfjsdfabcdefghigklmnupqlmfdfsdfjsdfijsdfiujsdfjsdifjsdfjsdfabcdefghigklmnupqlmfdfsdfjsdfijsdfiujsdfjsdifjsdfjsdfabcdefghigklmnupqlmfdfsdfjsdfijsdfiujs.js deleted file mode 100644 index cc798ff50..000000000 --- a/e2e/fixtures/os.file_name_too_long/src/abcdefghigklmnupqlmfdfsdfjsdfijsdfiujsdfjsdifjsdfjsdfabcdefghigklmnupqlmfdfsdfjsdfijsdfiujsdfjsdifjsdfjsdfabcdefghigklmnupqlmfdfsdfjsdfijsdfiujsdfjsdifjsdfjsdfabcdefghigklmnupqlmfdfsdfjsdfijsdfiujsdfjsdifjsdfjsdfabcdefghigklmnupqlmfdfsdfjsdfijsdfiujs.js +++ /dev/null @@ -1 +0,0 @@ -export const a = 1; diff --git a/packages/mako/package.json b/packages/mako/package.json index cdd4d4130..092f12f1c 100644 --- a/packages/mako/package.json +++ b/packages/mako/package.json @@ -12,6 +12,7 @@ "defaults": false, "additional": [ "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", @@ -81,4 +82,4 @@ "@umijs/mako-linux-x64-musl": "0.8.8" }, "repository": "git@github.com:umijs/mako.git" -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9daf555c..35e2a0915 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -539,6 +539,9 @@ importers: '@umijs/mako-darwin-x64': specifier: 0.8.8 version: 0.8.8 + '@umijs/mako-linux-arm64-musl': + specifier: 0.8.8 + version: 0.8.8 '@umijs/mako-linux-x64-gnu': specifier: 0.8.8 version: 0.8.8 @@ -6524,6 +6527,15 @@ packages: dev: false optional: true + /@umijs/mako-linux-arm64-musl@0.8.8: + resolution: {integrity: sha512-BCjC4f3QBIXdvvBkEpcCqRqQF5N+2JaGH7twIUitiRcEt6RaZyunotRZMVMkbDZOozsaDZPFI1oocrKBPV4yqg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@umijs/mako-linux-x64-gnu@0.7.4: resolution: {integrity: sha512-Wew66PM1A6PhPDsve46tChGyQ0UzagELEgmzeKjBjVrvtC5+pgtcBrRDAx9cpN16x3eMLY0z9SZAc9zGNjkZow==} engines: {node: '>= 10'} diff --git a/scripts/test-e2e.mjs b/scripts/test-e2e.mjs index 2402abd13..90382edbb 100644 --- a/scripts/test-e2e.mjs +++ b/scripts/test-e2e.mjs @@ -26,6 +26,7 @@ for (const dir of onlyDir ? [onlyDir] : dirs) { const testFn = dir.includes('failed') && !argv.only ? test.skip : test; await testFn(dir, async () => { const cwd = path.join(fixtures, dir); + const expectPath = `file://${path.join(fixtures, dir, 'expect.js')}`; if (argv.umi) { if (!fs.existsSync(path.join(cwd, 'node_modules'))) { await $`cd ${cwd} && mkdir node_modules`; @@ -40,11 +41,11 @@ for (const dir of onlyDir ? [onlyDir] : dirs) { } else { try { // run mako build - await $`${path.join(root, 'scripts', 'mako.js')} ${cwd}`; + await $`node ${path.join(root, 'scripts', 'mako.js')} ${cwd}`; } catch (e) { const isErrorCase = dir.split('.').includes('error'); if (isErrorCase) { - const mod = await import(path.join(fixtures, dir, 'expect.js')); + const mod = await import(expectPath); mod.default(e); return; } else { @@ -53,7 +54,7 @@ for (const dir of onlyDir ? [onlyDir] : dirs) { } } // run expect.js - const mod = await import(path.join(fixtures, dir, 'expect.js')); + const mod = await import(expectPath); if (mod && typeof mod.default === 'function') { await mod.default(); }