diff --git a/.changeset/config.json b/.changeset/config.json index 9d5a1350533..e660031cfe0 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -26,6 +26,7 @@ "@module-federation/error-codes", "@module-federation/inject-external-runtime-core-plugin", "@module-federation/runtime-core", + "@module-federation/micro-effect", "create-module-federation", "@module-federation/cli", "@module-federation/rspress-plugin", diff --git a/.changeset/effect-runtime-consolidation.md b/.changeset/effect-runtime-consolidation.md new file mode 100644 index 00000000000..6a02ee2baab --- /dev/null +++ b/.changeset/effect-runtime-consolidation.md @@ -0,0 +1,27 @@ +--- +'@module-federation/cli': patch +'@module-federation/data-prefetch': patch +'@module-federation/devtools': patch +'@module-federation/dts-plugin': patch +'@module-federation/enhanced': patch +'@module-federation/error-codes': patch +'@module-federation/esbuild': patch +'@module-federation/inject-external-runtime-core-plugin': patch +'@module-federation/managers': patch +'@module-federation/manifest': patch +'@module-federation/metro': patch +'@module-federation/micro-effect': patch +'@module-federation/nextjs-mf': patch +'@module-federation/retry-plugin': patch +'@module-federation/rsbuild-plugin': patch +'@module-federation/rspack': patch +'@module-federation/rspress-plugin': patch +'@module-federation/runtime': patch +'@module-federation/runtime-core': patch +'@module-federation/runtime-tools': patch +'@module-federation/sdk': patch +'@module-federation/utilities': patch +'@module-federation/webpack-bundler-runtime': patch +--- + +Align package surfaces impacted by the Effect-based runtime-core migration, rslib structural consolidation, Metro compatibility updates, and related tooling/docs sync across this branch. diff --git a/.github/workflows/e2e-manifest.yml b/.github/workflows/e2e-manifest.yml index 71c19574e6e..55e2d26d083 100644 --- a/.github/workflows/e2e-manifest.yml +++ b/.github/workflows/e2e-manifest.yml @@ -57,22 +57,12 @@ jobs: - name: Run condition check script id: check-ci - run: | - ci_status=0 - node tools/scripts/ci-is-affected.mjs --appName=manifest-webpack-host || ci_status=$? - if [ "$ci_status" -eq 0 ]; then - echo "run-e2e=true" >> "$GITHUB_OUTPUT" - elif [ "$ci_status" -eq 1 ]; then - echo "run-e2e=false" >> "$GITHUB_OUTPUT" - else - echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2 - exit "$ci_status" - fi + run: node tools/scripts/ci-is-affected.mjs --appName=manifest-webpack-host - name: E2E Test for Manifest Demo Development - if: steps.check-ci.outputs.run-e2e == 'true' + if: steps.check-ci.outcome == 'success' run: node tools/scripts/run-manifest-e2e.mjs --mode=dev - name: E2E Test for Manifest Demo Production - if: steps.check-ci.outputs.run-e2e == 'true' + if: steps.check-ci.outcome == 'success' run: node tools/scripts/run-manifest-e2e.mjs --mode=prod diff --git a/.github/workflows/e2e-modern-ssr.yml b/.github/workflows/e2e-modern-ssr.yml index b143ed14610..5933b729874 100644 --- a/.github/workflows/e2e-modern-ssr.yml +++ b/.github/workflows/e2e-modern-ssr.yml @@ -56,20 +56,10 @@ jobs: - name: Run condition check script id: check-ci - run: | - ci_status=0 - node tools/scripts/ci-is-affected.mjs --appName=modernjs || ci_status=$? - if [ "$ci_status" -eq 0 ]; then - echo "run-e2e=true" >> "$GITHUB_OUTPUT" - elif [ "$ci_status" -eq 1 ]; then - echo "run-e2e=false" >> "$GITHUB_OUTPUT" - else - echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2 - exit "$ci_status" - fi + run: node tools/scripts/ci-is-affected.mjs --appName=modernjs - name: E2E Test for Modern.js SSR - if: steps.check-ci.outputs.run-e2e == 'true' + if: steps.check-ci.outcome == 'success' uses: nick-fields/retry@v3 with: max_attempts: 2 diff --git a/.github/workflows/e2e-modern.yml b/.github/workflows/e2e-modern.yml index 35106f03e33..15c066b9487 100644 --- a/.github/workflows/e2e-modern.yml +++ b/.github/workflows/e2e-modern.yml @@ -57,20 +57,10 @@ jobs: - name: Run condition check script id: check-ci - run: | - ci_status=0 - node tools/scripts/ci-is-affected.mjs --appName=modernjs || ci_status=$? - if [ "$ci_status" -eq 0 ]; then - echo "run-e2e=true" >> "$GITHUB_OUTPUT" - elif [ "$ci_status" -eq 1 ]; then - echo "run-e2e=false" >> "$GITHUB_OUTPUT" - else - echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2 - exit "$ci_status" - fi + run: node tools/scripts/ci-is-affected.mjs --appName=modernjs - name: E2E Test for ModernJS - if: steps.check-ci.outputs.run-e2e == 'true' + if: steps.check-ci.outcome == 'success' run: npx kill-port --port 4001 && npx nx run-many --target=test:e2e --projects=modernjs --parallel=1 && npx kill-port --port 4001 - name: Kill ports diff --git a/.github/workflows/e2e-next-dev.yml b/.github/workflows/e2e-next-dev.yml index db1f1029aac..86ad4d342f6 100644 --- a/.github/workflows/e2e-next-dev.yml +++ b/.github/workflows/e2e-next-dev.yml @@ -59,18 +59,8 @@ jobs: - name: Run condition check script id: check-ci - run: | - ci_status=0 - node tools/scripts/ci-is-affected.mjs --appName=3000-home || ci_status=$? - if [ "$ci_status" -eq 0 ]; then - echo "run-e2e=true" >> "$GITHUB_OUTPUT" - elif [ "$ci_status" -eq 1 ]; then - echo "run-e2e=false" >> "$GITHUB_OUTPUT" - else - echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2 - exit "$ci_status" - fi + run: node tools/scripts/ci-is-affected.mjs --appName=3000-home - name: E2E Test for Next.js Dev - if: steps.check-ci.outputs.run-e2e == 'true' + if: steps.check-ci.outcome == 'success' run: node tools/scripts/run-next-e2e.mjs --mode=dev diff --git a/.github/workflows/e2e-next-prod.yml b/.github/workflows/e2e-next-prod.yml index f4bcdf27981..02a8a90e15a 100644 --- a/.github/workflows/e2e-next-prod.yml +++ b/.github/workflows/e2e-next-prod.yml @@ -56,18 +56,8 @@ jobs: - name: Run condition check script id: check-ci - run: | - ci_status=0 - node tools/scripts/ci-is-affected.mjs --appName=3000-home || ci_status=$? - if [ "$ci_status" -eq 0 ]; then - echo "run-e2e=true" >> "$GITHUB_OUTPUT" - elif [ "$ci_status" -eq 1 ]; then - echo "run-e2e=false" >> "$GITHUB_OUTPUT" - else - echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2 - exit "$ci_status" - fi + run: node tools/scripts/ci-is-affected.mjs --appName=3000-home - name: E2E Test for Next.js Prod - if: steps.check-ci.outputs.run-e2e == 'true' + if: steps.check-ci.outcome == 'success' run: node tools/scripts/run-next-e2e.mjs --mode=prod diff --git a/.github/workflows/e2e-node.yml b/.github/workflows/e2e-node.yml index 773cc3b3bf7..8f3793aeb68 100644 --- a/.github/workflows/e2e-node.yml +++ b/.github/workflows/e2e-node.yml @@ -57,18 +57,8 @@ jobs: - name: Run condition check script id: check-ci - run: | - ci_status=0 - node tools/scripts/ci-is-affected.mjs --appName=node-local-remote,node-remote,node-dynamic-remote-new-version,node-dynamic-remote || ci_status=$? - if [ "$ci_status" -eq 0 ]; then - echo "run-e2e=true" >> "$GITHUB_OUTPUT" - elif [ "$ci_status" -eq 1 ]; then - echo "run-e2e=false" >> "$GITHUB_OUTPUT" - else - echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2 - exit "$ci_status" - fi + run: node tools/scripts/ci-is-affected.mjs --appName=node-local-remote,node-remote,node-dynamic-remote-new-version,node-dynamic-remote - name: E2E Node Federation - if: steps.check-ci.outputs.run-e2e == 'true' + if: steps.check-ci.outcome == 'success' run: npx nx run-many --target=serve --projects=node-local-remote,node-remote,node-dynamic-remote-new-version,node-dynamic-remote --parallel=10 & echo "done" && sleep 25 && npx nx run-many --target=serve --projects=node-host & sleep 5 && npx wait-on tcp:3333 && npx nx run node-host-e2e:test:e2e diff --git a/.github/workflows/e2e-router.yml b/.github/workflows/e2e-router.yml index cb9674329ff..66cf8977ace 100644 --- a/.github/workflows/e2e-router.yml +++ b/.github/workflows/e2e-router.yml @@ -57,18 +57,8 @@ jobs: - name: Run condition check script id: check-ci - run: | - ci_status=0 - node tools/scripts/ci-is-affected.mjs --appName=router-host-2000,router-host-v5-2200,router-host-vue3-2100,router-remote1-2001,router-remote2-2002,router-remote3-2003,router-remote4-2004 || ci_status=$? - if [ "$ci_status" -eq 0 ]; then - echo "run-e2e=true" >> "$GITHUB_OUTPUT" - elif [ "$ci_status" -eq 1 ]; then - echo "run-e2e=false" >> "$GITHUB_OUTPUT" - else - echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2 - exit "$ci_status" - fi + run: node tools/scripts/ci-is-affected.mjs --appName=router-host-2000,router-host-v5-2200,router-host-vue3-2100,router-remote1-2001,router-remote2-2002,router-remote3-2003,router-remote4-2004 - name: E2E Test for Runtime Demo - if: steps.check-ci.outputs.run-e2e == 'true' + if: steps.check-ci.outcome == 'success' run: node tools/scripts/run-router-e2e.mjs --mode=dev diff --git a/.github/workflows/e2e-runtime.yml b/.github/workflows/e2e-runtime.yml index 9b34b74a2aa..be899080fa8 100644 --- a/.github/workflows/e2e-runtime.yml +++ b/.github/workflows/e2e-runtime.yml @@ -57,18 +57,8 @@ jobs: - name: Run condition check script id: check-ci - run: | - ci_status=0 - node tools/scripts/ci-is-affected.mjs --appName=3005-runtime-host || ci_status=$? - if [ "$ci_status" -eq 0 ]; then - echo "run-e2e=true" >> "$GITHUB_OUTPUT" - elif [ "$ci_status" -eq 1 ]; then - echo "run-e2e=false" >> "$GITHUB_OUTPUT" - else - echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2 - exit "$ci_status" - fi + run: node tools/scripts/ci-is-affected.mjs --appName=3005-runtime-host - name: E2E Test for Runtime Demo - if: steps.check-ci.outputs.run-e2e == 'true' + if: steps.check-ci.outcome == 'success' run: npx kill-port --port 3005,3006,3007 && pnpm run app:runtime:dev & echo "done" && sleep 20 && npx nx run-many --target=test:e2e --projects=3005-runtime-host --parallel=1 && lsof -ti tcp:3005,3006,3007 | xargs kill diff --git a/.github/workflows/e2e-shared-tree-shaking.yml b/.github/workflows/e2e-shared-tree-shaking.yml index 959e976c529..6c3564282a6 100644 --- a/.github/workflows/e2e-shared-tree-shaking.yml +++ b/.github/workflows/e2e-shared-tree-shaking.yml @@ -56,22 +56,12 @@ jobs: - name: Run condition check script id: check-ci - run: | - ci_status=0 - node tools/scripts/ci-is-affected.mjs --appName=shared-tree-shaking-with-server-host || ci_status=$? - if [ "$ci_status" -eq 0 ]; then - echo "run-e2e=true" >> "$GITHUB_OUTPUT" - elif [ "$ci_status" -eq 1 ]; then - echo "run-e2e=false" >> "$GITHUB_OUTPUT" - else - echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2 - exit "$ci_status" - fi + run: node tools/scripts/ci-is-affected.mjs --appName=shared-tree-shaking-with-server-host - name: E2E Test for Shared Tree Shaking Demo - Mode runtime-infer - if: steps.check-ci.outputs.run-e2e == 'true' + if: steps.check-ci.outcome == 'success' run: npx kill-port --port 3001,3002 && nx run shared-tree-shaking-no-server-host:test:e2e && lsof -ti tcp:3001,3002 | xargs kill - name: E2E Test for Shared Tree Shaking Demo - Mode server-calc - if: steps.check-ci.outputs.run-e2e == 'true' + if: steps.check-ci.outcome == 'success' run: npx kill-port --port 3001,3002,3003 && nx run shared-tree-shaking-with-server-host:test:e2e && lsof -ti tcp:3001,3002,3003 | xargs kill diff --git a/.github/workflows/e2e-treeshake.yml b/.github/workflows/e2e-treeshake.yml index fbcc8851cab..170fccda34d 100644 --- a/.github/workflows/e2e-treeshake.yml +++ b/.github/workflows/e2e-treeshake.yml @@ -56,22 +56,12 @@ jobs: - name: Run condition check script id: check-ci - run: | - ci_status=0 - node tools/scripts/ci-is-affected.mjs --appName=treeshake-server,treeshake-frontend || ci_status=$? - if [ "$ci_status" -eq 0 ]; then - echo "run-e2e=true" >> "$GITHUB_OUTPUT" - elif [ "$ci_status" -eq 1 ]; then - echo "run-e2e=false" >> "$GITHUB_OUTPUT" - else - echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2 - exit "$ci_status" - fi + run: node tools/scripts/ci-is-affected.mjs --appName=treeshake-server,treeshake-frontend - name: E2E Treeshake Server - if: steps.check-ci.outputs.run-e2e == 'true' + if: steps.check-ci.outcome == 'success' run: npx nx run treeshake-server:test - name: E2E Treeshake Frontend - if: steps.check-ci.outputs.run-e2e == 'true' + if: steps.check-ci.outcome == 'success' run: npx nx run treeshake-frontend:e2e diff --git a/apps/metro-example-host/Gemfile.lock b/apps/metro-example-host/Gemfile.lock index 6f58f7d9b75..46cb5c886c6 100644 --- a/apps/metro-example-host/Gemfile.lock +++ b/apps/metro-example-host/Gemfile.lock @@ -1,29 +1,31 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.9) - activesupport (7.1.6) + CFPropertyList (3.0.7) + base64 + nkf + rexml + activesupport (7.2.2.1) base64 benchmark (>= 0.3) bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) + concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) logger (>= 1.4.2) minitest (>= 5.1) - mutex_m securerandom (>= 0.3) - tzinfo (~> 2.0) - addressable (2.8.8) - public_suffix (>= 2.0.2, < 8.0) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) - base64 (0.3.0) - benchmark (0.5.0) - bigdecimal (4.0.1) + base64 (0.2.0) + benchmark (0.4.0) + bigdecimal (3.1.9) claide (1.1.0) cocoapods (1.15.2) addressable (~> 2.8) @@ -64,33 +66,34 @@ GEM cocoapods-try (1.2.0) colored2 (3.1.2) concurrent-ruby (1.3.3) - connection_pool (2.5.5) - drb (2.2.3) + connection_pool (2.5.3) + drb (2.2.1) escape (0.0.4) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) - ffi (1.17.3) + ffi (1.17.2) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) httpclient (2.9.0) mutex_m - i18n (1.14.8) + i18n (1.14.7) concurrent-ruby (~> 1.0) - json (2.18.1) + json (2.11.3) logger (1.7.0) - minitest (5.26.1) + minitest (5.25.5) molinillo (0.8.0) mutex_m (0.3.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) + nkf (0.2.0) public_suffix (4.0.7) - rexml (3.4.4) + rexml (3.4.1) ruby-macho (2.5.1) - securerandom (0.3.2) - typhoeus (1.5.0) - ethon (>= 0.9.0, < 0.16.0) + securerandom (0.4.1) + typhoeus (1.4.1) + ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) xcodeproj (1.25.1) @@ -115,7 +118,7 @@ DEPENDENCIES xcodeproj (< 1.26.0) RUBY VERSION - ruby 2.7.6p219 + ruby 3.1.0p0 BUNDLED WITH - 2.1.4 + 2.6.1 diff --git a/apps/metro-example-host/metro.config.js b/apps/metro-example-host/metro.config.js index d9940291da5..bb93c22dfae 100644 --- a/apps/metro-example-host/metro.config.js +++ b/apps/metro-example-host/metro.config.js @@ -11,15 +11,11 @@ const {withModuleFederation} = require('@module-federation/metro'); */ const config = { - resolver: { - extraNodeModules: { - '@babel/runtime': path.resolve(__dirname, 'node_modules/@babel/runtime'), - }, - useWatchman: false, - }, + resolver: {useWatchman: false}, watchFolders: [ path.resolve(__dirname, '../../node_modules'), - path.resolve(__dirname, '../../packages'), + path.resolve(__dirname, '../../packages/core'), + path.resolve(__dirname, '../../packages/metro-core'), ], }; diff --git a/apps/metro-example-mini/Gemfile.lock b/apps/metro-example-mini/Gemfile.lock index 6f58f7d9b75..5af6b649c94 100644 --- a/apps/metro-example-mini/Gemfile.lock +++ b/apps/metro-example-mini/Gemfile.lock @@ -1,29 +1,31 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.9) - activesupport (7.1.6) + CFPropertyList (3.0.7) + base64 + nkf + rexml + activesupport (7.2.2.1) base64 benchmark (>= 0.3) bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) + concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) logger (>= 1.4.2) minitest (>= 5.1) - mutex_m securerandom (>= 0.3) - tzinfo (~> 2.0) - addressable (2.8.8) - public_suffix (>= 2.0.2, < 8.0) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) base64 (0.3.0) - benchmark (0.5.0) - bigdecimal (4.0.1) + benchmark (0.4.1) + bigdecimal (3.2.2) claide (1.1.0) cocoapods (1.15.2) addressable (~> 2.8) @@ -64,33 +66,34 @@ GEM cocoapods-try (1.2.0) colored2 (3.1.2) concurrent-ruby (1.3.3) - connection_pool (2.5.5) + connection_pool (2.5.3) drb (2.2.3) escape (0.0.4) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) - ffi (1.17.3) + ffi (1.17.2) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) httpclient (2.9.0) mutex_m - i18n (1.14.8) + i18n (1.14.7) concurrent-ruby (~> 1.0) - json (2.18.1) + json (2.12.2) logger (1.7.0) - minitest (5.26.1) + minitest (5.25.5) molinillo (0.8.0) mutex_m (0.3.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) + nkf (0.2.0) public_suffix (4.0.7) - rexml (3.4.4) + rexml (3.4.2) ruby-macho (2.5.1) - securerandom (0.3.2) - typhoeus (1.5.0) - ethon (>= 0.9.0, < 0.16.0) + securerandom (0.4.1) + typhoeus (1.4.1) + ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) xcodeproj (1.25.1) @@ -115,7 +118,7 @@ DEPENDENCIES xcodeproj (< 1.26.0) RUBY VERSION - ruby 2.7.6p219 + ruby 3.1.0p0 BUNDLED WITH - 2.1.4 + 2.6.1 diff --git a/apps/metro-example-mini/ios/Podfile.lock b/apps/metro-example-mini/ios/Podfile.lock index 779df101702..3b3b7029bb4 100644 --- a/apps/metro-example-mini/ios/Podfile.lock +++ b/apps/metro-example-mini/ios/Podfile.lock @@ -2422,10 +2422,10 @@ SPEC CHECKSUMS: React-timing: a275a1c2e6112dba17f8f7dd496d439213bbea0d React-utils: 449a6e1fd53886510e284e80bdbb1b1c6db29452 ReactAppDependencyProvider: 3267432b637c9b38e86961b287f784ee1b08dde0 - ReactCodegen: d308d08c58717331dcf82d0129efa8b73e28a64c + ReactCodegen: a1a6d7288d6a5fc86f109e46149c35d707932702 ReactCommon: b028d09a66e60ebd83ca59d8cc9a1216360db147 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 395b5d614cd7cbbfd76b05d01bd67230a6ad004e + Yoga: 0c4b7d2aacc910a1f702694fa86be830386f4ceb PODFILE CHECKSUM: a8134080201cda3c42e54a89f48d0930861e3c58 diff --git a/apps/metro-example-mini/metro.config.js b/apps/metro-example-mini/metro.config.js index 94f953a8281..caec135c811 100644 --- a/apps/metro-example-mini/metro.config.js +++ b/apps/metro-example-mini/metro.config.js @@ -10,15 +10,11 @@ const {withModuleFederation} = require('@module-federation/metro'); * @type {import('@react-native/metro-config').MetroConfig} */ const config = { - resolver: { - extraNodeModules: { - '@babel/runtime': path.resolve(__dirname, 'node_modules/@babel/runtime'), - }, - useWatchman: false, - }, + resolver: {useWatchman: false}, watchFolders: [ path.resolve(__dirname, '../../node_modules'), - path.resolve(__dirname, '../../packages'), + path.resolve(__dirname, '../../packages/core'), + path.resolve(__dirname, '../../packages/metro-core'), ], }; diff --git a/apps/metro-example-nested-mini/Gemfile.lock b/apps/metro-example-nested-mini/Gemfile.lock index 6f58f7d9b75..792397b7eb9 100644 --- a/apps/metro-example-nested-mini/Gemfile.lock +++ b/apps/metro-example-nested-mini/Gemfile.lock @@ -1,29 +1,31 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.9) - activesupport (7.1.6) + CFPropertyList (3.0.7) + base64 + nkf + rexml + activesupport (7.2.2.1) base64 benchmark (>= 0.3) bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) + concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) logger (>= 1.4.2) minitest (>= 5.1) - mutex_m securerandom (>= 0.3) - tzinfo (~> 2.0) - addressable (2.8.8) - public_suffix (>= 2.0.2, < 8.0) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) base64 (0.3.0) - benchmark (0.5.0) - bigdecimal (4.0.1) + benchmark (0.4.1) + bigdecimal (3.2.2) claide (1.1.0) cocoapods (1.15.2) addressable (~> 2.8) @@ -64,33 +66,34 @@ GEM cocoapods-try (1.2.0) colored2 (3.1.2) concurrent-ruby (1.3.3) - connection_pool (2.5.5) + connection_pool (2.5.3) drb (2.2.3) escape (0.0.4) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) - ffi (1.17.3) + ffi (1.17.2) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) httpclient (2.9.0) mutex_m - i18n (1.14.8) + i18n (1.14.7) concurrent-ruby (~> 1.0) - json (2.18.1) + json (2.12.2) logger (1.7.0) - minitest (5.26.1) + minitest (5.25.5) molinillo (0.8.0) mutex_m (0.3.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) + nkf (0.2.0) public_suffix (4.0.7) - rexml (3.4.4) + rexml (3.4.1) ruby-macho (2.5.1) - securerandom (0.3.2) - typhoeus (1.5.0) - ethon (>= 0.9.0, < 0.16.0) + securerandom (0.4.1) + typhoeus (1.4.1) + ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) xcodeproj (1.25.1) @@ -115,7 +118,7 @@ DEPENDENCIES xcodeproj (< 1.26.0) RUBY VERSION - ruby 2.7.6p219 + ruby 3.1.0p0 BUNDLED WITH - 2.1.4 + 2.6.1 diff --git a/apps/metro-example-nested-mini/ios/Podfile.lock b/apps/metro-example-nested-mini/ios/Podfile.lock index 779df101702..3b3b7029bb4 100644 --- a/apps/metro-example-nested-mini/ios/Podfile.lock +++ b/apps/metro-example-nested-mini/ios/Podfile.lock @@ -2422,10 +2422,10 @@ SPEC CHECKSUMS: React-timing: a275a1c2e6112dba17f8f7dd496d439213bbea0d React-utils: 449a6e1fd53886510e284e80bdbb1b1c6db29452 ReactAppDependencyProvider: 3267432b637c9b38e86961b287f784ee1b08dde0 - ReactCodegen: d308d08c58717331dcf82d0129efa8b73e28a64c + ReactCodegen: a1a6d7288d6a5fc86f109e46149c35d707932702 ReactCommon: b028d09a66e60ebd83ca59d8cc9a1216360db147 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 395b5d614cd7cbbfd76b05d01bd67230a6ad004e + Yoga: 0c4b7d2aacc910a1f702694fa86be830386f4ceb PODFILE CHECKSUM: a8134080201cda3c42e54a89f48d0930861e3c58 diff --git a/apps/metro-example-nested-mini/metro.config.js b/apps/metro-example-nested-mini/metro.config.js index f77a324c024..5636b392e2b 100644 --- a/apps/metro-example-nested-mini/metro.config.js +++ b/apps/metro-example-nested-mini/metro.config.js @@ -10,15 +10,11 @@ const {withModuleFederation} = require('@module-federation/metro'); * @type {import('@react-native/metro-config').MetroConfig} */ const config = { - resolver: { - extraNodeModules: { - '@babel/runtime': path.resolve(__dirname, 'node_modules/@babel/runtime'), - }, - useWatchman: false, - }, + resolver: {useWatchman: false}, watchFolders: [ path.resolve(__dirname, '../../node_modules'), - path.resolve(__dirname, '../../packages'), + path.resolve(__dirname, '../../packages/core'), + path.resolve(__dirname, '../../packages/metro-core'), ], }; diff --git a/apps/website-new/docs/en/_nav.json b/apps/website-new/docs/en/_nav.json index 76184351d60..eba64d27060 100644 --- a/apps/website-new/docs/en/_nav.json +++ b/apps/website-new/docs/en/_nav.json @@ -26,7 +26,7 @@ }, { "text": "Blog", - "link": "/blog/index", + "link": "/blog/announcement", "activeMatch": "/blog/" }, { diff --git a/apps/website-new/docs/en/blog/_meta.json b/apps/website-new/docs/en/blog/_meta.json index 13404f7317b..764405b3349 100644 --- a/apps/website-new/docs/en/blog/_meta.json +++ b/apps/website-new/docs/en/blog/_meta.json @@ -1,5 +1,4 @@ [ - "index", "announcement", "hoisted-runtime", "error-load-remote", @@ -7,10 +6,5 @@ "type": "file", "name": "node", "label": "Module Federation on Node.js, Made Easy" - }, - { - "type": "file", - "name": "v2-stable-version", - "label": "MF 2.0 Stable Release" } ] diff --git a/apps/website-new/docs/en/blog/index.mdx b/apps/website-new/docs/en/blog/index.mdx index 86b815fa7d1..403d8c9d3ae 100644 --- a/apps/website-new/docs/en/blog/index.mdx +++ b/apps/website-new/docs/en/blog/index.mdx @@ -1,31 +1,21 @@ ---- -title: Overview -sidebar: false ---- -# Module Federation Blog +# Overview -Find the latest posts and release announcements about Module Federation here. +## Changelog -## [Module Federation 2.0 Stable Release](./v2-stable-version) +Please visit [GitHub - release](https://github.com/module-federation/universe/releases) to view the changes for each version of Module Federation 2.0. -> February 10, 2026 +## Semantic Version -Module Federation 2.0 is officially stable, bringing shared dependency tree shaking and a Modern.js v3 plugin to make integration easier. +Module Federation 2.0 follows the [Semantic Versioning](https://semver.org/lang/zh-CN/) semantic version specification. -## [Module Federation on Node.js: From Usable to Ergonomic](./node) +- Major version: contains incompatible API changes. +- Minor version: contains backward compatible features and fixes. +- Patch version: contains backwards compatible bug fixes. -> January 23, 2026 +:::tip +Module Federation 2.0 is currently in 0.x version, so minor version may also contain some breaking changes. +::: -Learn how to use Module Federation in Node.js, including runtime-only consumption, Rspack/Webpack integration, and producer-side build configuration. +## Release cycle -## [Handling Remote Module Rendering Errors](./error-load-remote) - -> September 9, 2025 - -Fallback strategies for remote module load/render failures: network retries, the errorLoadRemote hook, and React ErrorBoundary. - -## [Release Announcement](./announcement) - -> April 26, 2024 - -Announces the official release of Module Federation 2.0 and introduces core capabilities like runtime decoupling, type safety, devtools, and the Manifest protocol. +- Module Federation 2.0 will release several patch versions every week. diff --git a/apps/website-new/docs/en/configure/_meta.json b/apps/website-new/docs/en/configure/_meta.json index 86bccb92f47..a01a76ade78 100644 --- a/apps/website-new/docs/en/configure/_meta.json +++ b/apps/website-new/docs/en/configure/_meta.json @@ -73,25 +73,5 @@ "type": "file", "name": "experiments", "label": "experiments" - }, - { - "type": "file", - "name": "injectTreeShakingUsedExports", - "label": "injectTreeShakingUsedExports" - }, - { - "type": "file", - "name": "treeShakingDir", - "label": "treeShakingDir" - }, - { - "type": "file", - "name": "treeShakingSharedExcludePlugins", - "label": "treeShakingSharedExcludePlugins" - }, - { - "type": "file", - "name": "treeShakingSharedPlugins", - "label": "treeShakingSharedPlugins" } ] diff --git a/apps/website-new/docs/en/configure/index.mdx b/apps/website-new/docs/en/configure/index.mdx index 10c1ea85440..83f408a4db7 100644 --- a/apps/website-new/docs/en/configure/index.mdx +++ b/apps/website-new/docs/en/configure/index.mdx @@ -30,9 +30,5 @@ type ModuleFederationOptions = { dts?: boolean | PluginDtsOptions; // Use a virtual runtime entrypoint instead of writing a temporary file to disk virtualRuntimeEntry?: boolean; - injectTreeShakingUsedExports?: boolean; - treeShakingDir?: string; - treeShakingSharedPlugins?: Array; - treeShakingSharedExcludePlugins?: Array; }; ``` diff --git a/apps/website-new/docs/en/configure/shared.mdx b/apps/website-new/docs/en/configure/shared.mdx index b01d92f6e77..26512fc3f90 100644 --- a/apps/website-new/docs/en/configure/shared.mdx +++ b/apps/website-new/docs/en/configure/shared.mdx @@ -22,12 +22,6 @@ interface SharedConfig { shareScope?: string; import?: string | false; allowNodeModulesSuffixMatch?: boolean; - treeShaking?: TreeShakingConfig; -} - -interface TreeShakingConfig { - mode: 'server-calc' | 'runtime-infer'; - usedExports?: string[]; } ``` @@ -114,7 +108,7 @@ Enables a fallback lookup that matches shared modules by the portion of their re > Previously this option was called `nodeModulesReconstructedLookup`. - +{/* ## treeShaking - Type: `TreeShakingConfig` @@ -123,9 +117,9 @@ Enables a fallback lookup that matches shared modules by the portion of their re Configures tree-shaking behavior for shared dependencies. Enabling tree shaking for shared dependencies can reduce the size of the shared bundle. -> Rspack is the recommended bundler for this capability, and the docs/examples prioritize Rspack usage. - - +> Only takes effect when using Rspack as the bundler. + */} +{/* ### usedExports - Type: `string[]` @@ -143,7 +137,7 @@ Manually **add** the exports used from the shared dependency. Configures the loading strategy for tree-shaken shared dependencies. - `runtime-infer`: infers based on the consumer’s `usedExports`. If the provider’s `usedExports` satisfies the consumer’s `usedExports`, it uses the provider’s `shared`; otherwise it uses the consumer’s own `shared`. If neither satisfies the requirement, it falls back to the full bundle. -- `server-calc`: decides whether to load the shared dependency based on the Snapshot delivered by the server. +- `server-calc`: decides whether to load the shared dependency based on the Snapshot delivered by the server. */} ### filename diff --git a/apps/website-new/docs/en/configure/treeShakingDir.mdx b/apps/website-new/docs/en/configure/treeShakingDir.mdx index d390116cb96..65447ea181b 100644 --- a/apps/website-new/docs/en/configure/treeShakingDir.mdx +++ b/apps/website-new/docs/en/configure/treeShakingDir.mdx @@ -1,9 +1,9 @@ # treeShakingDir -The directory used to output tree-shaking shared fallback assets. +用于输出 tree shaking 共享 fallback 资源的目录。 -- Type: `string` -- Required: No -- Default: `undefined` +- 类型:`string` +- 是否必填:否 +- 默认值:`undefined` -When shared dependency tree shaking is enabled, Module Federation will extract the exports of unused shared modules. This option specifies the output directory for those fallback assets. +当启用共享依赖 tree shaking 功能时,Module Federation 会将未使用的共享模块导出拆分出来。该选项指定了这些 fallback 资源的输出目录。 diff --git a/apps/website-new/docs/en/configure/treeShakingSharedExcludePlugins.mdx b/apps/website-new/docs/en/configure/treeShakingSharedExcludePlugins.mdx index db3319da1d4..e2b8cf208e6 100644 --- a/apps/website-new/docs/en/configure/treeShakingSharedExcludePlugins.mdx +++ b/apps/website-new/docs/en/configure/treeShakingSharedExcludePlugins.mdx @@ -1,9 +1,11 @@ # treeShakingSharedExcludePlugins -Configures the plugin names to exclude during the shared dependency tree shaking/fallback process. +配置在构建共享依赖 tree shaking/fallback 过程中需要排除的插件名称。 + +- 类型:`string[]` +- 是否必填:否 +- 默认值:`['HtmlWebpackPlugin','HtmlRspackPlugin']` + +允许用户指定一组插件名称,这些插件在构建共享依赖 tree shaking/fallback 过程 时将被忽略或不参与处理。 -- Type: `string[]` -- Required: No -- Default: `['HtmlWebpackPlugin','HtmlRspackPlugin']` -Allows users to specify a list of plugin names. These plugins will be ignored and will not participate in the shared dependency tree shaking/fallback process. diff --git a/apps/website-new/docs/en/configure/treeShakingSharedPlugins.mdx b/apps/website-new/docs/en/configure/treeShakingSharedPlugins.mdx index 3f3c95939a6..86125bd06f9 100644 --- a/apps/website-new/docs/en/configure/treeShakingSharedPlugins.mdx +++ b/apps/website-new/docs/en/configure/treeShakingSharedPlugins.mdx @@ -1,16 +1,15 @@ # treeShakingSharedPlugins -Allows users to explicitly specify which plugins should participate in the second tree-shaking pass for shared modules. +允许用户显式指定哪些插件应该参与 shared 模块的第二次 treeshaking 过程。 -- Type: `string[]` -- Required: No -- Default: `undefined` +- 类型:`string[]` +- 是否必填:否 +- 默认值:`undefined` -If `shared.treeShaking.mode` is set to `'server-calc'`, the deployment service will rebuild the shared dependencies that need tree shaking. In this process, only the shared dependencies are built, and the original project's build configuration is not loaded. +如果设置了 `shared.treeShaking.mode` 为 'server-calc',那么在部署服务中,会重新构建需要 tree shaking 的共享依赖,此时仅构建共享依赖,不会加载原项目的构建配置。 +如果你的项目有特殊的构建配置,例如设置了 externals ,那么你可以把这些构建配置集成到一个 NPM Package ,然后在 `treeShakingSharedPlugins` 中填写此插件的名称和版本,这样就可以在第二次 treeshaking 过程中参与 shared 模块的 treeshaking 过程。 -If your project relies on special build configuration—for example, setting `externals`—you can package that configuration into an NPM package, then provide the plugin name and version in `treeShakingSharedPlugins`. This allows the plugin to participate in the second tree-shaking pass for shared modules. - -For example, suppose you provide a plugin `my-build-plugin` that sets `externals`: +例如提供了一个插件 `my-build-plugin`,它会设置 `externals`: ```ts title='my-build-plugin' class MyBuildPlugin { @@ -23,7 +22,7 @@ class MyBuildPlugin { export default MyBuildPlugin; ``` -If you publish this plugin as version `0.0.2`, you only need to specify its name and version in `treeShakingSharedPlugins`: +将此插件发布的版本为 `0.0.2` ,那么此时只需要在 `treeShakingSharedPlugins` 中填写插件的名称和版本即可: ```ts title='module-federation.config.ts' diff --git a/apps/website-new/docs/en/guide/_meta.json b/apps/website-new/docs/en/guide/_meta.json index ebb1ec5c376..98e48604f22 100644 --- a/apps/website-new/docs/en/guide/_meta.json +++ b/apps/website-new/docs/en/guide/_meta.json @@ -24,16 +24,6 @@ "name": "framework", "label": "Frameworks" }, - { - "type": "dir-section-header", - "name": "performance", - "label": "Performance" - }, - { - "type": "dir-section-header", - "name": "advanced", - "label": "Advanced" - }, { "type": "dir-section-header", "name": "deployment", diff --git a/apps/website-new/docs/en/guide/performance/_meta.json b/apps/website-new/docs/en/guide/performance/_meta.json deleted file mode 100644 index 9738874ed56..00000000000 --- a/apps/website-new/docs/en/guide/performance/_meta.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - "shared-tree-shaking" -] diff --git a/apps/website-new/docs/en/guide/performance/shared-tree-shaking.mdx b/apps/website-new/docs/en/guide/performance/shared-tree-shaking.mdx deleted file mode 100644 index 58792dadb11..00000000000 --- a/apps/website-new/docs/en/guide/performance/shared-tree-shaking.mdx +++ /dev/null @@ -1,132 +0,0 @@ -# Shared Tree Shaking - -## Background - -When using Module Federation (MF), we often share common dependencies (such as antd, React, etc.) through the `shared` configuration to reduce duplicate bundling and keep dependency versions consistent across applications. However, the classic `shared` mechanism has a downside: it always provides the **full** dependency bundle, even if your application only uses a small portion of it (for example, only the `Button` component from antd). - -This can lead to: - -* **Large build artifacts**: unnecessary code is bundled, increasing the final output size. -* **Runtime overhead**: the browser downloads and executes more JavaScript than needed, slowing down page load and render. - -**Shared Tree Shaking** is designed to solve this. It analyzes your code, precisely identifies which exports are actually used, and bundles only that subset. As a result, your application can consume an optimized, smaller shared dependency. - -:::tip Key Benefits - -* **Smaller output size**: reduces unnecessary code at the source. -* **Faster loading**: users download only what they need. -* **Better runtime performance**: less JavaScript to parse and execute. - -::: - -## Quick Start - -Enabling Shared Tree Shaking is straightforward: add the `treeShaking` option in your configuration. - -### Example - -```ts title="rspack.config.ts" -export default { - // ... - plugins: [ - new ModuleFederationPlugin({ - // ... - shared: { - antd: { - treeShaking: { mode: 'runtime-infer' }, - }, - }, - }), - ], -}; -``` - -### Local verification - -After building locally, check the output size and the exported content of the shared dependency (e.g. antd). The expected result is that it only contains the modules you actually use. - -## Configuration - -The core option is [sharedItem.treeShaking](../../configure/shared#treeshaking). You can also control where Tree Shaking artifacts are generated via: - -* [treeShakingDir](../../configure/treeShakingDir) -* [injectTreeShakingUsedExports](../../configure/injectTreeShakingUsedExports) -* [treeShakingSharedPlugins](../../configure/treeShakingSharedPlugins) -* [treeShakingSharedExcludePlugins](../../configure/treeShakingSharedExcludePlugins) - -## Modes - -There are two modes to fit different team setups and project scales. - -### runtime-infer - -`runtime-infer` is a lightweight strategy that does not require a centralized service. - -- **Enable**: set `mode` to `runtime-infer`. -- **Best for**: - - local development and quick validation, - - teams without a deployment or CI service, - - smaller projects with a single consumer. -- **How it works**: at runtime, if the current page’s needs are already satisfied by a previously loaded, tree-shaken shared bundle, it reuses it. Otherwise, it **falls back to loading the full dependency bundle** to ensure correctness. -- **Improving hit rate**: without a global view, the reuse rate of `runtime-infer` may be limited. You can manually augment the `usedExports` list in your configuration to pre-declare modules you may need, guiding the compiler to generate a more suitable shared bundle and improving reuse. - -### server-calc - -`server-calc` is the **strongly recommended best practice**. It leverages a centralized service to maximize the benefits of Tree Shaking and achieve a global optimum. - -import CreateSharedTreeShakingDeployServer from '@components/en/create-shared-tree-shaking-deploy-server.mdx' - -{!props.name && ( - -)} - -## Validation and rollback - -### How to confirm Tree Shaking is working? - -1. **Network panel check**: in the browser DevTools Network panel, filter loaded JS files. Confirm the loaded file is the secondary build artifact (typically containing identifiers like `secondary` or a hash) rather than the original full bundle. Its size should be significantly smaller. -2. **Inspect bundle contents**: download the loaded shared dependency JS file and search for exports you **did not use** (e.g. you only used `Button`, so search for `Modal`). If they’re not present, they were successfully shaken out. -3. **Chrome DevTools verification**: in Chrome DevTools, open the “Shared” panel and select the target shared dependency. Check its status tags: `Tree Shaking Loaded` means Tree Shaking is effective and the trimmed artifact is loaded; `Loaded` means it fell back to loading the full bundle; `Tree Shaking Loading` means it’s loading via the Tree Shaking path. - -![](https://module-federation-assest.netlify.app/document/guide/performance/shared-tree-shaking/tree-shaking-devtool.jpg) - -### How to safely roll back? - -Shared dependency Tree Shaking is designed with an **automatic safe fallback**. If the runtime detects issues (Snapshot not delivered, network errors, version mismatch, etc.), it defaults to loading the full shared dependency bundle to keep the application stable. - -If you need to disable it manually, stop the secondary-build and Snapshot-update steps in your deployment service. - -import SecondaryBuild from '@components/en/secondary-build.mdx' -import React from 'react'; - -{props.secondaryBuild || React.createElement(SecondaryBuild)} - -## FAQ - -**1. Can Shared Tree Shaking be used with `eager: true`?** - -**No.** `eager: true` bundles shared dependencies into the initial chunk, which conflicts with the on-demand loading model required by Tree Shaking. You must choose between them: -- If the shared dependency is small and you want the fastest possible initial load, consider `eager: true`. -- If the shared dependency is large (e.g. a component library), **disable `eager`** and use Tree Shaking for significant size and performance gains. - -**2. What should I watch out for with singleton dependencies in `runtime-infer`?** - -Be careful. If a shared dependency is configured as `singleton: true` (must be globally singleton), you may hit scenarios like: -- App A uses only `antd/Button` and loads its own tree-shaken bundle. -- App B uses `antd/Modal` and loads its own full bundle. - -This can result in two different antd instances on the same page (a minimal one and a full one). That breaks the singleton constraint and may cause style conflicts, non-shared state, or even crashes. - -**Recommendation**: for libraries that must remain singleton, prefer **`server-calc`** so all consumers load the same globally optimized bundle. If you must use `runtime-infer`, expand `usedExports` to make the produced bundle more complete and reduce the risk of conflicts. - -**3. What are the prerequisites for Tree Shaking to work?** - -It primarily depends on the bundler’s static analysis. Your code must use ES Modules (`import`/`export`) so the compiler can determine which exports are used. CommonJS (`require`/`module.exports`) modules typically cannot be tree-shaken effectively. - -**4. Will this break shared dependencies for already released projects?** - -**No.** The data source and loading path for Tree Shaking are isolated from the existing shared mechanism, and there are strict hit conditions and safe fallback behavior. It does not affect stable legacy projects. - -## Further reading - -* [How Shared Dependency Tree Shaking Works](https://github.com/module-federation/core/discussions/4302) diff --git a/apps/website-new/docs/en/index.md b/apps/website-new/docs/en/index.md index 2d5fac3b338..4ecf10d7ddc 100644 --- a/apps/website-new/docs/en/index.md +++ b/apps/website-new/docs/en/index.md @@ -8,7 +8,7 @@ hero: actions: - theme: brand text: 🎉 2.0 Announcement - link: /blog/v2-stable-version.html + link: /blog/announcement.html - theme: alt text: Quick Start link: /guide/start/quick-start.html diff --git a/apps/website-new/docs/zh/_nav.json b/apps/website-new/docs/zh/_nav.json index 0e88e0cf352..1e19b05fb68 100644 --- a/apps/website-new/docs/zh/_nav.json +++ b/apps/website-new/docs/zh/_nav.json @@ -26,7 +26,7 @@ }, { "text": "博客", - "link": "/blog/index", + "link": "/blog/announcement", "activeMatch": "/blog/" }, { diff --git a/apps/website-new/docs/zh/blog/_meta.json b/apps/website-new/docs/zh/blog/_meta.json index 9c29d9eaca8..4aa261ac0d1 100644 --- a/apps/website-new/docs/zh/blog/_meta.json +++ b/apps/website-new/docs/zh/blog/_meta.json @@ -1,15 +1,5 @@ -[ - "index", - "announcement", - "error-load-remote", - { - "type": "file", - "name": "node", - "label": "Module Federation on Node.js:从能用到易用" - }, - { - "type": "file", - "name": "v2-stable-version", - "label": "Module Federation 2.0 稳定版本发布" - } -] +[ "announcement", "error-load-remote", { + "type": "file", + "name": "node", + "label": "Module Federation on Node.js:从能用到易用" +}] diff --git a/apps/website-new/docs/zh/blog/index.mdx b/apps/website-new/docs/zh/blog/index.mdx index 29a0b0b727e..ea7d84a5cab 100644 --- a/apps/website-new/docs/zh/blog/index.mdx +++ b/apps/website-new/docs/zh/blog/index.mdx @@ -1,31 +1,22 @@ ---- -title: 总览 -sidebar: false ---- -# Module Federation 博客 +# 总览 -在此查看有关 Module Federation 的最新文章和发布公告。 +## 更新日志 -## [Module Federation 2.0 稳定版本发布](./v2-stable-version) +请访问 [GitHub - release](https://github.com/module-federation/universe/releases) 来查看 Module Federation 2.0 每个版本的变更内容。 -> 2026 年 2 月 10 日 +## 语义化版本 -Module Federation 2.0 稳定版正式发布,提供共享依赖 Tree Shaking 能力,并提供 Modern.js v3 插件,帮助开发者更方便地集成 Module Federation 2.0。 +`Module Federation 2.0` 遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/) 语义化版本规范。 -## [Module Federation on Node.js:从能用到易用](./node) +- 主版本号:包含不兼容的 API 变更。 +- 次版本号:包含向下兼容的功能性变更。 +- 修订号:包含向下兼容的问题修正。 -> 2026 年 1 月 23 日 +:::tip +`Module Federation 2.0` 当前处于 0.x 版本,因此次版本号也会包含一些不兼容的 API 变更。 +::: -介绍 Module Federation 在 Node.js 环境下的使用方式,覆盖纯运行时消费、Rspack/Webpack 集成,以及生产者侧的构建配置。 +## 发版周期 -## [远程模块渲染错误处理方案](./error-load-remote) +- `Module Federation 2.0` 通常会在每周发布若干个 patch 版本。 -> 2025 年 9 月 9 日 - -提供远程模块加载/渲染失败的兜底方案:网络重试、errorLoadRemote hook 以及 React ErrorBoundary。 - -## [发布公告](./announcement) - -> 2024 年 4 月 26 日 - -宣布 Module Federation 2.0 正式发布,并介绍 Runtime 解耦、类型安全、调试工具与 Manifest 协议等核心能力。 diff --git a/apps/website-new/docs/zh/configure/_meta.json b/apps/website-new/docs/zh/configure/_meta.json index 6e9ea948ad4..cfd534ad096 100644 --- a/apps/website-new/docs/zh/configure/_meta.json +++ b/apps/website-new/docs/zh/configure/_meta.json @@ -73,25 +73,5 @@ "type": "file", "name": "experiments", "label": "experiments" - }, - { - "type": "file", - "name": "injectTreeShakingUsedExports", - "label": "injectTreeShakingUsedExports" - }, - { - "type": "file", - "name": "treeShakingDir", - "label": "treeShakingDir" - }, - { - "type": "file", - "name": "treeShakingSharedExcludePlugins", - "label": "treeShakingSharedExcludePlugins" - }, - { - "type": "file", - "name": "treeShakingSharedPlugins", - "label": "treeShakingSharedPlugins" } ] diff --git a/apps/website-new/docs/zh/configure/index.mdx b/apps/website-new/docs/zh/configure/index.mdx index fa950dd8c4e..d2bc04d5971 100644 --- a/apps/website-new/docs/zh/configure/index.mdx +++ b/apps/website-new/docs/zh/configure/index.mdx @@ -28,9 +28,5 @@ type ModuleFederationOptions { dev?: boolean | PluginDevOptions; // 控制类型生成 dts?: boolean | PluginDtsOptions; - injectTreeShakingUsedExports?: boolean; - treeShakingDir?: string; - treeShakingSharedPlugins?: Array; - treeShakingSharedExcludePlugins?: Array; }; ``` diff --git a/apps/website-new/docs/zh/configure/shared.mdx b/apps/website-new/docs/zh/configure/shared.mdx index 5b0ee769746..6863ad5d9f6 100644 --- a/apps/website-new/docs/zh/configure/shared.mdx +++ b/apps/website-new/docs/zh/configure/shared.mdx @@ -22,12 +22,6 @@ interface SharedConfig { shareScope?: string; import?: string | false; allowNodeModulesSuffixMatch?: boolean; - treeShaking?: TreeShakingConfig; -} - -interface TreeShakingConfig { - mode: 'server-calc' | 'runtime-infer'; - usedExports?: string[]; } ``` @@ -115,7 +109,7 @@ new ModuleFederationPlugin({ > 该选项此前名为 `nodeModulesReconstructedLookup`。 -## treeShaking +{/* ## treeShaking - 类型:`TreeShakingConfig` - 是否必填:否 @@ -123,9 +117,9 @@ new ModuleFederationPlugin({ 配置 shared 依赖的 treeshaking 行为。通过开启 shared 依赖的 treeshaking,可以减少共享依赖的体积。 -> 该能力推荐优先使用 Rspack,文档与示例也以 Rspack 为主。 - - +> 仅在使用 Rspack 作为构建工具时生效 + */} +{/* ### usedExports - 类型:`string[]` @@ -143,7 +137,7 @@ new ModuleFederationPlugin({ 配置 treeshaking 的加载策略。 - `runtime-infer`: 根据消费方的 `usedExports` 进行推断,如果提供方的 `usedExports` 符合当前消费方的 `usedExports`,那么就会使用提供方的 `shared`,否则会使用消费方自身的 `shared`,如果都不满足,就使用全量的。 -- `server-calc`: 根据服务端下发的 snapshot 来决定是否加载共享依赖。 +- `server-calc`: 根据服务端下发的 snapshot 来决定是否加载共享依赖。 */} ### filename diff --git a/apps/website-new/docs/zh/guide/_meta.json b/apps/website-new/docs/zh/guide/_meta.json index 87517f086d1..cc4c33143df 100644 --- a/apps/website-new/docs/zh/guide/_meta.json +++ b/apps/website-new/docs/zh/guide/_meta.json @@ -24,16 +24,6 @@ "name": "framework", "label": "框架" }, - { - "type": "dir-section-header", - "name": "performance", - "label": "性能优化" - }, - { - "type": "dir-section-header", - "name": "advanced", - "label": "高级能力" - }, { "type": "dir-section-header", "name": "deployment", diff --git a/apps/website-new/docs/zh/guide/performance/_meta.json b/apps/website-new/docs/zh/guide/performance/_meta.json deleted file mode 100644 index 9738874ed56..00000000000 --- a/apps/website-new/docs/zh/guide/performance/_meta.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - "shared-tree-shaking" -] diff --git a/apps/website-new/docs/zh/guide/performance/shared-tree-shaking.mdx b/apps/website-new/docs/zh/guide/performance/shared-tree-shaking.mdx deleted file mode 100644 index ea923b415de..00000000000 --- a/apps/website-new/docs/zh/guide/performance/shared-tree-shaking.mdx +++ /dev/null @@ -1,131 +0,0 @@ -# 共享依赖 Tree Shaking - -## 背景 - -在使用 { props.name || 'Module Federation' } 时,我们常常通过 `shared` 配置来共享通用依赖(如 antd, React 等),以减少重复打包和提升应用间的一致性。然而,传统 `shared` 机制存在一个痛点:它总是会打包并提供**完整的**依赖包,即便你的应用实际只用到了其中的一小部分功能(例如,只用了一个 antd 的 `Button` 组件)。 - -这会导致: -* **构建产物体积过大**:不必要的代码被打包,增加了最终产物的体积。 -* **运行时性能损耗**:浏览器需要下载并执行更多非必需的 JavaScript,延长了页面加载和渲染时间。 - -**Shared Tree Shaking** 正是为了解决这一问题而生。它能够智能地分析你的代码,精确识别出实际被使用的模块导出(exports),并仅打包这部分代码。最终,你的应用将获得一个经过“摇树”优化、体积更小的共享依赖。 - -:::tip 核心收益 - -* **更小的构建体积**:从源头上减少不必要的代码,大幅缩减产物尺寸。 -* **更快的加载速度**:用户只需下载真正需要的代码,加速页面响应。 -* **更优的渲染性能**:减少浏览器解析和执行的 JavaScript,提升运行时体验。 - -::: - -## 快速上手 - -为你的项目开启 Shared Tree Shaking 非常简单,只需在配置文件中添加 `treeShaking` 选项即可。 - -### 示例 - -```ts title="rspack.config.ts" -export default { - //... - plugins: [ - new ModuleFederationPlugin({ - //... - shared: { - 'antd': { - treeShaking: { mode: 'runtime-infer' } - } - } - }) - ] -}; -``` - -### 本地验证 - -在本地构建后,观测对应的共享依赖(antd)产物体积以及导出内容,期望的内容是只包含使用的模块。 - -## 配置项 - -核心配置为 [sharedItem.treeShaking](../../configure/shared#treeshaking),你也可以通过设置以下配置来控制 Tree Shaking 产物路径: - -* [treeShakingDir](../../configure/treeShakingDir) -* [injectTreeShakingUsedExports](../../configure/injectTreeShakingUsedExports) -* [treeShakingSharedPlugins](../../configure/treeShakingSharedPlugins) -* [treeShakingSharedExcludePlugins](../../configure/treeShakingSharedExcludePlugins) - -## 模式切换 - -我们提供了两种模式来以适应不同团队和项目的需求。 - -### runtime-infer - -`runtime-infer` 是一种轻量级的、无需中心化服务支持的策略。 - -- **如何启用**:将 `mode` 设置为 `runtime-infer` 。 -- **适用场景**: - - 本地开发环境,快速验证效果。 - - 没有部署或 CI 服务的团队。 - - 规模较小、消费者单一的项目。 -- **工作逻辑**:运行时,如果当前页面需要的功能已经被某个已加载的、经过 Tree Shaking 的共享包所满足,则直接复用。否则,**回退加载完整的依赖包**,以确保功能完备。 -- **提升复用率**:由于缺少全局视角,`runtime-infer` 的复用率可能不高。你可以通过手动在配置中补充 `usedExports` 列表,提前声明可能会用到的模块,从而引导编译器生成更优化的共享包,提升复用效率。 - -### server-calc - -`server-calc` 是**强烈推荐的最佳实践**。它通过中心化的服务来最大化 Tree Shaking 的收益,实现全局最优。 - -import CreateSharedTreeShakingDeployServer from '@components/zh/create-shared-tree-shaking-deploy-server.mdx' - -{!props.name && ( - -)} - -## 验证与回退 - -### 如何确认 Tree Shaking 生效? - -1. **网络面板检查**:在浏览器开发者工具的“网络 (Network)”面板中,筛选加载的 JS 文件。确认加载的是二次构建生成的、带有特定标识(如 `secondary` 或哈希值)的优化包,而不是原始的全量包。其体积也应该显著小于全量包。 -2. **产物内容比对**:直接下载并查看加载的共享依赖 JS 文件内容。搜索你**未使用**的组件或函数名(例如,你只用了 `Button`,可以去搜索 `Modal`),如果搜索不到,说明它们已被成功摇掉。 -3. **Chrome DevTools 验证**:在 Chrome DevTools 的「Shared」面板中选中目标共享依赖,查看其状态标签:出现 `Tree Shaking Loaded` 表示 Tree Shaking 生效并加载了裁剪产物;仅有 `Loaded` 则表示回退为全量包加载;`Tree Shaking Loading` 表示正在按 Tree Shaking 路径加载。 - -![](https://module-federation-assest.netlify.app/document/guide/performance/shared-tree-shaking/tree-shaking-devtool.jpg) - -### 如何安全回退? - -共享依赖 Tree Shaking 的设计包含了**自动安全回退**机制。如果运行时检测到任何问题(如 Snapshot 未下发、网络错误、版本不匹配等),它会默认加载全量的共享依赖包,确保应用的稳定性。 - -如果你需要手动禁用此功能,只需在部署服务中停止二次构建和 Snapshot 更新流程即可。 - -import SecondaryBuild from '@components/zh/secondary-build.mdx' -import React from 'react'; - -{props.secondaryBuild || React.createElement(SecondaryBuild)} - -## 常见问题 (FAQ) - -**1. Shared Tree Shaking 能和 `eager: true` 一起使用吗?** - -**不能。** `eager: true` 会将共享依赖直接打包进应用入口文件 (initial chunk),这与 Tree Shaking 按需动态加载的机制是互斥的。你需要在这两者之间做出取舍: -- 若共享依赖体积不大,且追求极致的初始加载速度,可考虑 `eager: true`。 -- 若共享依赖体积庞大(如组件库),强烈建议**关闭 `eager`**,使用 Tree Shaking 来获得显著的体积与性能收益。 - -**2. 在 `runtime-infer` 模式下,单例依赖需要注意什么?** - -需要特别小心。如果一个共享依赖被配置为 `singleton: true`(必须全局单例),可能会出现以下场景: -- 应用 A 只用到了 `antd` 的 `Button`,加载了**自己**的树摇包。 -- 应用 B 用到了 `antd` 的 `Modal`,它会加载**自己**的全量包。 - -此时,页面上会同时存在两个不同版本的 `antd` 实例(一个极简版,一个完整版),这会破坏单例模式,可能导致样式冲突、状态不共享、甚至应用崩溃。 - -**建议**:对于必须保持单例的库,**优先使用 `server-calc` 策略**,确保所有消费者都使用同一个经过全局优化的共享包。如果只能使用 `runtime-infer`,请通过补充 `usedExports` 尽可能地让生成的包更完整,以降低冲突风险。 - -**3. Tree Shaking 的命中条件是什么?** - -主要依赖于构建工具的静态分析能力。代码必须采用 ES Module (`import`/`export`) 语法,以便编译器能够分析出哪些导出被使用了。CommonJS (`require`/`module.exports`) 模块通常无法被有效 Tree Shaking。 - -**4. 它是否会破坏已发布项目的共享依赖?** - -**不会。** Tree Shaking 的数据源和加载路径与原有的共享机制是隔离的,并且有严格的命中条件和安全回退逻辑。它不会影响已经稳定运行的老项目。 - -## 延伸阅读 - -* [共享依赖 Tree Shaking 工作原理](https://github.com/module-federation/core/discussions/4302) diff --git a/apps/website-new/docs/zh/index.md b/apps/website-new/docs/zh/index.md index ce617481cf5..4a83cbf67fa 100644 --- a/apps/website-new/docs/zh/index.md +++ b/apps/website-new/docs/zh/index.md @@ -8,7 +8,7 @@ hero: actions: - theme: brand text: 🎉 发布公告 - link: /zh/blog/v2-stable-version.html + link: /zh/blog/announcement.html - theme: alt text: 快速开始 link: /zh/guide/start/quick-start.html diff --git a/apps/website-new/package.json b/apps/website-new/package.json index 321aa5d402d..0b2cf6ab156 100644 --- a/apps/website-new/package.json +++ b/apps/website-new/package.json @@ -8,17 +8,21 @@ "preview": "rspress preview" }, "dependencies": { + "framer-motion": "^10.0.0", + "@rspress/core": "2.0.1", + "tailwindcss": "^3.2.7", + "xgplayer": "^3.0.16", + "@rspress/plugin-llms": "2.0.1", + "@rsbuild/plugin-sass": "^1.3.2", "@module-federation/error-codes": "workspace:*", + "@rspress/plugin-llms": "2.0.1", "@module-federation/rspress-plugin": "workspace:*", "@rsbuild/plugin-sass": "^1.5.0", "@rspress/core": "2.0.3", "@rspress/plugin-llms": "2.0.1", "framer-motion": "^10.0.0", "react": "19.1.1", - "react-dom": "19.1.1", - "react-router-dom": "^7.6.1", - "tailwindcss": "^3.2.7", - "xgplayer": "^3.0.16" + "react-dom": "19.1.1" }, "devDependencies": { "@types/node": "^20.19.5", diff --git a/apps/website-new/src/components/NoSSR.tsx b/apps/website-new/src/components/NoSSR.tsx deleted file mode 100644 index 2fb23ea70f1..00000000000 --- a/apps/website-new/src/components/NoSSR.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type { ReactNode } from 'react'; - -export function NoSSR(props: { children?: ReactNode }) { - if (typeof document === 'undefined') { - return null; - } - - return <>{props.children}; -} diff --git a/package.json b/package.json index fbf55f1f451..c47a00d5e1d 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "serve:website": "nx run website-new:serve", "build:website": "NX_TUI=false nx run website-new:build", "extract-i18n:website": "nx run website:extract-i18n", - "sync:webpack:types": "node ./scripts/sync-webpack-unbundled-types.mjs", "sync:pullMFTypes": "concurrently \"node ./packages/enhanced/pullts.js\"", "app:next:dev": "nx run-many --target=serve --configuration=development -p 3000-home,3001-shop,3002-checkout", "app:next:build": "nx run-many --target=build --parallel=2 --configuration=production -p 3000-home,3001-shop,3002-checkout", diff --git a/packages/chrome-devtools/e2e/index.spec.ts b/packages/chrome-devtools/e2e/index.spec.ts index 03d37f6722c..d891eea6808 100644 --- a/packages/chrome-devtools/e2e/index.spec.ts +++ b/packages/chrome-devtools/e2e/index.spec.ts @@ -18,6 +18,16 @@ const sleep = (timeout: number) => }, timeout); }); +const waitForMatchedVersion = async (page: Page, expected: string) => { + await page.waitForFunction( + (matchedVersion) => + (window as any)?.__FEDERATION__?.moduleInfo?.manifest_host?.remotesInfo + ?.webpack_provider?.matchedVersion === matchedVersion, + expected, + { timeout: 30000 }, + ); +}; + const beforeHandler = (request: Request) => { const url = request.url(); if (url.includes('manifest.json') && !beforeProxyRequest.includes(url)) { @@ -79,6 +89,23 @@ test('test proxy', async ({ request }) => { targetPage.removeListener('request', beforeHandler); await sleep(3000); + // Check the page proxy status + await waitForMatchedVersion(targetPage, proxyUrl); + let targetPageModuleInfo = await targetPage.evaluate(() => { + return (window as any)?.__FEDERATION__?.moduleInfo ?? {}; + }); + + expect(targetPageModuleInfo).toMatchObject({ + manifest_host: { + remotesInfo: { + webpack_provider: { + matchedVersion: proxyUrl, + }, + }, + }, + }); + await sleep(3000); + // Setting proxy logic const addButton = devtoolsPage.locator('[data-set-e2e=e2eAdd]'); await expect(addButton).toBeVisible({ timeout: 60000 }); @@ -115,5 +142,27 @@ test('test proxy', async ({ request }) => { await targetPage.bringToFront(); + expect(beforeProxyRequest).toContain(proxyUrl); + expect(beforeProxyRequest).not.toContain(mockUrl); + + expect(afterProxyRequest).toContain(mockUrl); + expect(afterProxyRequest).not.toContain(proxyUrl); + + // check proxy snapshot + await waitForMatchedVersion(targetPage, mockUrl); + let targetPageModuleInfoNew = await targetPage.evaluate(() => { + return (window as any)?.__FEDERATION__?.moduleInfo ?? {}; + }); + + expect(targetPageModuleInfoNew).toMatchObject({ + manifest_host: { + remotesInfo: { + webpack_provider: { + matchedVersion: mockUrl, + }, + }, + }, + }); + console.log(beforeProxyRequest, afterProxyRequest); }); diff --git a/packages/chrome-devtools/src/utils/chrome/fast-refresh.ts b/packages/chrome-devtools/src/utils/chrome/fast-refresh.ts index 56d228e9f60..cbaa5776234 100644 --- a/packages/chrome-devtools/src/utils/chrome/fast-refresh.ts +++ b/packages/chrome-devtools/src/utils/chrome/fast-refresh.ts @@ -1,5 +1,7 @@ -import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime'; -import type { Shared } from '@module-federation/runtime/types'; +import type { + ModuleFederationRuntimePlugin, + Shared, +} from '@module-federation/runtime/types'; import { loadScript, createScript } from '@module-federation/sdk'; import { isObject, getUnpkgUrl } from '../index'; diff --git a/packages/chrome-devtools/src/utils/chrome/override-remote.ts b/packages/chrome-devtools/src/utils/chrome/override-remote.ts index 001e65289de..7e0a169c616 100644 --- a/packages/chrome-devtools/src/utils/chrome/override-remote.ts +++ b/packages/chrome-devtools/src/utils/chrome/override-remote.ts @@ -1,6 +1,6 @@ import runtimeHelpers from '@module-federation/runtime/helpers'; -import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; import { definePropertyGlobalVal } from '../sdk'; import { __FEDERATION_DEVTOOLS__ } from '@/template'; diff --git a/packages/chrome-devtools/src/utils/chrome/post-message.ts b/packages/chrome-devtools/src/utils/chrome/post-message.ts index bf70d49b05c..58a96e23a5e 100644 --- a/packages/chrome-devtools/src/utils/chrome/post-message.ts +++ b/packages/chrome-devtools/src/utils/chrome/post-message.ts @@ -1,5 +1,5 @@ import helpers from '@module-federation/runtime/helpers'; -import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; import { definePropertyGlobalVal } from '../sdk'; diff --git a/packages/chrome-devtools/src/utils/chrome/snapshot-plugin.ts b/packages/chrome-devtools/src/utils/chrome/snapshot-plugin.ts index 6a6122bd990..70ed2534387 100644 --- a/packages/chrome-devtools/src/utils/chrome/snapshot-plugin.ts +++ b/packages/chrome-devtools/src/utils/chrome/snapshot-plugin.ts @@ -1,7 +1,7 @@ import { MODULE_DEVTOOL_IDENTIFIER } from '@module-federation/sdk'; import runtimeHelpers from '@module-federation/runtime/helpers'; -import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; import { definePropertyGlobalVal } from '../sdk'; diff --git a/packages/cli/bin/mf.js b/packages/cli/bin/mf.js index a3b68de5e72..62f70a3dd05 100755 --- a/packages/cli/bin/mf.js +++ b/packages/cli/bin/mf.js @@ -1,4 +1,4 @@ #!/usr/bin/env node -const { runCli } = require('../dist/index.js'); +const { runCli } = require('../dist/index.cjs.js'); runCli(); diff --git a/packages/cli/package.json b/packages/cli/package.json index 3d825c0d84a..d363422b586 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -17,7 +17,7 @@ }, "types": "./dist/index.d.ts", "license": "MIT", - "main": "./dist/index.js", + "main": "./dist/index.cjs.js", "files": [ "dist", "bin" diff --git a/packages/cli/project.json b/packages/cli/project.json index 7c283f2505c..bc226bad700 100644 --- a/packages/cli/project.json +++ b/packages/cli/project.json @@ -5,18 +5,20 @@ "projectType": "library", "targets": { "build": { - "executor": "nx:run-commands", - "outputs": ["{workspaceRoot}/packages/cli/dist"], + "executor": "@nx/rollup:rollup", + "outputs": ["{options.outputPath}"], "options": { - "command": "rslib build", - "cwd": "packages/cli" - }, - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ] + "outputPath": "packages/cli/dist", + "main": "packages/cli/src/index.ts", + "tsConfig": "packages/cli/tsconfig.json", + "assets": [], + "project": "packages/cli/package.json", + "rollupConfig": "packages/cli/rollup.config.js", + "compiler": "tsc", + "format": ["cjs"], + "generatePackageJson": false, + "useLegacyTypescriptPlugin": false + } }, "lint": { "executor": "@nx/eslint:lint", diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index e4b76e67cee..bae5caa511b 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "baseUrl": ".", "rootDir": "./", + "outDir": "dist", "sourceMap": false, "module": "commonjs", "target": "ES2015", diff --git a/packages/data-prefetch/package.json b/packages/data-prefetch/package.json index 1a169cc73ee..63572f7010b 100644 --- a/packages/data-prefetch/package.json +++ b/packages/data-prefetch/package.json @@ -24,54 +24,29 @@ ], "exports": { ".": { - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "require": { - "types": "./dist/index.d.cts", - "default": "./dist/index.cjs" - } + "types": "./dist/index.d.ts", + "import": "./dist/index.esm.js", + "require": "./dist/index.cjs.cjs" }, "./react": { - "import": { - "types": "./dist/react.d.ts", - "default": "./dist/react.js" - }, - "require": { - "types": "./dist/react.d.cts", - "default": "./dist/react.cjs" - } + "types": "./dist/react.d.ts", + "import": "./dist/react.esm.js", + "require": "./dist/react.cjs.cjs" }, "./cli": { - "import": { - "types": "./dist/cli.d.ts", - "default": "./dist/cli.js" - }, - "require": { - "types": "./dist/cli.d.cts", - "default": "./dist/cli.cjs" - } + "types": "./dist/cli.d.ts", + "import": "./dist/cli.esm.js", + "require": "./dist/cli.cjs.cjs" }, "./babel-plugin": { - "import": { - "types": "./dist/babel.d.ts", - "default": "./dist/babel.js" - }, - "require": { - "types": "./dist/babel.d.cts", - "default": "./dist/babel.cjs" - } + "types": "./dist/babel.d.ts", + "import": "./dist/babel.esm.js", + "require": "./dist/babel.cjs.cjs" }, "./universal": { - "import": { - "types": "./dist/universal.d.ts", - "default": "./dist/universal.js" - }, - "require": { - "types": "./dist/universal.d.cts", - "default": "./dist/universal.cjs" - } + "types": "./dist/universal.d.ts", + "import": "./dist/universal.esm.js", + "require": "./dist/universal.cjs.cjs" } }, "typesVersions": { @@ -93,9 +68,8 @@ ] } }, - "main": "./dist/index.cjs", - "types": "./dist/index.d.ts", - "module": "./dist/index.js", + "main": "dist/index.cjs.cjs", + "module": "dist/index.esm.js", "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" diff --git a/packages/data-prefetch/project.json b/packages/data-prefetch/project.json index 4b7d219ea69..5b06302fcf7 100644 --- a/packages/data-prefetch/project.json +++ b/packages/data-prefetch/project.json @@ -6,18 +6,20 @@ "tags": ["type:pkg"], "targets": { "build": { - "executor": "nx:run-commands", - "outputs": ["{workspaceRoot}/packages/data-prefetch/dist"], + "executor": "@nx/rollup:rollup", + "outputs": ["{options.outputPath}"], "options": { - "command": "rslib build", - "cwd": "packages/data-prefetch" - }, - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ] + "outputPath": "packages/data-prefetch/dist", + "main": "packages/data-prefetch/src/index.ts", + "tsConfig": "packages/data-prefetch/tsconfig.lib.json", + "assets": [], + "project": "packages/data-prefetch/package.json", + "rollupConfig": "packages/data-prefetch/rollup.config.cjs", + "compiler": "tsc", + "format": ["cjs", "esm"], + "generatePackageJson": false, + "useLegacyTypescriptPlugin": false + } }, "test": { "executor": "@nx/jest:jest", diff --git a/packages/data-prefetch/src/babel.ts b/packages/data-prefetch/src/babel.ts deleted file mode 100644 index f0422639299..00000000000 --- a/packages/data-prefetch/src/babel.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './cli/babel'; diff --git a/packages/data-prefetch/src/cli.ts b/packages/data-prefetch/src/cli.ts deleted file mode 100644 index d88a29a8d85..00000000000 --- a/packages/data-prefetch/src/cli.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './cli/index'; diff --git a/packages/data-prefetch/src/plugin.ts b/packages/data-prefetch/src/plugin.ts index 962fcf53663..0885ac992aa 100644 --- a/packages/data-prefetch/src/plugin.ts +++ b/packages/data-prefetch/src/plugin.ts @@ -1,4 +1,4 @@ -import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; import { ModuleInfo, getResourceUrl } from '@module-federation/sdk'; import { getSignalFromManifest } from './common/runtime-utils'; diff --git a/packages/data-prefetch/src/prefetch.ts b/packages/data-prefetch/src/prefetch.ts index 16c6dbb2a7f..11f2078ff04 100644 --- a/packages/data-prefetch/src/prefetch.ts +++ b/packages/data-prefetch/src/prefetch.ts @@ -22,9 +22,6 @@ interface FederationWithPrefetch extends Federation { } type PrefetchExports = Record; -type FederationGlobal = typeof globalThis & { - __FEDERATION__?: FederationWithPrefetch; -}; export interface DataPrefetchOptions { name: string; @@ -41,15 +38,14 @@ export interface prefetchOptions { } // @ts-ignore init global variable for test -const federationGlobal = globalThis as FederationGlobal; -federationGlobal.__FEDERATION__ ??= {} as FederationWithPrefetch; -federationGlobal.__FEDERATION__.__PREFETCH__ ??= { +globalThis.__FEDERATION__ ??= {}; +( + globalThis.__FEDERATION__ as unknown as FederationWithPrefetch +).__PREFETCH__ ??= { entryLoading: {}, instance: new Map(), __PREFETCH_EXPORTS__: {}, } as FederationWithPrefetch['__PREFETCH__']; -const getFederation = (): FederationWithPrefetch => - (globalThis as FederationGlobal).__FEDERATION__!; export class MFDataPrefetch { public prefetchMemory: Map>; public recordOutdate: Record>; @@ -65,11 +61,14 @@ export class MFDataPrefetch { } get global(): FederationWithPrefetch['__PREFETCH__'] { - return getFederation().__PREFETCH__; + return (globalThis.__FEDERATION__ as unknown as FederationWithPrefetch) + .__PREFETCH__; } static getInstance(id: string): MFDataPrefetch | undefined { - return getFederation().__PREFETCH__.instance.get(id); + return ( + globalThis.__FEDERATION__ as unknown as FederationWithPrefetch + ).__PREFETCH__.instance.get(id); } async loadEntry(entry: string | undefined): Promise { @@ -99,8 +98,9 @@ export class MFDataPrefetch { return this._exports; } const { name } = this._options; - const exportsPromiseFn = - getFederation().__PREFETCH__.__PREFETCH_EXPORTS__?.[name]; + const exportsPromiseFn = ( + globalThis.__FEDERATION__ as unknown as FederationWithPrefetch + ).__PREFETCH__.__PREFETCH_EXPORTS__?.[name]; const exportsPromise = typeof exportsPromiseFn === 'function' ? exportsPromiseFn() diff --git a/packages/data-prefetch/src/react.ts b/packages/data-prefetch/src/react.ts deleted file mode 100644 index f97dcae7962..00000000000 --- a/packages/data-prefetch/src/react.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './react/index'; diff --git a/packages/data-prefetch/src/universal.ts b/packages/data-prefetch/src/universal.ts deleted file mode 100644 index 82b5e38422c..00000000000 --- a/packages/data-prefetch/src/universal.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './universal/index'; diff --git a/packages/data-prefetch/tsconfig.lib.json b/packages/data-prefetch/tsconfig.lib.json index 4b75aca1f6b..33eca2c2cdf 100644 --- a/packages/data-prefetch/tsconfig.lib.json +++ b/packages/data-prefetch/tsconfig.lib.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "rootDir": "./src", + "outDir": "../../dist/out-tsc", "declaration": true, "types": ["node"] }, diff --git a/packages/dts-plugin/src/runtime-plugins/dynamic-remote-type-hints-plugin.ts b/packages/dts-plugin/src/runtime-plugins/dynamic-remote-type-hints-plugin.ts index da4b883effd..543d8e5aa9b 100644 --- a/packages/dts-plugin/src/runtime-plugins/dynamic-remote-type-hints-plugin.ts +++ b/packages/dts-plugin/src/runtime-plugins/dynamic-remote-type-hints-plugin.ts @@ -1,4 +1,4 @@ -import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; import { createWebsocket } from '../server/createWebsocket'; import { AddDynamicRemoteAction, diff --git a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts index 39b290c86a5..ce5233ef79c 100644 --- a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts @@ -34,98 +34,21 @@ const { mkdirpSync } = require( normalizeWebpackPath('webpack/lib/util/fs'), ) as typeof import('webpack/lib/util/fs'); -function resolveModule( - candidates: string[], - options?: NodeJS.RequireResolveOptions, -): string { - let lastError: unknown; - for (const candidate of candidates) { - try { - return require.resolve(candidate, options); - } catch (error) { - lastError = error; - } - } - - throw ( - lastError ?? - new Error(`Unable to resolve any module from: ${candidates.join(', ')}`) - ); -} - -function isEsmOutputBuild(compiler: Compiler): boolean { - if (compiler.options.experiments?.outputModule) { - return true; - } - - if (compiler.options.output?.module) { - return true; - } - - const library = compiler.options.output?.library; - if ( - library && - typeof library === 'object' && - !Array.isArray(library) && - 'type' in library - ) { - return library.type === 'module'; - } - - return false; -} - -function getModuleCandidates( - packageName: string, - preferEsm: boolean, -): string[] { - const preferred = preferEsm - ? [`${packageName}/dist/index.js`, `${packageName}/dist/index.cjs`] - : [`${packageName}/dist/index.cjs`, `${packageName}/dist/index.js`]; - - const legacyFallback = preferEsm - ? [`${packageName}/dist/index.esm.js`, `${packageName}/dist/index.cjs.cjs`] - : [`${packageName}/dist/index.cjs.cjs`, `${packageName}/dist/index.esm.js`]; - - // Keep legacy dist entry names as fallbacks for mixed plugin/runtime versions. - // Resolve package root last to avoid accidentally selecting a CJS export when - // an explicit ESM fallback file exists in legacy runtime layouts. - return [...preferred, ...legacyFallback, packageName]; -} - -function resolveRuntimePaths(preferEsm: boolean, implementation?: string) { - const runtimeToolsCandidates = getModuleCandidates( - '@module-federation/runtime-tools', - preferEsm, - ); - const bundlerRuntimeCandidates = getModuleCandidates( - '@module-federation/webpack-bundler-runtime', - preferEsm, - ); - const runtimeCandidates = getModuleCandidates( - '@module-federation/runtime', - preferEsm, - ); - - const runtimeToolsPath = resolveModule(runtimeToolsCandidates); - const modulePaths = implementation ? [implementation] : [runtimeToolsPath]; - - return { - runtimeToolsPath, - bundlerRuntimePath: resolveModule(bundlerRuntimeCandidates, { - paths: modulePaths, - }), - runtimePath: resolveModule(runtimeCandidates, { - paths: modulePaths, - }), - }; -} - -const { - runtimeToolsPath: RuntimeToolsPath, - bundlerRuntimePath: BundlerRuntimePath, - runtimePath: RuntimePath, -} = resolveRuntimePaths(true); +const RuntimeToolsPath = require.resolve( + '@module-federation/runtime-tools/dist/index.esm.js', +); +const BundlerRuntimePath = require.resolve( + '@module-federation/webpack-bundler-runtime/dist/index.esm.js', + { + paths: [RuntimeToolsPath], + }, +); +const RuntimePath = require.resolve( + '@module-federation/runtime/dist/index.esm.js', + { + paths: [RuntimeToolsPath], + }, +); const federationGlobal = getFederationGlobalScope(RuntimeGlobals); const onceForCompiler = new WeakSet(); @@ -135,16 +58,12 @@ class FederationRuntimePlugin { options?: moduleFederationPlugin.ModuleFederationPluginOptions; entryFilePath: string; bundlerRuntimePath: string; - runtimePath: string; - runtimeToolsPath: string; federationRuntimeDependency?: FederationRuntimeDependency; // Add this line constructor(options?: moduleFederationPlugin.ModuleFederationPluginOptions) { this.options = options ? { ...options } : undefined; this.entryFilePath = ''; this.bundlerRuntimePath = BundlerRuntimePath; - this.runtimePath = RuntimePath; - this.runtimeToolsPath = RuntimeToolsPath; this.federationRuntimeDependency = undefined; // Initialize as undefined } @@ -424,24 +343,23 @@ class FederationRuntimePlugin { getRuntimeAlias(compiler: Compiler) { const { implementation } = this.options || {}; + let runtimePath = RuntimePath; const alias: any = compiler.options.resolve.alias || {}; - const resolvedPaths = resolveRuntimePaths( - isEsmOutputBuild(compiler), - implementation, - ); - - this.runtimeToolsPath = resolvedPaths.runtimeToolsPath; - this.bundlerRuntimePath = resolvedPaths.bundlerRuntimePath; - if (alias['@module-federation/runtime$']) { - this.runtimePath = alias['@module-federation/runtime$']; - return this.runtimePath; + runtimePath = alias['@module-federation/runtime$']; + } else { + if (implementation) { + runtimePath = require.resolve( + `@module-federation/runtime/dist/index.esm.js`, + { + paths: [implementation], + }, + ); + } } - this.runtimePath = resolvedPaths.runtimePath; - - return this.runtimePath; + return runtimePath; } setRuntimeAlias(compiler: Compiler) { @@ -453,7 +371,7 @@ class FederationRuntimePlugin { alias['@module-federation/runtime-tools$'] = alias['@module-federation/runtime-tools$'] || implementation || - this.runtimeToolsPath; + RuntimeToolsPath; // Set up aliases for the federation runtime and tools // This ensures that the correct versions are used throughout the project @@ -512,13 +430,14 @@ class FederationRuntimePlugin { compiler.options.output.uniqueName || `container_${Date.now()}`; } - const resolvedPaths = resolveRuntimePaths( - isEsmOutputBuild(compiler), - this.options?.implementation, - ); - this.bundlerRuntimePath = resolvedPaths.bundlerRuntimePath; - this.runtimePath = resolvedPaths.runtimePath; - this.runtimeToolsPath = resolvedPaths.runtimeToolsPath; + if (this.options?.implementation) { + this.bundlerRuntimePath = require.resolve( + '@module-federation/webpack-bundler-runtime/dist/index.esm.js', + { + paths: [this.options.implementation], + }, + ); + } this.entryFilePath = this.getFilePath(compiler); diff --git a/packages/error-codes/package.json b/packages/error-codes/package.json index e1e5397cf54..a117d7b1217 100644 --- a/packages/error-codes/package.json +++ b/packages/error-codes/package.json @@ -22,22 +22,14 @@ "publishConfig": { "access": "public" }, - "browser": { - "url": false - }, - "main": "./dist/index.cjs", - "module": "./dist/index.mjs", + "main": "./dist/index.cjs.js", + "module": "./dist/index.esm.mjs", "types": "./dist/index.d.ts", "exports": { ".": { - "import": { - "types": "./dist/index.d.mts", - "default": "./dist/index.mjs" - }, - "require": { - "types": "./dist/index.d.ts", - "default": "./dist/index.cjs" - } + "types": "./dist/index.d.ts", + "import": "./dist/index.esm.mjs", + "require": "./dist/index.cjs.js" } }, "typesVersions": { diff --git a/packages/esbuild/rslib.config.ts b/packages/esbuild/rslib.config.ts index 904bbbfde82..8856f364aea 100644 --- a/packages/esbuild/rslib.config.ts +++ b/packages/esbuild/rslib.config.ts @@ -17,10 +17,8 @@ export default defineConfig({ { format: 'esm', syntax: 'es2021', - bundle: false, - outBase: 'src', + bundle: true, dts: { - bundle: false, distPath: './dist', }, }, @@ -28,19 +26,16 @@ export default defineConfig({ { format: 'cjs', syntax: 'es2021', - bundle: false, - outBase: 'src', + bundle: true, dts: false, // Only generate types once for ESM }, ], // Shared configurations source: { entry: { - index: [ - './src/**/*.{ts,tsx,js,jsx}', - '!./src/**/*.spec.*', - '!./src/**/*.test.*', - ], + index: './src/index.ts', + plugin: './src/adapters/lib/plugin.ts', + build: './src/build.ts', }, define: { __VERSION__: JSON.stringify(pkg.version), @@ -50,7 +45,6 @@ export default defineConfig({ }, output: { target: 'node', - minify: false, distPath: { root: './dist', }, diff --git a/packages/esbuild/src/plugin.ts b/packages/esbuild/src/plugin.ts deleted file mode 100644 index 85b1e1a9f79..00000000000 --- a/packages/esbuild/src/plugin.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './adapters/lib/plugin'; diff --git a/packages/managers/package.json b/packages/managers/package.json index 2ac510503b7..216b387c91f 100644 --- a/packages/managers/package.json +++ b/packages/managers/package.json @@ -6,7 +6,6 @@ "keywords": [ "Module Federation" ], - "type": "commonjs", "files": [ "dist/", "README.md" @@ -21,8 +20,8 @@ "directory": "packages/managers" }, "sideEffects": false, - "main": "./dist/index.js", - "module": "./dist/index.mjs", + "main": "./dist/index.cjs.js", + "module": "./dist/index.esm.js", "types": "./dist/index.d.ts", "dependencies": { "@module-federation/sdk": "workspace:*", @@ -34,14 +33,9 @@ }, "exports": { ".": { - "import": { - "types": "./dist/index.d.mts", - "default": "./dist/index.mjs" - }, - "require": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } + "types": "./dist/index.d.ts", + "import": "./dist/index.esm.js", + "require": "./dist/index.cjs.js" } }, "typesVersions": { diff --git a/packages/managers/project.json b/packages/managers/project.json index 22809a6d9ac..4cab86b2c77 100644 --- a/packages/managers/project.json +++ b/packages/managers/project.json @@ -6,18 +6,21 @@ "tags": ["type:pkg"], "targets": { "build": { - "executor": "nx:run-commands", - "outputs": ["{workspaceRoot}/packages/managers/dist"], + "executor": "@nx/rollup:rollup", + "outputs": ["{options.outputPath}"], "options": { - "command": "rslib build", - "cwd": "packages/managers" - }, - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ] + "outputPath": "packages/managers/dist", + "main": "packages/managers/src/index.ts", + "tsConfig": "packages/managers/tsconfig.lib.json", + "assets": [], + "external": ["@module-federation/*"], + "project": "packages/managers/package.json", + "rollupConfig": "packages/managers/rollup.config.js", + "compiler": "tsc", + "format": ["cjs", "esm"], + "generatePackageJson": false, + "useLegacyTypescriptPlugin": false + } }, "lint": { "executor": "@nx/eslint:lint", diff --git a/packages/managers/tsconfig.lib.json b/packages/managers/tsconfig.lib.json index 4b75aca1f6b..33eca2c2cdf 100644 --- a/packages/managers/tsconfig.lib.json +++ b/packages/managers/tsconfig.lib.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "rootDir": "./src", + "outDir": "../../dist/out-tsc", "declaration": true, "types": ["node"] }, diff --git a/packages/manifest/package.json b/packages/manifest/package.json index bf97ed289a2..0930af1b36d 100644 --- a/packages/manifest/package.json +++ b/packages/manifest/package.json @@ -23,8 +23,8 @@ "directory": "packages/manifest" }, "sideEffects": false, - "main": "./dist/index.js", - "module": "./dist/index.mjs", + "main": "./dist/index.cjs.js", + "module": "./dist/index.esm.js", "types": "./dist/index.d.ts", "dependencies": { "@module-federation/sdk": "workspace:*", @@ -35,14 +35,9 @@ }, "exports": { ".": { - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.mjs" - }, - "require": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } + "types": "./dist/index.d.ts", + "import": "./dist/index.esm.js", + "require": "./dist/index.cjs.js" } }, "typesVersions": { diff --git a/packages/manifest/project.json b/packages/manifest/project.json index 8854bb0f6de..afebc635601 100644 --- a/packages/manifest/project.json +++ b/packages/manifest/project.json @@ -6,18 +6,21 @@ "tags": ["type:pkg"], "targets": { "build": { - "executor": "nx:run-commands", - "outputs": ["{workspaceRoot}/packages/manifest/dist"], + "executor": "@nx/rollup:rollup", + "outputs": ["{options.outputPath}"], "options": { - "command": "rslib build", - "cwd": "packages/manifest" - }, - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ] + "outputPath": "packages/manifest/dist", + "main": "packages/manifest/src/index.ts", + "tsConfig": "packages/manifest/tsconfig.lib.json", + "assets": [], + "external": ["@module-federation/*"], + "project": "packages/manifest/package.json", + "rollupConfig": "packages/manifest/rollup.config.js", + "compiler": "tsc", + "format": ["cjs", "esm"], + "generatePackageJson": false, + "useLegacyTypescriptPlugin": false + } }, "lint": { "executor": "@nx/eslint:lint", diff --git a/packages/manifest/tsconfig.lib.json b/packages/manifest/tsconfig.lib.json index 4b75aca1f6b..33eca2c2cdf 100644 --- a/packages/manifest/tsconfig.lib.json +++ b/packages/manifest/tsconfig.lib.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "rootDir": "./src", + "outDir": "../../dist/out-tsc", "declaration": true, "types": ["node"] }, diff --git a/packages/metro-core/src/commands/bundle-host/index.ts b/packages/metro-core/src/commands/bundle-host/index.ts index d9db28d84e2..830a97835e3 100644 --- a/packages/metro-core/src/commands/bundle-host/index.ts +++ b/packages/metro-core/src/commands/bundle-host/index.ts @@ -1,9 +1,9 @@ import path from 'node:path'; import util from 'node:util'; +import Server from 'metro/src/Server'; +import type { RequestOptions } from 'metro/src/shared/types'; import type { ModuleFederationConfigNormalized } from '../../types'; import { CLIError } from '../../utils/errors'; -import type { RequestOptions } from '../../utils/metro-compat'; -import { Server } from '../../utils/metro-compat'; import type { Config } from '../types'; import { createResolver } from '../utils/create-resolver'; import { getCommunityCliPlugin } from '../utils/get-community-plugin'; diff --git a/packages/metro-core/src/commands/utils/create-resolver.ts b/packages/metro-core/src/commands/utils/create-resolver.ts index 232807d0fc2..f12574a2cad 100644 --- a/packages/metro-core/src/commands/utils/create-resolver.ts +++ b/packages/metro-core/src/commands/utils/create-resolver.ts @@ -1,4 +1,4 @@ -import type { Server } from '../../utils/metro-compat'; +import type Server from 'metro/src/Server'; /** * Creates a resolver utility that mirrors Metro's bundling resolution behavior. diff --git a/packages/metro-core/src/commands/utils/get-community-plugin.ts b/packages/metro-core/src/commands/utils/get-community-plugin.ts index 7df6e9c31fd..7a1e73be00a 100644 --- a/packages/metro-core/src/commands/utils/get-community-plugin.ts +++ b/packages/metro-core/src/commands/utils/get-community-plugin.ts @@ -1,10 +1,7 @@ import type { ConfigT } from 'metro-config'; +import type Server from 'metro/src/Server'; +import type { OutputOptions, RequestOptions } from 'metro/src/shared/types'; import { CLIError } from '../../utils/errors'; -import type { - OutputOptions, - RequestOptions, - Server, -} from '../../utils/metro-compat'; interface Command { name: string; @@ -38,14 +35,15 @@ interface CommunityCliPlugin { } export function getCommunityCliPlugin(reactNativePath?: string) { - let communityCliPluginPath: string; + let communityCliPlugin: CommunityCliPlugin; try { - communityCliPluginPath = require.resolve( + const communityCliPluginPath = require.resolve( '@react-native/community-cli-plugin', { paths: [reactNativePath ?? require.resolve('react-native')] }, ); + communityCliPlugin = require(communityCliPluginPath); } catch { throw new CLIError('Community CLI plugin is not installed.'); } - return require(communityCliPluginPath) as CommunityCliPlugin; + return communityCliPlugin; } diff --git a/packages/metro-core/src/commands/utils/save-bundle-and-map.ts b/packages/metro-core/src/commands/utils/save-bundle-and-map.ts index ea42161797b..c059a28bf83 100644 --- a/packages/metro-core/src/commands/utils/save-bundle-and-map.ts +++ b/packages/metro-core/src/commands/utils/save-bundle-and-map.ts @@ -1,8 +1,8 @@ import { promises as fs } from 'node:fs'; import util from 'node:util'; import type { MixedSourceMap } from 'metro-source-map'; -import type { OutputOptions } from '../../utils/metro-compat'; -import { relativizeSourceMapInline } from '../../utils/metro-compat'; +import relativizeSourceMapInline from 'metro/src/lib/relativizeSourceMap'; +import type { OutputOptions } from 'metro/src/shared/types'; function relativizeSerializedMap( map: string, diff --git a/packages/metro-core/src/types/metro/baseJSBundle.d.ts b/packages/metro-core/src/types/metro/baseJSBundle.d.ts index 4ed4414e466..3bbb9660be9 100644 --- a/packages/metro-core/src/types/metro/baseJSBundle.d.ts +++ b/packages/metro-core/src/types/metro/baseJSBundle.d.ts @@ -1,10 +1,10 @@ -declare module 'metro/src/DeltaBundler/Serializers/baseJSBundle' { - import type { - Module, - ReadOnlyGraph, - SerializerOptions, - } from 'metro/src/DeltaBundler/types'; +import type { + Module, + ReadOnlyGraph, + SerializerOptions, +} from 'metro/src/DeltaBundler/types'; +declare module 'metro/src/DeltaBundler/Serializers/baseJSBundle' { interface Bundle { modules: readonly [number, string][]; post: string; diff --git a/packages/metro-core/src/types/metro/getAppendScripts.d.ts b/packages/metro-core/src/types/metro/getAppendScripts.d.ts index a56aec403a0..1514bf44284 100644 --- a/packages/metro-core/src/types/metro/getAppendScripts.d.ts +++ b/packages/metro-core/src/types/metro/getAppendScripts.d.ts @@ -1,5 +1,6 @@ +import type { Module } from 'metro/src/DeltaBundler/types'; + declare module 'metro/src/lib/getAppendScripts' { - import type { Module } from 'metro/src/DeltaBundler/types'; interface Options { asyncRequireModulePath: string; createModuleId: (path: string) => T; diff --git a/packages/metro-core/src/types/metro/processModules.d.ts b/packages/metro-core/src/types/metro/processModules.d.ts index a7de84c74c1..5b414a46afe 100644 --- a/packages/metro-core/src/types/metro/processModules.d.ts +++ b/packages/metro-core/src/types/metro/processModules.d.ts @@ -1,5 +1,6 @@ +import type { Module } from 'metro/src/DeltaBundler/types'; + declare module 'metro/src/DeltaBundler/Serializers/helpers/processModules' { - import type { Module } from 'metro/src/DeltaBundler/types'; interface Options { filter?: (module: Module) => boolean; createModuleId: (path: string) => number; diff --git a/packages/metro-core/src/types/metro/relativizeSourceMap.d.ts b/packages/metro-core/src/types/metro/relativizeSourceMap.d.ts index 043eac5ad95..335944e4688 100644 --- a/packages/metro-core/src/types/metro/relativizeSourceMap.d.ts +++ b/packages/metro-core/src/types/metro/relativizeSourceMap.d.ts @@ -1,13 +1,8 @@ -declare module 'metro/src/lib/relativizeSourceMap' { - import type { MixedSourceMap } from 'metro-source-map'; +import type { MixedSourceMap } from 'metro-source-map'; +declare module 'metro/src/lib/relativizeSourceMap' { export default function relativizeSourceMap( sourceMap: MixedSourceMap, sourcesRoot: string, ): void; - - export function relativizeSourceMapInline( - sourceMap: MixedSourceMap, - sourcesRoot: string, - ): void; } diff --git a/packages/metro-core/src/utils/metro-compat.ts b/packages/metro-core/src/utils/metro-compat.ts index fe2e3e9d171..97f5ba71511 100644 --- a/packages/metro-core/src/utils/metro-compat.ts +++ b/packages/metro-core/src/utils/metro-compat.ts @@ -10,7 +10,7 @@ * installed Metro version, ensuring compatibility with both versions. */ -// Type-only imports — resolved at compile-time via metro/src/*.d.ts, erased at runtime +// Type-only imports - resolved at compile-time via metro/src/*.d.ts, erased at runtime import type DefaultServer from 'metro/src/Server'; import type DefaultBaseJSBundle from 'metro/src/DeltaBundler/Serializers/baseJSBundle'; import type DefaultCountingSet from 'metro/src/lib/CountingSet'; diff --git a/packages/metro-core/src/utils/vm-manager.ts b/packages/metro-core/src/utils/vm-manager.ts index 836d9ffb36c..7babfca544a 100644 --- a/packages/metro-core/src/utils/vm-manager.ts +++ b/packages/metro-core/src/utils/vm-manager.ts @@ -6,7 +6,7 @@ import type { TransformerConfigT, } from 'metro-config'; import type { FileSystem } from 'metro-file-map'; -import type { Server as MetroServer } from './metro-compat'; +import type MetroServer from 'metro/src/Server'; type EnhanceMiddleware = ServerConfigT['enhanceMiddleware']; type GetTransformOptions = TransformerConfigT['getTransformOptions']; diff --git a/packages/micro-effect/__tests__/micro-effect.spec.ts b/packages/micro-effect/__tests__/micro-effect.spec.ts new file mode 100644 index 00000000000..ab28eedbb95 --- /dev/null +++ b/packages/micro-effect/__tests__/micro-effect.spec.ts @@ -0,0 +1,338 @@ +import { Effect, TaggedError } from '../src/index'; + +describe('micro-effect', () => { + describe('Effect.succeed', () => { + it('resolves with the provided value', async () => { + const result = await Effect.runPromise(Effect.succeed(42)); + expect(result).toBe(42); + }); + }); + + describe('Effect.fail', () => { + it('rejects with the provided error', async () => { + await expect(Effect.runPromise(Effect.fail('boom'))).rejects.toBe('boom'); + }); + }); + + describe('Effect.sync', () => { + it('executes the thunk and returns its value', async () => { + const result = await Effect.runPromise(Effect.sync(() => 'hello')); + expect(result).toBe('hello'); + }); + }); + + describe('Effect.promise', () => { + it('awaits the async thunk', async () => { + const result = await Effect.runPromise( + Effect.promise(() => Promise.resolve('async-value')), + ); + expect(result).toBe('async-value'); + }); + }); + + describe('Effect.tryPromise', () => { + it('returns the value on success', async () => { + const result = await Effect.runPromise( + Effect.tryPromise({ + try: () => Promise.resolve(10), + catch: () => 'mapped-error', + }), + ); + expect(result).toBe(10); + }); + + it('maps the error on failure', async () => { + await expect( + Effect.runPromise( + Effect.tryPromise({ + try: () => Promise.reject(new Error('original')), + catch: (err) => `mapped: ${(err as Error).message}`, + }), + ), + ).rejects.toBe('mapped: original'); + }); + }); + + describe('Effect.gen', () => { + it('chains multiple yielded effects', async () => { + const result = await Effect.runPromise( + Effect.gen(function* () { + const a = yield* Effect.succeed(1); + const b = yield* Effect.succeed(2); + return a + b; + }), + ); + expect(result).toBe(3); + }); + + it('propagates errors from failed effects via catch', async () => { + const program = Effect.gen(function* () { + const a = yield* Effect.succeed(1); + const inner = Effect.tryPromise({ + try: () => Promise.reject(new Error('async-fail')), + catch: (err) => (err as Error).message, + }).pipe(Effect.catch((err) => Effect.succeed(`recovered: ${err}`))); + const b = yield* inner; + return `${a}-${b}`; + }); + const result = await Effect.runPromise(program); + expect(result).toBe('1-recovered: async-fail'); + }); + }); + + describe('Effect.catchTag', () => { + it('catches errors with matching _tag (data-last via pipe)', async () => { + const MyError = TaggedError('MyError'); + const program = Effect.fail(new MyError()).pipe( + Effect.catchTag('MyError', () => Effect.succeed('recovered')), + ); + const result = await Effect.runPromise(program); + expect(result).toBe('recovered'); + }); + + it('rethrows errors with non-matching _tag', async () => { + const MyError = TaggedError('MyError'); + const program = Effect.fail(new MyError()).pipe( + Effect.catchTag('OtherError', () => Effect.succeed('recovered')), + ); + await expect(Effect.runPromise(program)).rejects.toBeInstanceOf(Error); + }); + }); + + describe('Effect.catch', () => { + it('catches all errors (data-last via pipe)', async () => { + const program = Effect.fail('any-error').pipe( + Effect.catch(() => Effect.succeed('caught')), + ); + const result = await Effect.runPromise(program); + expect(result).toBe('caught'); + }); + + it('works in data-first form', async () => { + const result = await Effect.runPromise( + Effect.catch(Effect.fail('err'), () => Effect.succeed('caught-first')), + ); + expect(result).toBe('caught-first'); + }); + }); + + describe('Effect.runPromise', () => { + it('returns a Promise', () => { + const result = Effect.runPromise(Effect.succeed(1)); + expect(result).toBeInstanceOf(Promise); + }); + }); + + describe('TaggedError', () => { + it('creates an error with _tag and extends Error', () => { + const MyErr = TaggedError('MyErr'); + const err = new MyErr(); + expect(err).toBeInstanceOf(Error); + expect(err._tag).toBe('MyErr'); + expect(err.name).toBe('MyErr'); + }); + + it('assigns constructor properties', () => { + const MyErr = TaggedError('MyErr'); + const err = new MyErr({ code: 42 }); + expect((err as any).code).toBe(42); + expect(err._tag).toBe('MyErr'); + }); + + it('is yieldable in gen via Symbol.iterator and catchable via catchTag', async () => { + const MyErr = TaggedError('MyErr'); + const program = Effect.gen(function* () { + const inner = Effect.fail(new MyErr({ code: 99 })).pipe( + Effect.catchTag('MyErr', (err) => + Effect.succeed(`caught: ${(err as any).code}`), + ), + ); + return yield* inner; + }); + const result = await Effect.runPromise(program); + expect(result).toBe('caught: 99'); + }); + }); + + describe('pipe', () => { + it('chains multiple operations', async () => { + const result = await Effect.runPromise( + Effect.succeed(5).pipe( + (eff) => + Effect.gen(function* () { + const val = yield* eff; + return val * 2; + }), + (eff) => + Effect.gen(function* () { + const val = yield* eff; + return val + 1; + }), + ), + ); + expect(result).toBe(11); + }); + }); + + describe('yield* protocol', () => { + it('single-shot iterator works correctly', async () => { + const eff = Effect.succeed(99); + const result = await Effect.runPromise( + Effect.gen(function* () { + const val = yield* eff; + return val; + }), + ); + expect(result).toBe(99); + }); + }); + + describe('Effect.forEach', () => { + it('processes items in parallel by default', async () => { + const result = await Effect.runPromise( + Effect.forEach([1, 2, 3], (item) => Effect.succeed(item * 2)), + ); + expect(result).toEqual([2, 4, 6]); + }); + + it('processes items in parallel explicitly', async () => { + const result = await Effect.runPromise( + Effect.forEach( + [10, 20, 30], + (item, index) => Effect.succeed(item + index), + { concurrency: 'parallel' }, + ), + ); + expect(result).toEqual([10, 21, 32]); + }); + + it('processes items sequentially', async () => { + const order: number[] = []; + const result = await Effect.runPromise( + Effect.forEach( + [1, 2, 3], + (item) => + Effect.promise(async () => { + order.push(item); + return item * 10; + }), + { concurrency: 'sequential' }, + ), + ); + expect(result).toEqual([10, 20, 30]); + expect(order).toEqual([1, 2, 3]); + }); + + it('propagates errors', async () => { + const MyErr = TaggedError('MyErr'); + await expect( + Effect.runPromise( + Effect.forEach([1, 2, 3], (item) => { + if (item === 2) return Effect.fail(new MyErr()); + return Effect.succeed(item); + }), + ), + ).rejects.toBeInstanceOf(Error); + }); + + it('works with empty array', async () => { + const result = await Effect.runPromise( + Effect.forEach([], (item: number) => Effect.succeed(item)), + ); + expect(result).toEqual([]); + }); + }); + + describe('Effect.tap', () => { + it('executes side-effect and returns original value (data-first)', async () => { + let sideEffect = 0; + const result = await Effect.runPromise( + Effect.tap(Effect.succeed(42), (val) => + Effect.sync(() => { + sideEffect = val; + }), + ), + ); + expect(result).toBe(42); + expect(sideEffect).toBe(42); + }); + + it('executes side-effect and returns original value (data-last via pipe)', async () => { + let sideEffect = ''; + const result = await Effect.runPromise( + Effect.succeed('hello').pipe( + Effect.tap((val) => + Effect.sync(() => { + sideEffect = val; + }), + ), + ), + ); + expect(result).toBe('hello'); + expect(sideEffect).toBe('hello'); + }); + + it('propagates errors from the tap function', async () => { + await expect( + Effect.runPromise( + Effect.tap(Effect.succeed(1), () => Effect.fail('tap-error')), + ), + ).rejects.toBe('tap-error'); + }); + }); + + describe('Effect.all', () => { + it('runs all effects in parallel and collects results', async () => { + const result = await Effect.runPromise( + Effect.all([ + Effect.succeed(1), + Effect.succeed('two'), + Effect.succeed(true), + ]), + ); + expect(result).toEqual([1, 'two', true]); + }); + + it('propagates errors', async () => { + await expect( + Effect.runPromise( + Effect.all([ + Effect.succeed(1), + Effect.fail('boom'), + Effect.succeed(3), + ]), + ), + ).rejects.toBe('boom'); + }); + + it('works with empty array', async () => { + const result = await Effect.runPromise(Effect.all([])); + expect(result).toEqual([]); + }); + }); + + describe('Effect.trySync', () => { + it('returns value on success', async () => { + const result = await Effect.runPromise( + Effect.trySync({ + try: () => 42, + catch: () => 'error', + }), + ); + expect(result).toBe(42); + }); + + it('maps error on failure', async () => { + await expect( + Effect.runPromise( + Effect.trySync({ + try: () => { + throw new Error('original'); + }, + catch: (err) => `mapped: ${(err as Error).message}`, + }), + ), + ).rejects.toBe('mapped: original'); + }); + }); +}); diff --git a/packages/micro-effect/package.json b/packages/micro-effect/package.json new file mode 100644 index 00000000000..2fe30f65888 --- /dev/null +++ b/packages/micro-effect/package.json @@ -0,0 +1,43 @@ +{ + "name": "@module-federation/micro-effect", + "description": "Micro Effect runtime for Module Federation - lightweight drop-in replacement for effect-smol", + "author": "zhanghang ", + "public": true, + "sideEffects": false, + "version": "0.23.0", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/module-federation/core.git", + "directory": "packages/micro-effect" + }, + "keywords": [ + "Module Federation", + "effect", + "micro-effect" + ], + "files": [ + "dist/", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "main": "./dist/index.cjs.js", + "module": "./dist/index.esm.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.esm.mjs", + "require": "./dist/index.cjs.js" + } + }, + "typesVersions": { + "*": { + ".": [ + "./dist/index.d.ts" + ] + } + } +} diff --git a/packages/micro-effect/project.json b/packages/micro-effect/project.json new file mode 100644 index 00000000000..5ee320b1b0f --- /dev/null +++ b/packages/micro-effect/project.json @@ -0,0 +1,54 @@ +{ + "name": "micro-effect", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/micro-effect/src", + "projectType": "library", + "tags": ["type:pkg"], + "targets": { + "build": { + "executor": "@nx/rollup:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "parallel": false, + "outputPath": "packages/micro-effect/dist", + "main": "packages/micro-effect/src/index.ts", + "tsConfig": "packages/micro-effect/tsconfig.lib.json", + "rollupConfig": "packages/micro-effect/rollup.config.js", + "assets": [], + "project": "packages/micro-effect/package.json", + "compiler": "tsc", + "format": ["cjs", "esm"], + "generatePackageJson": false, + "useLegacyTypescriptPlugin": false + }, + "dependsOn": [ + { + "target": "build", + "dependencies": true + } + ] + }, + "test": { + "executor": "nx:run-commands", + "options": { + "parallel": false, + "commands": [ + { + "command": "vitest run -u -c packages/micro-effect/vitest.config.ts", + "forwardAllArgs": false + } + ] + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "packages/micro-effect/**/*.ts", + "packages/micro-effect/package.json" + ] + } + } + } +} diff --git a/packages/micro-effect/rollup.config.js b/packages/micro-effect/rollup.config.js new file mode 100644 index 00000000000..c3d8cd253c9 --- /dev/null +++ b/packages/micro-effect/rollup.config.js @@ -0,0 +1,47 @@ +module.exports = (rollupConfig, projectOptions) => { + const adjustSourceMapPath = (relativePath) => { + const normalized = relativePath.replace(/\\/g, '/'); + if (normalized.startsWith('../../src/')) { + return normalized.replace('../../src/', '../src/'); + } + return normalized; + }; + + if (Array.isArray(rollupConfig.output)) { + rollupConfig.output = rollupConfig.output.map((c) => ({ + ...c, + sourcemap: true, + sourcemapPathTransform: adjustSourceMapPath, + hoistTransitiveImports: false, + entryFileNames: + c.format === 'esm' + ? c.entryFileNames.replace('.js', '.mjs') + : c.entryFileNames, + chunkFileNames: + c.format === 'esm' + ? c.chunkFileNames.replace('.js', '.mjs') + : c.chunkFileNames, + ...(c.format === 'cjs' ? { externalLiveBindings: false } : {}), + })); + } else { + rollupConfig.output = { + ...rollupConfig.output, + sourcemap: true, + sourcemapPathTransform: adjustSourceMapPath, + hoistTransitiveImports: false, + entryFileNames: + rollupConfig.output.format === 'esm' + ? rollupConfig.output.entryFileNames.replace('.js', '.mjs') + : rollupConfig.output.entryFileNames, + chunkFileNames: + rollupConfig.output.format === 'esm' + ? rollupConfig.output.chunkFileNames.replace('.js', '.mjs') + : rollupConfig.output.chunkFileNames, + ...(rollupConfig.output.format === 'cjs' + ? { externalLiveBindings: false } + : {}), + }; + } + + return rollupConfig; +}; diff --git a/packages/micro-effect/src/index.ts b/packages/micro-effect/src/index.ts new file mode 100644 index 00000000000..4dff4ef1fda --- /dev/null +++ b/packages/micro-effect/src/index.ts @@ -0,0 +1,390 @@ +// Micro Effect runtime for Module Federation +// Drop-in replacement for effect-smol with minimal footprint (~2KB minified) + +// ---- Internal variant types ---- + +interface Succeed { + readonly _tag: 'Succeed'; + readonly value: A; +} +interface Fail { + readonly _tag: 'Fail'; + readonly error: E; +} +interface Sync { + readonly _tag: 'Sync'; + readonly thunk: () => A; +} +interface EffPromise { + readonly _tag: 'Promise'; + readonly thunk: () => Promise; +} +interface TryPromise { + readonly _tag: 'TryPromise'; + readonly tryFn: () => Promise; + readonly catchFn: (error: unknown) => E; +} +interface Gen { + readonly _tag: 'Gen'; + readonly body: () => Generator, A, any>; +} +interface CatchTag { + readonly _tag: 'CatchTag'; + readonly self: MFEffect; + readonly targetTag: string; + readonly handler: (err: any) => MFEffect; +} +interface Catch { + readonly _tag: 'Catch'; + readonly self: MFEffect; + readonly handler: (err: any) => MFEffect; +} + +interface ForEach { + readonly _tag: 'ForEach'; + readonly items: readonly any[]; + readonly fn: (item: any, index: number) => MFEffect; + readonly concurrency: 'parallel' | 'sequential'; +} +interface Tap { + readonly _tag: 'Tap'; + readonly self: MFEffect; + readonly fn: (value: any) => MFEffect; +} +interface All { + readonly _tag: 'All'; + readonly effects: readonly MFEffect[]; +} +interface TrySync { + readonly _tag: 'TrySync'; + readonly tryFn: () => A; + readonly catchFn: (error: unknown) => E; +} + +type EffectVariant = + | Succeed + | Fail + | Sync + | EffPromise + | TryPromise + | Gen + | CatchTag + | Catch + | ForEach + | Tap + | All + | TrySync; + +// ---- Prototype for pipe + iterator ---- + +const EffectProto = { + pipe(this: any, ...fns: Array<(a: any) => any>): any { + let result: any = this; + for (let i = 0; i < fns.length; i++) { + result = fns[i](result); + } + return result; + }, + [Symbol.iterator](this: any): Iterator { + let done = false; + const self = this; + return { + next(value?: any) { + if (done) { + return { value, done: true }; + } + done = true; + return { value: self, done: false }; + }, + throw(error?: any) { + throw error; + }, + return(value?: any) { + done = true; + return { value, done: true }; + }, + } as Iterator; + }, +}; + +// ---- MFEffect type (opaque) ---- + +interface MFEffect { + readonly _tag: string; + pipe(ab: (a: this) => B): B; + pipe(ab: (a: this) => B, bc: (b: B) => C): C; + pipe(ab: (a: this) => B, bc: (b: B) => C, cd: (c: C) => D): D; + [Symbol.iterator](): Iterator; +} + +function makeEffect(variant: EffectVariant): MFEffect { + const eff = Object.create(EffectProto); + Object.assign(eff, variant); + return eff; +} + +// ---- Constructors ---- + +function succeed(value: A): MFEffect { + return makeEffect({ _tag: 'Succeed', value }); +} + +function fail(error: E): MFEffect { + return makeEffect({ _tag: 'Fail', error }); +} + +function sync(thunk: () => A): MFEffect { + return makeEffect({ _tag: 'Sync', thunk }); +} + +function promise(thunk: () => Promise): MFEffect { + return makeEffect({ _tag: 'Promise', thunk }); +} + +function tryPromise(options: { + try: () => Promise; + catch: (error: unknown) => E; +}): MFEffect { + return makeEffect({ + _tag: 'TryPromise', + tryFn: options.try, + catchFn: options.catch, + }); +} + +function gen( + body: () => Generator, A, any>, +): MFEffect { + return makeEffect({ _tag: 'Gen', body } as any); +} + +function forEach( + items: readonly A[], + fn: (item: A, index: number) => MFEffect, + options?: { concurrency?: 'parallel' | 'sequential' }, +): MFEffect { + return makeEffect({ + _tag: 'ForEach', + items, + fn, + concurrency: options?.concurrency ?? 'parallel', + } as any); +} + +function tap( + self: MFEffect, + fn: (a: A) => MFEffect, +): MFEffect; +function tap( + fn: (a: A) => MFEffect, +): (self: MFEffect) => MFEffect; +function tap(...args: any[]): any { + if ( + args.length === 2 && + args[0] && + typeof args[0] === 'object' && + 'pipe' in args[0] + ) { + const [self, fn] = args; + return makeEffect({ _tag: 'Tap', self, fn }); + } + const [fn] = args; + return (self: any) => makeEffect({ _tag: 'Tap', self, fn }); +} + +function all[]>( + effects: [...Effects], +): MFEffect< + { + [K in keyof Effects]: Effects[K] extends MFEffect ? A : never; + }, + any +> { + return makeEffect({ _tag: 'All', effects } as any); +} + +function trySync(options: { + try: () => A; + catch: (error: unknown) => E; +}): MFEffect { + return makeEffect({ + _tag: 'TrySync', + tryFn: options.try, + catchFn: options.catch, + } as any); +} + +// ---- Interpreter ---- + +async function interpret(effect: MFEffect): Promise { + const e = effect as any; + switch (e._tag) { + case 'Succeed': + return e.value; + case 'Fail': + throw e.error; + case 'Sync': + return e.thunk(); + case 'Promise': + return await e.thunk(); + case 'TryPromise': + try { + return await e.tryFn(); + } catch (err) { + throw e.catchFn(err); + } + case 'Gen': { + const iter = e.body(); + let result = iter.next(); + while (!result.done) { + try { + const resolved = await interpret(result.value); + result = iter.next(resolved); + } catch (err) { + result = iter.throw(err); + } + } + return result.value; + } + case 'CatchTag': + try { + return await interpret(e.self); + } catch (err: any) { + if ( + err && + typeof err === 'object' && + '_tag' in err && + err._tag === e.targetTag + ) { + return await interpret(e.handler(err)); + } + throw err; + } + case 'Catch': + try { + return await interpret(e.self); + } catch (err) { + return await interpret(e.handler(err)); + } + case 'ForEach': { + if (e.concurrency === 'parallel') { + return (await Promise.all( + e.items.map((item: any, i: number) => interpret(e.fn(item, i))), + )) as A; + } + const results = []; + for (let i = 0; i < e.items.length; i++) { + results.push(await interpret(e.fn(e.items[i], i))); + } + return results as A; + } + case 'Tap': { + const value = await interpret(e.self); + await interpret(e.fn(value)); + return value as A; + } + case 'All': + return (await Promise.all( + e.effects.map((eff: any) => interpret(eff)), + )) as A; + case 'TrySync': + try { + return e.tryFn() as A; + } catch (err) { + throw e.catchFn(err); + } + default: + throw new Error(`Unknown effect tag: ${e._tag}`); + } +} + +// ---- Error recovery operators (dual-form) ---- + +function catchTag( + self: MFEffect, + tag: Tag, + handler: (err: Extract) => MFEffect, +): MFEffect | E2>; +function catchTag( + tag: Tag, + handler: (err: Extract) => MFEffect, +): ( + self: MFEffect, +) => MFEffect | E2>; +function catchTag(...args: any[]): any { + if (args.length === 3) { + const [self, tag, handler] = args; + return makeEffect({ _tag: 'CatchTag', self, targetTag: tag, handler }); + } + const [tag, handler] = args; + return (self: any) => + makeEffect({ _tag: 'CatchTag', self, targetTag: tag, handler }); +} + +function catch_( + self: MFEffect, + handler: (err: E) => MFEffect, +): MFEffect; +function catch_( + handler: (err: E) => MFEffect, +): (self: MFEffect) => MFEffect; +function catch_(...args: any[]): any { + if ( + args.length === 2 && + args[0] && + typeof args[0] === 'object' && + 'pipe' in args[0] + ) { + const [self, handler] = args; + return makeEffect({ _tag: 'Catch', self, handler }); + } + const [handler] = args; + return (self: any) => makeEffect({ _tag: 'Catch', self, handler }); +} + +// ---- Run ---- + +function runPromise(effect: MFEffect): Promise { + return interpret(effect); +} + +// ---- TaggedError factory ---- + +export function TaggedError( + tag: Tag, +): new (args?: any) => Error & { readonly _tag: Tag } { + class TaggedErr extends Error { + readonly _tag = tag; + constructor(args?: any) { + super(tag); + this.name = tag; + if (args) Object.assign(this, args); + } + [Symbol.iterator]() { + return fail(this)[Symbol.iterator](); + } + } + return TaggedErr as any; +} + +// ---- Public namespace (drop-in compatible) ---- + +export const Effect = { + gen, + succeed, + fail, + sync, + promise, + tryPromise, + catchTag, + catch: catch_, + runPromise, + forEach, + tap, + all, + trySync, +}; + +export namespace Effect { + export type Effect = MFEffect; +} diff --git a/packages/micro-effect/tsconfig.json b/packages/micro-effect/tsconfig.json new file mode 100644 index 00000000000..77e68ddc837 --- /dev/null +++ b/packages/micro-effect/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "esModuleInterop": true, + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "declaration": true, + "noImplicitReturns": false + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/cli/tsconfig.lib.json b/packages/micro-effect/tsconfig.lib.json similarity index 54% rename from packages/cli/tsconfig.lib.json rename to packages/micro-effect/tsconfig.lib.json index 046990a94a9..33eca2c2cdf 100644 --- a/packages/cli/tsconfig.lib.json +++ b/packages/micro-effect/tsconfig.lib.json @@ -1,8 +1,10 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "rootDir": "./src" + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] }, - "include": ["src"], + "include": ["src/**/*.ts"], "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] } diff --git a/packages/micro-effect/vitest.config.ts b/packages/micro-effect/vitest.config.ts new file mode 100644 index 00000000000..46af40a8a51 --- /dev/null +++ b/packages/micro-effect/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + plugins: [], + test: { + environment: 'node', + include: [path.resolve(__dirname, '__tests__/*.spec.ts')], + globals: true, + testTimeout: 10000, + }, +}); diff --git a/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts b/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts index 3eeddcfe3fd..e9bbabd699e 100644 --- a/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts +++ b/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts @@ -1,4 +1,4 @@ -import { ModuleFederationRuntimePlugin } from '@module-federation/runtime'; +import { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; export default function (): ModuleFederationRuntimePlugin { return { diff --git a/packages/retry-plugin/src/index.ts b/packages/retry-plugin/src/index.ts index 1a5c9851a81..8137fa5199f 100644 --- a/packages/retry-plugin/src/index.ts +++ b/packages/retry-plugin/src/index.ts @@ -1,4 +1,4 @@ -import { ModuleFederationRuntimePlugin } from '@module-federation/runtime'; +import { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; import type { CommonRetryOptions } from './types'; import { fetchRetry } from './fetch-retry'; import { scriptRetry } from './script-retry'; diff --git a/packages/rsbuild-plugin/rslib.config.ts b/packages/rsbuild-plugin/rslib.config.ts index 3af93270640..3ff9e349157 100644 --- a/packages/rsbuild-plugin/rslib.config.ts +++ b/packages/rsbuild-plugin/rslib.config.ts @@ -7,34 +7,28 @@ export default defineConfig({ { format: 'esm', syntax: 'es2021', - bundle: false, - outBase: 'src', + bundle: true, dts: { - bundle: false, distPath: './dist', }, }, { format: 'cjs', syntax: 'es2021', - bundle: false, - outBase: 'src', + bundle: true, dts: false, }, ], source: { entry: { - index: [ - './src/**/*.{ts,tsx,js,jsx}', - '!./src/**/*.spec.*', - '!./src/**/*.test.*', - ], + index: './src/cli/index.ts', + utils: './src/utils/index.ts', + constant: './src/constant.ts', }, tsconfigPath: './tsconfig.lib.json', }, output: { target: 'node', - minify: false, distPath: { root: './dist', }, diff --git a/packages/rsbuild-plugin/src/cli/index.ts b/packages/rsbuild-plugin/src/cli/index.ts index 5b6184fa2da..094358485f4 100644 --- a/packages/rsbuild-plugin/src/cli/index.ts +++ b/packages/rsbuild-plugin/src/cli/index.ts @@ -27,7 +27,6 @@ import { CALL_NAME_MAP, RSPRESS_BUNDLER_CONFIG_NAME, RSPRESS_SSR_DIR, - RSPRESS_SSG_MD_ENV_NAME, } from '../constant'; import { patchNodeConfig, patchToolsTspack } from '../utils/ssr'; @@ -129,9 +128,7 @@ const isSSRConfig = (bundlerConfigName?: string) => Boolean(bundlerConfigName === SSR_ENV_NAME); const isRspressSSGConfig = (bundlerConfigName?: string) => { - return [RSPRESS_BUNDLER_CONFIG_NAME, RSPRESS_SSG_MD_ENV_NAME].includes( - bundlerConfigName || '', - ); + return bundlerConfigName === RSPRESS_BUNDLER_CONFIG_NAME; }; export const pluginModuleFederation = ( @@ -181,11 +178,10 @@ export const pluginModuleFederation = ( const rsbuildConfig = api.getRsbuildConfig(); if ( - !isRspress && - (!rsbuildConfig.environments?.[environment] || - Object.keys(rsbuildConfig.environments).some( - (key) => key.startsWith(environment) && key !== environment, - )) + !rsbuildConfig.environments?.[environment] || + Object.keys(rsbuildConfig.environments).some( + (key) => key.startsWith(environment) && key !== environment, + ) ) { throw new Error( `Please set ${RSBUILD_PLUGIN_NAME} as global plugin in rslib.config.ts if you set 'target: "dual"'.`, @@ -281,12 +277,6 @@ export const pluginModuleFederation = ( config, callerName, ); - const ssgMDEnv = config.environments![RSPRESS_SSG_MD_ENV_NAME]; - if (isRspress && ssgMDEnv) { - patchToolsTspack(ssgMDEnv, (config, { environment }) => { - config.target = 'async-node'; - }); - } } else if (target === 'node') { const nodeTargetEnv = config.environments?.[environment]; if (!nodeTargetEnv) { @@ -575,12 +565,11 @@ export const pluginModuleFederation = ( bundlerConfig.output.publicPath = '/'; // MF depend on asyncChunks bundlerConfig.output.asyncChunks = undefined; - const p = new ModuleFederationPlugin(mfConfig); - if (bundlerConfig.name === RSPRESS_BUNDLER_CONFIG_NAME) { - generateMergedStatsAndManifestOptions.options.rspressSSGPlugin = - p; - } - bundlerConfig.plugins!.push(p); + generateMergedStatsAndManifestOptions.options.rspressSSGPlugin = + new ModuleFederationPlugin(mfConfig); + bundlerConfig.plugins!.push( + generateMergedStatsAndManifestOptions.options.rspressSSGPlugin, + ); return; } diff --git a/packages/rsbuild-plugin/src/constant.ts b/packages/rsbuild-plugin/src/constant.ts index 9f57688eb53..b981d2d3aa8 100644 --- a/packages/rsbuild-plugin/src/constant.ts +++ b/packages/rsbuild-plugin/src/constant.ts @@ -6,5 +6,4 @@ export const CALL_NAME_MAP = { RSLIB: 'rslib', }; export const RSPRESS_BUNDLER_CONFIG_NAME = 'node'; -export const RSPRESS_SSG_MD_ENV_NAME = 'node_md'; export const RSPRESS_SSR_DIR = 'ssr'; diff --git a/packages/rsbuild-plugin/src/index.ts b/packages/rsbuild-plugin/src/index.ts deleted file mode 100644 index d88a29a8d85..00000000000 --- a/packages/rsbuild-plugin/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './cli/index'; diff --git a/packages/rsbuild-plugin/src/utils.ts b/packages/rsbuild-plugin/src/utils.ts deleted file mode 100644 index c6cc6044786..00000000000 --- a/packages/rsbuild-plugin/src/utils.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './utils/index'; diff --git a/packages/rspack/package.json b/packages/rspack/package.json index 023a79f9003..ea95e09272a 100644 --- a/packages/rspack/package.json +++ b/packages/rspack/package.json @@ -20,8 +20,8 @@ }, "author": "hanric ", "sideEffects": false, - "main": "./dist/index.js", - "module": "./dist/index.mjs", + "main": "./dist/index.cjs.js", + "module": "./dist/index.esm.mjs", "types": "./dist/index.d.ts", "dependencies": { "btoa": "1.2.1", @@ -38,34 +38,19 @@ }, "exports": { ".": { - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.mjs" - }, - "require": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } + "types": "./dist/index.d.ts", + "import": "./dist/index.esm.mjs", + "require": "./dist/index.cjs.js" }, "./plugin": { - "import": { - "types": "./dist/plugin.d.ts", - "default": "./dist/plugin.mjs" - }, - "require": { - "types": "./dist/plugin.d.ts", - "default": "./dist/plugin.js" - } + "types": "./dist/plugin.d.ts", + "import": "./dist/plugin.esm.mjs", + "require": "./dist/plugin.cjs.js" }, "./remote-entry-plugin": { - "import": { - "types": "./dist/remote-entry-plugin.d.ts", - "default": "./dist/remote-entry-plugin.mjs" - }, - "require": { - "types": "./dist/remote-entry-plugin.d.ts", - "default": "./dist/remote-entry-plugin.js" - } + "types": "./dist/remote-entry-plugin.d.ts", + "import": "./dist/remote-entry-plugin.esm.mjs", + "require": "./dist/remote-entry-plugin.cjs.js" } }, "typesVersions": { diff --git a/packages/rspack/project.json b/packages/rspack/project.json index c55ab82f718..1ad51e58f8b 100644 --- a/packages/rspack/project.json +++ b/packages/rspack/project.json @@ -6,18 +6,21 @@ "tags": ["type:pkg"], "targets": { "build": { - "executor": "nx:run-commands", - "outputs": ["{workspaceRoot}/packages/rspack/dist"], + "executor": "@nx/rollup:rollup", + "outputs": ["{options.outputPath}"], "options": { - "command": "rslib build", - "cwd": "packages/rspack" - }, - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ] + "outputPath": "packages/rspack/dist", + "main": "packages/rspack/src/index.ts", + "tsConfig": "packages/rspack/tsconfig.lib.json", + "assets": [], + "external": ["@module-federation/*", "@rspack/core"], + "project": "packages/rspack/package.json", + "rollupConfig": "packages/rspack/rollup.config.js", + "compiler": "tsc", + "format": ["cjs", "esm"], + "generatePackageJson": false, + "useLegacyTypescriptPlugin": false + } }, "lint": { "executor": "@nx/eslint:lint", diff --git a/packages/rspack/src/ModuleFederationPlugin.ts b/packages/rspack/src/ModuleFederationPlugin.ts index 159529f4775..4ffa55fc6ae 100644 --- a/packages/rspack/src/ModuleFederationPlugin.ts +++ b/packages/rspack/src/ModuleFederationPlugin.ts @@ -136,8 +136,7 @@ export class ModuleFederationPlugin implements RspackPluginInstance { }).apply(compiler); } - const implementationPath = options.implementation || RuntimeToolsPath; - options.implementation = implementationPath; + options.implementation = options.implementation || RuntimeToolsPath; let disableManifest = options.manifest === false; let disableDts = options.dts === false; @@ -163,28 +162,10 @@ export class ModuleFederationPlugin implements RspackPluginInstance { options as unknown as ModuleFederationPluginOptions, ).apply(compiler); - const resolveRuntimePath = (candidates: string[]) => { - for (const candidate of candidates) { - try { - return require.resolve(candidate, { - paths: [implementationPath], - }); - } catch {} - } - throw new Error( - `[ ModuleFederationPlugin ]: Unable to resolve runtime entry from ${candidates.join( - ', ', - )}`, - ); - }; - - const runtimeESMPath = resolveRuntimePath([ - '@module-federation/runtime/dist/index.js', + const runtimeESMPath = require.resolve( '@module-federation/runtime/dist/index.esm.js', - '@module-federation/runtime/dist/index.cjs', - '@module-federation/runtime/dist/index.cjs.cjs', - '@module-federation/runtime', - ]); + { paths: [options.implementation] }, + ); compiler.hooks.afterPlugins.tap('PatchAliasWebpackPlugin', () => { compiler.options.resolve.alias = { diff --git a/packages/rspack/src/plugin.ts b/packages/rspack/src/plugin.ts deleted file mode 100644 index 20599e736b3..00000000000 --- a/packages/rspack/src/plugin.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ModuleFederationPlugin'; diff --git a/packages/rspack/src/remote-entry-plugin.ts b/packages/rspack/src/remote-entry-plugin.ts deleted file mode 100644 index 9e1685a395e..00000000000 --- a/packages/rspack/src/remote-entry-plugin.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './RemoteEntryPlugin'; diff --git a/packages/rspack/tsconfig.lib.json b/packages/rspack/tsconfig.lib.json index 4b75aca1f6b..33eca2c2cdf 100644 --- a/packages/rspack/tsconfig.lib.json +++ b/packages/rspack/tsconfig.lib.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "rootDir": "./src", + "outDir": "../../dist/out-tsc", "declaration": true, "types": ["node"] }, diff --git a/packages/rspress-plugin/package.json b/packages/rspress-plugin/package.json index 6a483e7350f..f84b656f01b 100644 --- a/packages/rspress-plugin/package.json +++ b/packages/rspress-plugin/package.json @@ -35,7 +35,7 @@ }, "devDependencies": { "@rslib/core": "^0.9.2", - "@rspress/core": "2.0.3", + "@rspress/core": "2.0.1", "@types/html-to-text": "^9.0.4", "@types/lodash-es": "^4.17.12", "@types/react": "^18.3.11" @@ -49,9 +49,9 @@ "@module-federation/enhanced": "workspace:*", "@module-federation/rsbuild-plugin": "workspace:*", "@module-federation/error-codes": "workspace:*", - "@rspress/shared": "2.0.3" + "@rspress/shared": "2.0.1" }, "peerDependencies": { - "@rspress/core": "^2.0.3" + "@rspress/core": "^2.0.1" } } diff --git a/packages/rspress-plugin/src/plugin.ts b/packages/rspress-plugin/src/plugin.ts index 25d01fa13c0..15135114a67 100644 --- a/packages/rspress-plugin/src/plugin.ts +++ b/packages/rspress-plugin/src/plugin.ts @@ -47,10 +47,18 @@ export function pluginModuleFederation( singleton: true, requiredVersion: false, }, - 'react-router-dom': { - singleton: true, - requiredVersion: false, - }, + // '@rspress/core/shiki-transformers': { + // singleton: true, + // requiredVersion: false, + // }, + // '@rspress/core/theme': { + // singleton: true, + // requiredVersion: false, + // }, + // '@rspress/core/theme-original': { + // singleton: true, + // requiredVersion: false, + // }, ...mfConfig.shared, }; } diff --git a/packages/runtime-core/__tests__/no-effect-dependency.spec.ts b/packages/runtime-core/__tests__/no-effect-dependency.spec.ts new file mode 100644 index 00000000000..14bc166a706 --- /dev/null +++ b/packages/runtime-core/__tests__/no-effect-dependency.spec.ts @@ -0,0 +1,10 @@ +import pkg from '../package.json'; + +describe('no-effect-dependency', () => { + it('should not depend on effect or effect-smol', () => { + const deps = { ...pkg.dependencies, ...(pkg as any).devDependencies }; + expect(deps['effect']).toBeUndefined(); + expect(deps['effect-smol']).toBeUndefined(); + expect(deps['@effect/platform']).toBeUndefined(); + }); +}); diff --git a/packages/runtime-core/__tests__/semver.spec.ts b/packages/runtime-core/__tests__/semver.spec.ts index 8bd41e475df..7b15b6475a0 100644 --- a/packages/runtime-core/__tests__/semver.spec.ts +++ b/packages/runtime-core/__tests__/semver.spec.ts @@ -1,6 +1,6 @@ // Test cases for semver ranges taken from https://devhints.io/semver import { describe, expect, test } from 'vitest'; -import { satisfy } from '../src/utils/semver'; +import { satisfy } from '../src'; const version = '1.2.3'; const belowVersion = '1.2.2'; diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json index fba90a6c2eb..3da133cb018 100644 --- a/packages/runtime-core/package.json +++ b/packages/runtime-core/package.json @@ -53,6 +53,7 @@ }, "dependencies": { "@module-federation/sdk": "workspace:*", - "@module-federation/error-codes": "workspace:*" + "@module-federation/error-codes": "workspace:*", + "@module-federation/micro-effect": "workspace:*" } } diff --git a/packages/runtime-core/src/core.ts b/packages/runtime-core/src/core.ts index 411dbefce01..1b518df0ec8 100644 --- a/packages/runtime-core/src/core.ts +++ b/packages/runtime-core/src/core.ts @@ -39,17 +39,13 @@ import { DEFAULT_SCOPE } from './constant'; import { SnapshotHandler } from './plugins/snapshot/SnapshotHandler'; import { SharedHandler } from './shared'; import { RemoteHandler } from './remote'; -import { formatShareConfigs } from './utils/share'; - -// Declare the global constant that will be defined by DefinePlugin -// Default to true if not defined (e.g., when runtime-core is used outside of webpack) -// so that snapshot functionality is included by default. +import { formatShareConfigs } from './shared'; +// Snapshot enabled unless explicitly disabled by DefinePlugin. declare const FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN: boolean; const USE_SNAPSHOT = typeof FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN === 'boolean' ? !FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN - : true; // Default to true (use snapshot) when not explicitly defined - + : true; export class ModuleFederation { options: Options; hooks = new PluginSystem({ @@ -57,21 +53,12 @@ export class ModuleFederation { userOptions: UserOptions; options: Options; origin: ModuleFederation; - /** - * @deprecated shareInfo will be removed soon, please use userOptions directly! - */ shareInfo: ShareInfos; }>('beforeInit'), init: new SyncHook< - [ - { - options: Options; - origin: ModuleFederation; - }, - ], + [{ options: Options; origin: ModuleFederation }], void >(), - // maybe will change, temporarily for internal use only beforeInitContainer: new AsyncWaterfallHook<{ shareScope: ShareScopeMap[string]; initScope: InitScope; @@ -79,7 +66,6 @@ export class ModuleFederation { remoteInfo: RemoteInfo; origin: ModuleFederation; }>('beforeInitContainer'), - // maybe will change, temporarily for internal use only initContainer: new AsyncWaterfallHook<{ shareScope: ShareScopeMap[string]; initScope: InitScope; @@ -99,32 +85,16 @@ export class ModuleFederation { remoteHandler: RemoteHandler; shareScopeMap: ShareScopeMap; loaderHook = new PluginSystem({ - // FIXME: may not be suitable , not open to the public yet getModuleInfo: new SyncHook< - [ - { - target: Record; - key: any; - }, - ], + [{ target: Record; key: any }], { value: any | undefined; key: string } | void >(), createScript: new SyncHook< - [ - { - url: string; - attrs?: Record; - }, - ], + [{ url: string; attrs?: Record }], CreateScriptHookReturn >(), createLink: new SyncHook< - [ - { - url: string; - attrs?: Record; - }, - ], + [{ url: string; attrs?: Record }], HTMLLinkElement | void >(), fetch: new AsyncHook< @@ -177,7 +147,6 @@ export class ModuleFederation { >(), }); moduleInfo?: GlobalModuleInfo[string]; - constructor(userOptions: UserOptions) { const plugins = USE_SNAPSHOT ? [snapshotPlugin(), generatePreloadAssetsPlugin()] @@ -192,7 +161,6 @@ export class ModuleFederation { shared: {}, inBrowser: isBrowserEnv(), }; - this.name = userOptions.name; this.options = defaultOptions; this.snapshotHandler = new SnapshotHandler(this); @@ -205,19 +173,15 @@ export class ModuleFederation { ]); this.options = this.formatOptions(defaultOptions, userOptions); } - initOptions(userOptions: UserOptions): Options { if (userOptions.name && userOptions.name !== this.options.name) { error(getShortErrorMsg(RUNTIME_010, runtimeDescMap)); } this.registerPlugins(userOptions.plugins); const options = this.formatOptions(this.options, userOptions); - this.options = options; - return options; } - async loadShare( pkgName: string, extraOptions?: { @@ -227,7 +191,6 @@ export class ModuleFederation { ): Promise T | undefined)> { return this.sharedHandler.loadShare(pkgName, extraOptions); } - // The lib function will only be available if the shared set by eager or runtime init is set or the shared is successfully loaded. // 1. If the loaded shared already exists globally, then it will be reused // 2. If lib exists in local shared, it will be used directly @@ -242,7 +205,6 @@ export class ModuleFederation { ): () => T | never { return this.sharedHandler.loadShareSync(pkgName, extraOptions); } - initializeSharing( shareScopeName = DEFAULT_SCOPE, extraOptions?: { @@ -253,7 +215,6 @@ export class ModuleFederation { ): Array> { return this.sharedHandler.initializeSharing(shareScopeName, extraOptions); } - initRawContainer( name: string, url: string, @@ -261,13 +222,10 @@ export class ModuleFederation { ): Module { const remoteInfo = getRemoteInfo({ name, entry: url }); const module = new Module({ host: this, remoteInfo }); - module.remoteEntryExports = container; this.moduleCache.set(name, module); - return module; } - // eslint-disable-next-line max-lines-per-function // eslint-disable-next-line @typescript-eslint/member-ordering async loadRemote( @@ -276,12 +234,10 @@ export class ModuleFederation { ): Promise { return this.remoteHandler.loadRemote(id, options); } - // eslint-disable-next-line @typescript-eslint/member-ordering async preloadRemote(preloadOptions: Array): Promise { return this.remoteHandler.preloadRemote(preloadOptions); } - initShareScopeMap( scopeName: string, shareScope: ShareScopeMap[string], @@ -289,7 +245,6 @@ export class ModuleFederation { ): void { this.sharedHandler.initShareScopeMap(scopeName, shareScope, extraOptions); } - formatOptions(globalOptions: Options, userOptions: UserOptions): Options { const { allShareInfos: shared } = formatShareConfigs( globalOptions, @@ -302,27 +257,18 @@ export class ModuleFederation { options: globalOptions, shareInfo: shared, }); - const remotes = this.remoteHandler.formatAndRegisterRemote( globalOptionsRes, userOptionsRes, ); - const { allShareInfos } = this.sharedHandler.registerShared( globalOptionsRes, userOptionsRes, ); - const plugins = [...globalOptionsRes.plugins]; - - if (userOptionsRes.plugins) { - userOptionsRes.plugins.forEach((plugin) => { - if (!plugins.includes(plugin)) { - plugins.push(plugin); - } - }); + for (const p of userOptionsRes.plugins || []) { + if (!plugins.includes(p)) plugins.push(p); } - const optionsRes: Options = { ...globalOptions, ...userOptions, @@ -330,29 +276,23 @@ export class ModuleFederation { remotes, shared: allShareInfos, }; - this.hooks.lifecycle.init.emit({ origin: this, options: optionsRes, }); return optionsRes; } - registerPlugins(plugins: UserOptions['plugins']) { - const pluginRes = registerPlugins(plugins, this); - // Merge plugin - this.options.plugins = this.options.plugins.reduce((res, plugin) => { - if (!plugin) return res; - if (res && !res.find((item) => item.name === plugin.name)) { - res.push(plugin); - } - return res; - }, pluginRes || []); + const base: Options['plugins'] = registerPlugins(plugins, this) || []; + for (const plugin of this.options.plugins) { + if (plugin && !base.find((item) => item.name === plugin.name)) + base.push(plugin); + } + this.options.plugins = base; } registerRemotes(remotes: Remote[], options?: { force?: boolean }): void { return this.remoteHandler.registerRemotes(remotes, options); } - registerShared(shared: UserOptions['shared']) { this.sharedHandler.registerShared(this.options, { ...this.options, diff --git a/packages/runtime-core/src/effect/errors.ts b/packages/runtime-core/src/effect/errors.ts new file mode 100644 index 00000000000..5ee999f0a0c --- /dev/null +++ b/packages/runtime-core/src/effect/errors.ts @@ -0,0 +1,64 @@ +import { TaggedError } from '@module-federation/micro-effect'; + +// RUNTIME_001 — Remote entry not found in global scope after script load +export interface RemoteEntryNotFound { + readonly remoteName: string; + readonly remoteEntryUrl: string; + readonly remoteEntryKey: string; +} +export class RemoteEntryNotFound extends TaggedError('RemoteEntryNotFound') {} + +// RUNTIME_002 — Remote entry has no init function +export interface RemoteEntryNoInit { + readonly hostName: string; + readonly remoteName: string; + readonly remoteEntryUrl: string; + readonly remoteEntryKey: string; +} +export class RemoteEntryNoInit extends TaggedError('RemoteEntryNoInit') {} + +// RUNTIME_003 — Manifest load failed +export interface ManifestLoadFailed { + readonly hostName: string; + readonly remoteUrl: string; +} +export class ManifestLoadFailed extends TaggedError('ManifestLoadFailed') {} + +// RUNTIME_004 — Remote not found in host remotes config +export interface RemoteNotFound { + readonly hostName: string; + readonly requestId: string; +} +export class RemoteNotFound extends TaggedError('RemoteNotFound') {} + +// RUNTIME_005, RUNTIME_006 — Share sync invalid (build vs runtime) +export interface ShareSyncInvalid { + readonly hostName: string; + readonly sharedPkgName: string; + readonly from: 'build' | 'runtime'; +} +export class ShareSyncInvalid extends TaggedError('ShareSyncInvalid') {} + +// RUNTIME_007 — Snapshot load failed +export interface SnapshotLoadFailed { + readonly hostName: string; + readonly remoteUrl: string; +} +export class SnapshotLoadFailed extends TaggedError('SnapshotLoadFailed') {} + +// RUNTIME_008 — Script/entry load failed +export interface ScriptLoadFailed { + readonly remoteName: string; + readonly resourceUrl: string; +} +export class ScriptLoadFailed extends TaggedError('ScriptLoadFailed') {} + +// Union type for all runtime errors +export type RuntimeError = + | RemoteEntryNotFound + | RemoteEntryNoInit + | ManifestLoadFailed + | RemoteNotFound + | ShareSyncInvalid + | SnapshotLoadFailed + | ScriptLoadFailed; diff --git a/packages/runtime-core/src/effect/index.ts b/packages/runtime-core/src/effect/index.ts new file mode 100644 index 00000000000..f72bc43e28c --- /dev/null +++ b/packages/runtime-core/src/effect/index.ts @@ -0,0 +1 @@ +export * from './errors'; diff --git a/packages/runtime-core/src/global.ts b/packages/runtime-core/src/global.ts index d55534192f3..101e8fae99d 100644 --- a/packages/runtime-core/src/global.ts +++ b/packages/runtime-core/src/global.ts @@ -49,41 +49,22 @@ declare global { >; } -function definePropertyGlobalVal( - target: typeof CurrentGlobal, - key: string, - val: any, -) { - Object.defineProperty(target, key, { - value: val, - configurable: false, - writable: true, - }); -} - -function includeOwnProperty(target: typeof CurrentGlobal, key: string) { - return Object.hasOwnProperty.call(target, key); -} - // This section is to prevent encapsulation by certain microfrontend frameworks. Due to reuse policies, sandbox escapes. // The sandbox in the microfrontend does not replicate the value of 'configurable'. -// If there is no loading content on the global object, this section defines the loading object. -if (!includeOwnProperty(CurrentGlobal, '__GLOBAL_LOADING_REMOTE_ENTRY__')) { - definePropertyGlobalVal(CurrentGlobal, '__GLOBAL_LOADING_REMOTE_ENTRY__', {}); -} - -export const globalLoading = CurrentGlobal.__GLOBAL_LOADING_REMOTE_ENTRY__; - -function setGlobalDefaultVal(target: typeof CurrentGlobal) { - if ( - includeOwnProperty(target, '__VMOK__') && - !includeOwnProperty(target, '__FEDERATION__') - ) { - definePropertyGlobalVal(target, '__FEDERATION__', target.__VMOK__); - } - - if (!includeOwnProperty(target, '__FEDERATION__')) { - definePropertyGlobalVal(target, '__FEDERATION__', { +function initGlobalFederation(target: typeof CurrentGlobal) { + const has = (k: string) => Object.hasOwnProperty.call(target, k); + const def = (k: string, v: any) => + Object.defineProperty(target, k, { + value: v, + configurable: false, + writable: true, + }); + if (!has('__GLOBAL_LOADING_REMOTE_ENTRY__')) + def('__GLOBAL_LOADING_REMOTE_ENTRY__', {}); + if (has('__VMOK__') && !has('__FEDERATION__')) + def('__FEDERATION__', target.__VMOK__); + if (!has('__FEDERATION__')) { + def('__FEDERATION__', { __GLOBAL_PLUGIN__: [], __INSTANCES__: [], moduleInfo: {}, @@ -91,20 +72,20 @@ function setGlobalDefaultVal(target: typeof CurrentGlobal) { __MANIFEST_LOADING__: {}, __PRELOADED_MAP__: new Map(), }); - - definePropertyGlobalVal(target, '__VMOK__', target.__FEDERATION__); + def('__VMOK__', target.__FEDERATION__); } - - target.__FEDERATION__.__GLOBAL_PLUGIN__ ??= []; - target.__FEDERATION__.__INSTANCES__ ??= []; - target.__FEDERATION__.moduleInfo ??= {}; - target.__FEDERATION__.__SHARE__ ??= {}; - target.__FEDERATION__.__MANIFEST_LOADING__ ??= {}; - target.__FEDERATION__.__PRELOADED_MAP__ ??= new Map(); + const f = target.__FEDERATION__; + f.__GLOBAL_PLUGIN__ ??= []; + f.__INSTANCES__ ??= []; + f.moduleInfo ??= {}; + f.__SHARE__ ??= {}; + f.__MANIFEST_LOADING__ ??= {}; + f.__PRELOADED_MAP__ ??= new Map(); } -setGlobalDefaultVal(CurrentGlobal); -setGlobalDefaultVal(nativeGlobal); +[CurrentGlobal, nativeGlobal].forEach(initGlobalFederation); + +export const globalLoading = CurrentGlobal.__GLOBAL_LOADING_REMOTE_ENTRY__; export function resetFederationGlobalInfo(): void { CurrentGlobal.__FEDERATION__.__GLOBAL_PLUGIN__ = []; @@ -145,34 +126,13 @@ export function getInfoWithoutType( target: T, key: keyof T, ): { value: T[keyof T] | undefined; key: string } { - if (typeof key === 'string') { - const keyRes = target[key]; - if (keyRes) { - return { - value: target[key], - key: key as string, - }; - } else { - const targetKeys = Object.keys(target); - for (const targetKey of targetKeys) { - const [targetTypeOrName, _] = targetKey.split(':'); - const nKey = `${targetTypeOrName}:${key}` as unknown as keyof T; - const typeWithKeyRes = target[nKey]; - if (typeWithKeyRes) { - return { - value: typeWithKeyRes, - key: nKey as string, - }; - } - } - return { - value: undefined, - key: key as string, - }; - } - } else { - throw new Error('key must be string'); + if (typeof key !== 'string') throw new Error('key must be string'); + if (target[key]) return { value: target[key], key: key as string }; + for (const tk of Object.keys(target)) { + const nKey = `${tk.split(':')[0]}:${key}` as unknown as keyof T; + if (target[nKey]) return { value: target[nKey], key: nKey as string }; } + return { value: undefined, key: key as string }; } export const getGlobalSnapshot = (): GlobalModuleInfo => @@ -182,39 +142,28 @@ export const getTargetSnapshotInfoByModuleInfo = ( moduleInfo: Optional, snapshot: GlobalModuleInfo, ): GlobalModuleInfo[string] | undefined => { - // Check if the remote is included in the hostSnapshot const moduleKey = getFMId(moduleInfo); - const getModuleInfo = getInfoWithoutType(snapshot, moduleKey).value; + const found = getInfoWithoutType(snapshot, moduleKey).value; - // The remoteSnapshot might not include a version - if ( - getModuleInfo && - !getModuleInfo.version && - 'version' in moduleInfo && - moduleInfo['version'] - ) { - getModuleInfo.version = moduleInfo['version']; - } - - if (getModuleInfo) { - return getModuleInfo; + if (found) { + if (!found.version && 'version' in moduleInfo && moduleInfo['version']) { + found.version = moduleInfo['version']; + } + return found; } - // If the remote is not included in the hostSnapshot, deploy a micro app snapshot + // Fallback: try without version in the key if ('version' in moduleInfo && moduleInfo['version']) { const { version, ...resModuleInfo } = moduleInfo; - const moduleKeyWithoutVersion = getFMId(resModuleInfo); - const getModuleInfoWithoutVersion = getInfoWithoutType( + const fallback = getInfoWithoutType( nativeGlobal.__FEDERATION__.moduleInfo, - moduleKeyWithoutVersion, + getFMId(resModuleInfo), ).value; - - if (getModuleInfoWithoutVersion?.version === version) { - return getModuleInfoWithoutVersion; + if (fallback?.version === version) { + return fallback; } } - - return; + return undefined; }; export const getGlobalSnapshotInfoByModuleInfo = ( diff --git a/packages/runtime-core/src/helpers.ts b/packages/runtime-core/src/helpers.ts index e8fc734ae03..c1b495f2e21 100644 --- a/packages/runtime-core/src/helpers.ts +++ b/packages/runtime-core/src/helpers.ts @@ -17,39 +17,11 @@ import { setPreloaded, Global, } from './global'; -import { getRegisteredShare, getGlobalShareScope } from './utils/share'; +import { getRegisteredShare, getGlobalShareScope } from './shared'; import { getRemoteInfo, matchRemoteWithNameAndExpose } from './utils'; import { preloadAssets } from './utils/preload'; -interface IShareUtils { - getRegisteredShare: typeof getRegisteredShare; - getGlobalShareScope: typeof getGlobalShareScope; -} -const ShareUtils: IShareUtils = { - getRegisteredShare, - getGlobalShareScope, -}; -interface IGlobalUtils { - Global: typeof Global; - nativeGlobal: typeof global; - resetFederationGlobalInfo: typeof resetFederationGlobalInfo; - setGlobalFederationInstance: typeof setGlobalFederationInstance; - getGlobalFederationConstructor: typeof getGlobalFederationConstructor; - setGlobalFederationConstructor: typeof setGlobalFederationConstructor; - getInfoWithoutType: typeof getInfoWithoutType; - getGlobalSnapshot: typeof getGlobalSnapshot; - getTargetSnapshotInfoByModuleInfo: typeof getTargetSnapshotInfoByModuleInfo; - getGlobalSnapshotInfoByModuleInfo: typeof getGlobalSnapshotInfoByModuleInfo; - setGlobalSnapshotInfoByModuleInfo: typeof setGlobalSnapshotInfoByModuleInfo; - addGlobalSnapshot: typeof addGlobalSnapshot; - getRemoteEntryExports: typeof getRemoteEntryExports; - registerGlobalPlugins: typeof registerGlobalPlugins; - getGlobalHostPlugins: typeof getGlobalHostPlugins; - getPreloaded: typeof getPreloaded; - setPreloaded: typeof setPreloaded; -} - -const GlobalUtils: IGlobalUtils = { +const GlobalUtils = { Global, nativeGlobal, resetFederationGlobalInfo, @@ -69,6 +41,14 @@ const GlobalUtils: IGlobalUtils = { setPreloaded, }; +const ShareUtils = { + getRegisteredShare, + getGlobalShareScope, +}; + +export type IGlobalUtils = typeof GlobalUtils; +export type IShareUtils = typeof ShareUtils; + export default { global: GlobalUtils, share: ShareUtils, @@ -78,5 +58,3 @@ export default { getRemoteInfo, }, }; - -export type { IGlobalUtils, IShareUtils }; diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 91a52537c49..43b62b2f08b 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -25,10 +25,10 @@ export { matchRemoteWithNameAndExpose, safeWrapper, } from './utils'; -export { getRegisteredShare } from '../src/utils/share'; +export { getRegisteredShare } from './shared'; export { loadScript, loadScriptNode } from '@module-federation/sdk'; export { Module } from './module'; export * as types from './type'; export { helpers }; -export { satisfy } from '../src/utils/semver'; +export { satisfy } from '@module-federation/sdk'; export type { IGlobalUtils, IShareUtils }; diff --git a/packages/runtime-core/src/module/index.ts b/packages/runtime-core/src/module/index.ts index 43a2895c39b..5c264944317 100644 --- a/packages/runtime-core/src/module/index.ts +++ b/packages/runtime-core/src/module/index.ts @@ -1,66 +1,11 @@ -import { getFMId, assert, error, processModuleAlias } from '../utils'; -import { safeToString, ModuleInfo } from '@module-federation/sdk'; -import { - getShortErrorMsg, - RUNTIME_002, - RUNTIME_008, - runtimeDescMap, -} from '@module-federation/error-codes'; -import { getRemoteEntry } from '../utils/load'; +import { ModuleInfo } from '@module-federation/sdk'; import { ModuleFederation } from '../core'; -import { - RemoteEntryExports, - RemoteInfo, - InitScope, - ShareScopeMap, -} from '../type'; +import { RemoteEntryExports, RemoteInfo } from '../type'; +import { Effect } from '@module-federation/micro-effect'; export type ModuleOptions = ConstructorParameters[0]; -export function createRemoteEntryInitOptions( - remoteInfo: RemoteInfo, - hostShareScopeMap: ShareScopeMap, - rawInitScope?: InitScope, -): Record { - const localShareScopeMap = hostShareScopeMap; - - const shareScopeKeys = Array.isArray(remoteInfo.shareScope) - ? remoteInfo.shareScope - : [remoteInfo.shareScope]; - - if (!shareScopeKeys.length) { - shareScopeKeys.push('default'); - } - shareScopeKeys.forEach((shareScopeKey) => { - if (!localShareScopeMap[shareScopeKey]) { - localShareScopeMap[shareScopeKey] = {}; - } - }); - - const remoteEntryInitOptions = { - version: remoteInfo.version || '', - shareScopeKeys: Array.isArray(remoteInfo.shareScope) - ? shareScopeKeys - : remoteInfo.shareScope || 'default', - }; - - // Help to find host instance - Object.defineProperty(remoteEntryInitOptions, 'shareScopeMap', { - value: localShareScopeMap, - // remoteEntryInitOptions will be traversed and assigned during container init, ,so this attribute is not allowed to be traversed - enumerable: false, - }); - - // TODO: compate legacy init params, should use shareScopeMap if exist - const shareScope = localShareScopeMap[shareScopeKeys[0]]; - const initScope: InitScope = rawInitScope ?? []; - - return { - remoteEntryInitOptions, - shareScope, - initScope, - }; -} +const MF_MODULE_ID = Symbol.for('mf_module_id'); class Module { remoteInfo: RemoteInfo; @@ -83,173 +28,58 @@ class Module { } async getEntry(): Promise { - if (this.remoteEntryExports) { - return this.remoteEntryExports; - } - - const remoteEntryExports = await getRemoteEntry({ - origin: this.host, - remoteInfo: this.remoteInfo, - remoteEntryExports: this.remoteEntryExports, - }); - - assert( - remoteEntryExports, - `remoteEntryExports is undefined \n ${safeToString(this.remoteInfo)}`, - ); - - this.remoteEntryExports = remoteEntryExports; - return this.remoteEntryExports; + return Effect.runPromise(this.host.remoteHandler._ensureEntry(this)); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - - async init( - id?: string, - remoteSnapshot?: ModuleInfo, - rawInitScope?: InitScope, - ) { - // Get remoteEntry.js - const remoteEntryExports = await this.getEntry(); - - if (this.inited) { - return remoteEntryExports; - } - - if (this.initPromise) { - await this.initPromise; - return remoteEntryExports; - } - - this.initing = true; - this.initPromise = (async () => { - const { remoteEntryInitOptions, shareScope, initScope } = - createRemoteEntryInitOptions( - this.remoteInfo, - this.host.shareScopeMap, - rawInitScope, - ); - - const initContainerOptions = - await this.host.hooks.lifecycle.beforeInitContainer.emit({ - shareScope, - // @ts-ignore shareScopeMap will be set by Object.defineProperty - remoteEntryInitOptions, - initScope, - remoteInfo: this.remoteInfo, - origin: this.host, - }); - - if (typeof remoteEntryExports?.init === 'undefined') { - error( - getShortErrorMsg(RUNTIME_002, runtimeDescMap, { - hostName: this.host.name, - remoteName: this.remoteInfo.name, - remoteEntryUrl: this.remoteInfo.entry, - remoteEntryKey: this.remoteInfo.entryGlobalName, - }), - ); - } - - await remoteEntryExports.init( - initContainerOptions.shareScope, - initContainerOptions.initScope, - initContainerOptions.remoteEntryInitOptions, - ); - - await this.host.hooks.lifecycle.initContainer.emit({ - ...initContainerOptions, + async init(id?: string, remoteSnapshot?: ModuleInfo) { + return Effect.runPromise( + this.host.remoteHandler._ensureEntry(this, { + init: true, id, remoteSnapshot, - remoteEntryExports, - }); - this.inited = true; - })(); - - try { - await this.initPromise; - } finally { - this.initing = false; - this.initPromise = undefined; - } - - return remoteEntryExports; + }), + ); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types async get( id: string, expose: string, options?: { loadFactory?: boolean }, remoteSnapshot?: ModuleInfo, ) { - const { loadFactory = true } = options || { loadFactory: true }; - - const remoteEntryExports = await this.init(id, remoteSnapshot); - this.lib = remoteEntryExports; - - let moduleFactory; - moduleFactory = await this.host.loaderHook.lifecycle.getModuleFactory.emit({ - remoteEntryExports, - expose, - moduleInfo: this.remoteInfo, - }); - - // get exposeGetter - if (!moduleFactory) { - moduleFactory = await remoteEntryExports.get(expose); - } - - assert( - moduleFactory, - `${getFMId(this.remoteInfo)} remote don't export ${expose}.`, + return Effect.runPromise( + this.host.remoteHandler._getModule( + this, + id, + expose, + options, + remoteSnapshot, + ), ); - - // keep symbol for module name always one format - const symbolName = processModuleAlias(this.remoteInfo.name, expose); - const wrapModuleFactory = this.wraperFactory(moduleFactory, symbolName); - - if (!loadFactory) { - return wrapModuleFactory; - } - const exposeContent = await wrapModuleFactory(); - - return exposeContent; } - private wraperFactory( - moduleFactory: () => any | (() => Promise), - id: string, - ) { - function defineModuleId(res: any, id: string) { + wraperFactory(moduleFactory: any, id: string) { + const tag = (res: any) => { if ( res && typeof res === 'object' && Object.isExtensible(res) && - !Object.getOwnPropertyDescriptor(res, Symbol.for('mf_module_id')) + !Object.getOwnPropertyDescriptor(res, MF_MODULE_ID) ) { - Object.defineProperty(res, Symbol.for('mf_module_id'), { + Object.defineProperty(res, MF_MODULE_ID, { value: id, enumerable: false, }); } - } - + return res; + }; if (moduleFactory instanceof Promise) { return async () => { - const res = await moduleFactory(); - // This parameter is used for bridge debugging - defineModuleId(res, id); - return res; - }; - } else { - return () => { - const res = moduleFactory(); - // This parameter is used for bridge debugging - defineModuleId(res, id); - return res; + const factory = await moduleFactory; + return tag(factory()); }; } + return () => tag(moduleFactory()); } } diff --git a/packages/runtime-core/src/plugins/generate-preload-assets.ts b/packages/runtime-core/src/plugins/generate-preload-assets.ts index 4ac4ee56b0c..e2c971fc164 100644 --- a/packages/runtime-core/src/plugins/generate-preload-assets.ts +++ b/packages/runtime-core/src/plugins/generate-preload-assets.ts @@ -1,7 +1,6 @@ import { GlobalModuleInfo, ModuleInfo, - ProviderModuleInfo, isManifestProvider, getResourceUrl, isBrowserEnv, @@ -13,14 +12,13 @@ import { PreloadConfig, PreloadOptions, RemoteInfoOptionalVersion, - Shared, Remote, } from '../type'; import { assignRemoteInfo } from './snapshot'; import { getInfoWithoutType, getPreloaded, setPreloaded } from '../global'; import { ModuleFederation } from '../core'; import { defaultPreloadArgs, normalizePreloadExposes } from '../utils/preload'; -import { getRegisteredShare } from '../utils/share'; +import { getRegisteredShare } from '../shared'; import { arrayOptions, getFMId, @@ -28,86 +26,192 @@ import { isPureRemoteEntry, isRemoteInfoWithEntry, } from '../utils'; - declare global { // eslint-disable-next-line no-var var __INIT_VMOK_DEPLOY_GLOBAL_DATA__: boolean | undefined; } - -// name -// name:version -function splitId(id: string): { - name: string; - version: string | undefined; -} { - const splitInfo = id.split(':'); - if (splitInfo.length === 1) { - return { - name: splitInfo[0], - version: undefined, - }; - } else if (splitInfo.length === 2) { - return { - name: splitInfo[0], - version: splitInfo[1], - }; - } else { - return { - name: splitInfo[1], - version: splitInfo[2], - }; - } -} - -// Traverse all nodes in moduleInfo and traverse the entire snapshot -function traverseModuleInfo( +type SnapshotVisitor = ( + snapshotInfo: ModuleInfo, + remoteInfo: RemoteInfoOptionalVersion, + isRoot: boolean, +) => void; +function walkSnapshot( globalSnapshot: GlobalModuleInfo, remoteInfo: RemoteInfoOptionalVersion, - traverse: ( - snapshotInfo: ModuleInfo, - remoteInfo: RemoteInfoOptionalVersion, - isRoot: boolean, - ) => void, + visit: SnapshotVisitor, isRoot: boolean, memo: Record = {}, remoteSnapshot?: ModuleInfo, ): void { const id = getFMId(remoteInfo); const { value: snapshotValue } = getInfoWithoutType(globalSnapshot, id); - const effectiveRemoteSnapshot = remoteSnapshot || snapshotValue; - if (effectiveRemoteSnapshot && !isManifestProvider(effectiveRemoteSnapshot)) { - traverse(effectiveRemoteSnapshot, remoteInfo, isRoot); - if (effectiveRemoteSnapshot.remotesInfo) { - const remoteKeys = Object.keys(effectiveRemoteSnapshot.remotesInfo); - for (const key of remoteKeys) { - if (memo[key]) { - continue; - } - memo[key] = true; - const subRemoteInfo = splitId(key); - const remoteValue = effectiveRemoteSnapshot.remotesInfo[key]; - traverseModuleInfo( - globalSnapshot, - { - name: subRemoteInfo.name, - version: remoteValue.matchedVersion, - }, - traverse, - false, - memo, - undefined, - ); + const effectiveSnapshot = remoteSnapshot || snapshotValue; + if (!effectiveSnapshot || isManifestProvider(effectiveSnapshot)) { + return; + } + visit(effectiveSnapshot, remoteInfo, isRoot); + const remotesInfo = effectiveSnapshot.remotesInfo; + if (!remotesInfo) return; + for (const key of Object.keys(remotesInfo)) { + if (memo[key]) continue; + memo[key] = true; + const parts = key.split(':'); + const name = parts.length <= 2 ? parts[0] : parts[1]; + const remoteValue = remotesInfo[key]; + walkSnapshot( + globalSnapshot, + { name, version: remoteValue.matchedVersion }, + visit, + false, + memo, + undefined, + ); + } +} +const EMPTY_ASSETS: PreloadAssets = { + cssAssets: [], + jsAssetsWithoutEntry: [], + entryAssets: [], +}; +const isExisted = (type: 'link' | 'script', url: string) => + document.querySelector( + `${type}[${type === 'link' ? 'href' : 'src'}="${url}"]`, + ); +function resolvePreloadConfig( + rootConfig: PreloadConfig, + depsRemote: PreloadConfig['depsRemote'], + remoteInfo: RemoteInfoOptionalVersion, + isRoot: boolean, +): PreloadConfig | null { + if (isRoot) return rootConfig; + if (Array.isArray(depsRemote)) { + const matched = depsRemote.find( + (remoteConfig) => + remoteConfig.nameOrAlias === remoteInfo.name || + remoteConfig.nameOrAlias === remoteInfo.alias, + ); + return matched ? defaultPreloadArgs(matched) : null; + } + if (depsRemote === true) return rootConfig; + return null; +} +function mapAssets( + snapshot: ModuleInfo, + assets: string[], + filter?: PreloadConfig['filter'], +): string[] { + const mapped = assets.map((asset) => getResourceUrl(snapshot, asset)); + return filter ? mapped.filter(filter) : mapped; +} +function collectEntryAsset( + entryAssets: EntryAssets[], + snapshot: ModuleInfo, + remoteInfo: RemoteInfoOptionalVersion, +): void { + const entryInfo = getRemoteEntryInfoFromSnapshot(snapshot); + const url = entryInfo.url ? getResourceUrl(snapshot, entryInfo.url) : ''; + if (!url) return; + entryAssets.push({ + name: remoteInfo.name, + moduleInfo: { + name: remoteInfo.name, + entry: url, + type: 'remoteEntryType' in snapshot ? snapshot.remoteEntryType : 'global', + entryGlobalName: + 'globalName' in snapshot ? snapshot.globalName : remoteInfo.name, + shareScope: '', + version: 'version' in snapshot ? snapshot.version : undefined, + }, + url, + }); +} +function collectModuleAssets( + origin: ModuleFederation, + snapshot: ModuleInfo, + remoteInfo: RemoteInfoOptionalVersion, + preloadConfig: PreloadConfig, + cssAssets: string[], + jsAssets: string[], +): void { + if (!('modules' in snapshot)) return; + const exposes = normalizePreloadExposes(preloadConfig.exposes); + const modules = exposes.length + ? snapshot.modules.filter((m) => exposes.includes(m.moduleName)) + : snapshot.modules; + if (!modules?.length) return; + for (const info of modules) { + const fullPath = `${remoteInfo.name}/${info.moduleName}`; + origin.remoteHandler.hooks.lifecycle.handlePreloadModule.emit({ + id: info.moduleName === '.' ? remoteInfo.name : fullPath, + name: remoteInfo.name, + remoteSnapshot: snapshot, + preloadConfig, + remote: remoteInfo as Remote, + origin, + }); + if (getPreloaded(fullPath)) continue; + if (preloadConfig.resourceCategory === 'all') { + cssAssets.push( + ...mapAssets(snapshot, info.assets.css.async, preloadConfig.filter), + ); + cssAssets.push( + ...mapAssets(snapshot, info.assets.css.sync, preloadConfig.filter), + ); + jsAssets.push( + ...mapAssets(snapshot, info.assets.js.async, preloadConfig.filter), + ); + jsAssets.push( + ...mapAssets(snapshot, info.assets.js.sync, preloadConfig.filter), + ); + } else { + cssAssets.push( + ...mapAssets(snapshot, info.assets.css.sync, preloadConfig.filter), + ); + jsAssets.push( + ...mapAssets(snapshot, info.assets.js.sync, preloadConfig.filter), + ); + } + setPreloaded(fullPath); + } +} +function collectLoadedSharedAssets( + origin: ModuleFederation, + remoteSnapshot: ModuleInfo, + loadedSharedJsAssets: Set, + loadedSharedCssAssets: Set, +): void { + if (!remoteSnapshot.shared?.length) return; + for (const shared of remoteSnapshot.shared) { + const shareInfos = origin.options.shared?.[shared.sharedName]; + if (!shareInfos) continue; + const matched = shared.version + ? shareInfos.find((s) => s.version === shared.version) + : shareInfos; + if (!matched) continue; + for (const shareInfo of arrayOptions(matched)) { + const { shared: reg } = + getRegisteredShare( + origin.shareScopeMap, + shared.sharedName, + shareInfo, + origin.sharedHandler.hooks.lifecycle.resolveShare, + ) || {}; + if (reg && typeof reg.lib === 'function') { + shared.assets.js.sync.forEach((a) => loadedSharedJsAssets.add(a)); + shared.assets.css.sync.forEach((a) => loadedSharedCssAssets.add(a)); } } } } - -const isExisted = (type: 'link' | 'script', url: string) => { - return document.querySelector( - `${type}[${type === 'link' ? 'href' : 'src'}="${url}"]`, +function filterExistingAssets( + type: 'link' | 'script', + assets: string[], + loadedSet: Set, +): string[] { + return assets.filter( + (asset) => !loadedSet.has(asset) && !isExisted(type, asset), ); -}; - +} // eslint-disable-next-line max-lines-per-function export function generatePreloadAssets( origin: ModuleFederation, @@ -116,253 +220,92 @@ export function generatePreloadAssets( globalSnapshot: GlobalModuleInfo, remoteSnapshot: ModuleInfo, ): PreloadAssets { - const cssAssets: Array = []; - const jsAssets: Array = []; - const entryAssets: Array = []; - const loadedSharedJsAssets = new Set(); - const loadedSharedCssAssets = new Set(); - const { options } = origin; - + const cssAssets: string[] = []; + const jsAssets: string[] = []; + const entryAssets: EntryAssets[] = []; + const loadedSharedJsAssets = new Set(); + const loadedSharedCssAssets = new Set(); const { preloadConfig: rootPreloadConfig } = preloadOptions; const { depsRemote } = rootPreloadConfig; - const memo = {}; - traverseModuleInfo( + walkSnapshot( globalSnapshot, remote, - (moduleInfoSnapshot: ModuleInfo, remoteInfo, isRoot) => { - let preloadConfig: PreloadConfig; - if (isRoot) { - preloadConfig = rootPreloadConfig; - } else { - if (Array.isArray(depsRemote)) { - // eslint-disable-next-line array-callback-return - const findPreloadConfig = depsRemote.find((remoteConfig) => { - if ( - remoteConfig.nameOrAlias === remoteInfo.name || - remoteConfig.nameOrAlias === remoteInfo.alias - ) { - return true; - } - return false; - }); - if (!findPreloadConfig) { - return; - } - preloadConfig = defaultPreloadArgs(findPreloadConfig); - } else if (depsRemote === true) { - preloadConfig = rootPreloadConfig; - } else { - return; - } - } - - const remoteEntryUrl = getResourceUrl( - moduleInfoSnapshot, - getRemoteEntryInfoFromSnapshot(moduleInfoSnapshot).url, + (snapshot, remoteInfo, isRoot) => { + const preloadConfig = resolvePreloadConfig( + rootPreloadConfig, + depsRemote, + remoteInfo, + isRoot, ); - - if (remoteEntryUrl) { - entryAssets.push({ - name: remoteInfo.name, - moduleInfo: { - name: remoteInfo.name, - entry: remoteEntryUrl, - type: - 'remoteEntryType' in moduleInfoSnapshot - ? moduleInfoSnapshot.remoteEntryType - : 'global', - entryGlobalName: - 'globalName' in moduleInfoSnapshot - ? moduleInfoSnapshot.globalName - : remoteInfo.name, - shareScope: '', - version: - 'version' in moduleInfoSnapshot - ? moduleInfoSnapshot.version - : undefined, - }, - url: remoteEntryUrl, - }); - } - - let moduleAssetsInfo: NonNullable = - 'modules' in moduleInfoSnapshot ? moduleInfoSnapshot.modules : []; - const normalizedPreloadExposes = normalizePreloadExposes( - preloadConfig.exposes, + if (!preloadConfig) return; + collectEntryAsset(entryAssets, snapshot, remoteInfo); + collectModuleAssets( + origin, + snapshot, + remoteInfo, + preloadConfig, + cssAssets, + jsAssets, ); - if (normalizedPreloadExposes.length && 'modules' in moduleInfoSnapshot) { - moduleAssetsInfo = moduleInfoSnapshot?.modules?.reduce( - (assets, moduleAssetInfo) => { - if ( - normalizedPreloadExposes?.indexOf(moduleAssetInfo.moduleName) !== - -1 - ) { - assets.push(moduleAssetInfo); - } - return assets; - }, - [] as NonNullable<(typeof moduleInfoSnapshot)['modules']>, - ); - } - - function handleAssets(assets: string[]): string[] { - const assetsRes = assets.map((asset) => - getResourceUrl(moduleInfoSnapshot, asset), - ); - if (preloadConfig.filter) { - return assetsRes.filter(preloadConfig.filter); - } - return assetsRes; - } - - if (moduleAssetsInfo) { - const assetsLength = moduleAssetsInfo.length; - for (let index = 0; index < assetsLength; index++) { - const assetsInfo = moduleAssetsInfo[index]; - const exposeFullPath = `${remoteInfo.name}/${assetsInfo.moduleName}`; - origin.remoteHandler.hooks.lifecycle.handlePreloadModule.emit({ - id: - assetsInfo.moduleName === '.' ? remoteInfo.name : exposeFullPath, - name: remoteInfo.name, - remoteSnapshot: moduleInfoSnapshot, - preloadConfig, - remote: remoteInfo as Remote, - origin, - }); - const preloaded = getPreloaded(exposeFullPath); - if (preloaded) { - continue; - } - - if (preloadConfig.resourceCategory === 'all') { - cssAssets.push(...handleAssets(assetsInfo.assets.css.async)); - cssAssets.push(...handleAssets(assetsInfo.assets.css.sync)); - jsAssets.push(...handleAssets(assetsInfo.assets.js.async)); - jsAssets.push(...handleAssets(assetsInfo.assets.js.sync)); - // eslint-disable-next-line no-constant-condition - } else if ((preloadConfig.resourceCategory = 'sync')) { - cssAssets.push(...handleAssets(assetsInfo.assets.css.sync)); - jsAssets.push(...handleAssets(assetsInfo.assets.js.sync)); - } - - setPreloaded(exposeFullPath); - } - } }, true, - memo, + {}, remoteSnapshot, ); - - if (remoteSnapshot.shared && remoteSnapshot.shared.length > 0) { - const collectSharedAssets = ( - shareInfo: Shared, - snapshotShared: ModuleInfo['shared'][0], - ) => { - const { shared: registeredShared } = - getRegisteredShare( - origin.shareScopeMap, - snapshotShared.sharedName, - shareInfo, - origin.sharedHandler.hooks.lifecycle.resolveShare, - ) || {}; - // If the global share does not exist, or the lib function does not exist, it means that the shared has not been loaded yet and can be preloaded. - - if (registeredShared && typeof registeredShared.lib === 'function') { - snapshotShared.assets.js.sync.forEach((asset) => { - loadedSharedJsAssets.add(asset); - }); - snapshotShared.assets.css.sync.forEach((asset) => { - loadedSharedCssAssets.add(asset); - }); - } - }; - remoteSnapshot.shared.forEach((shared) => { - const shareInfos = options.shared?.[shared.sharedName]; - if (!shareInfos) { - return; - } - // if no version, preload all shared - const sharedOptions = shared.version - ? shareInfos.find((s) => s.version === shared.version) - : shareInfos; - - if (!sharedOptions) { - return; - } - const arrayShareInfo = arrayOptions(sharedOptions); - arrayShareInfo.forEach((s) => { - collectSharedAssets(s, shared); - }); - }); - } - - const needPreloadJsAssets = jsAssets.filter( - (asset) => !loadedSharedJsAssets.has(asset) && !isExisted('script', asset), - ); - const needPreloadCssAssets = cssAssets.filter( - (asset) => !loadedSharedCssAssets.has(asset) && !isExisted('link', asset), + collectLoadedSharedAssets( + origin, + remoteSnapshot, + loadedSharedJsAssets, + loadedSharedCssAssets, ); - return { - cssAssets: needPreloadCssAssets, - jsAssetsWithoutEntry: needPreloadJsAssets, + cssAssets: filterExistingAssets('link', cssAssets, loadedSharedCssAssets), + jsAssetsWithoutEntry: filterExistingAssets( + 'script', + jsAssets, + loadedSharedJsAssets, + ), entryAssets: entryAssets.filter((entry) => !isExisted('script', entry.url)), }; } - export const generatePreloadAssetsPlugin: () => ModuleFederationRuntimePlugin = - function () { - return { - name: 'generate-preload-assets-plugin', - async generatePreloadAssets(args) { - const { - origin, - preloadOptions, - remoteInfo, - remote, - globalSnapshot, - remoteSnapshot, - } = args; - if (!isBrowserEnv()) { - return { - cssAssets: [], - jsAssetsWithoutEntry: [], - entryAssets: [], - }; - } - - if (isRemoteInfoWithEntry(remote) && isPureRemoteEntry(remote)) { - return { - cssAssets: [], - jsAssetsWithoutEntry: [], - entryAssets: [ - { - name: remote.name, - url: remote.entry, - moduleInfo: { - name: remoteInfo.name, - entry: remote.entry, - type: remoteInfo.type || 'global', - entryGlobalName: '', - shareScope: '', - }, + () => ({ + name: 'generate-preload-assets-plugin', + async generatePreloadAssets({ + origin, + preloadOptions, + remoteInfo, + remote, + globalSnapshot, + remoteSnapshot, + }) { + if (!isBrowserEnv()) return EMPTY_ASSETS; + if (isRemoteInfoWithEntry(remote) && isPureRemoteEntry(remote)) { + return { + cssAssets: [], + jsAssetsWithoutEntry: [], + entryAssets: [ + { + name: remote.name, + url: remote.entry, + moduleInfo: { + name: remoteInfo.name, + entry: remote.entry, + type: remoteInfo.type || 'global', + entryGlobalName: '', + shareScope: '', }, - ], - }; - } - - assignRemoteInfo(remoteInfo, remoteSnapshot); - - const assets = generatePreloadAssets( - origin, - preloadOptions, - remoteInfo, - globalSnapshot, - remoteSnapshot, - ); - - return assets; - }, - }; - }; + }, + ], + }; + } + assignRemoteInfo(remoteInfo, remoteSnapshot); + return generatePreloadAssets( + origin, + preloadOptions, + remoteInfo, + globalSnapshot, + remoteSnapshot, + ); + }, + }); diff --git a/packages/runtime-core/src/plugins/snapshot/SnapshotHandler.ts b/packages/runtime-core/src/plugins/snapshot/SnapshotHandler.ts index fe470edcf4d..5530d2770e8 100644 --- a/packages/runtime-core/src/plugins/snapshot/SnapshotHandler.ts +++ b/packages/runtime-core/src/plugins/snapshot/SnapshotHandler.ts @@ -6,14 +6,14 @@ import { isManifestProvider, isBrowserEnv, } from '@module-federation/sdk'; -import { - getShortErrorMsg, - RUNTIME_003, - RUNTIME_007, - runtimeDescMap, -} from '@module-federation/error-codes'; +import { RUNTIME_003, RUNTIME_007 } from '@module-federation/error-codes'; import { Options, Remote } from '../../type'; -import { isRemoteInfoWithEntry, error } from '../../utils'; +import { + isRemoteInfoWithEntry, + error, + singleFlight, + runtimeError, +} from '../../utils'; import { getGlobalSnapshot, setGlobalSnapshotInfoByModuleInfo, @@ -25,7 +25,7 @@ import { import { PluginSystem, AsyncHook, AsyncWaterfallHook } from '../../utils/hooks'; import { ModuleFederation } from '../../core'; import { assert } from '../../utils/logger'; - +import { Effect } from '@module-federation/micro-effect'; export function getGlobalRemoteInfo( moduleInfo: Remote, origin: ModuleFederation, @@ -38,35 +38,234 @@ export function getGlobalRemoteInfo( name: origin.name, version: origin.options.version, }); - - // get remote detail info from global const globalRemoteInfo = hostGlobalSnapshot && 'remotesInfo' in hostGlobalSnapshot && hostGlobalSnapshot.remotesInfo && getInfoWithoutType(hostGlobalSnapshot.remotesInfo, moduleInfo.name).value; - + const globalSnapshot = getGlobalSnapshot(); if (globalRemoteInfo && globalRemoteInfo.matchedVersion) { return { hostGlobalSnapshot, - globalSnapshot: getGlobalSnapshot(), + globalSnapshot, remoteSnapshot: getGlobalSnapshotInfoByModuleInfo({ name: moduleInfo.name, version: globalRemoteInfo.matchedVersion, }), }; } - return { hostGlobalSnapshot: undefined, - globalSnapshot: getGlobalSnapshot(), + globalSnapshot, remoteSnapshot: getGlobalSnapshotInfoByModuleInfo({ name: moduleInfo.name, version: 'version' in moduleInfo ? moduleInfo.version : undefined, }), }; } - +const getManifestJsonEffect = ( + handler: SnapshotHandler, + manifestUrl: string, + moduleInfo: Remote, +): Effect.Effect => + Effect.gen(function* () { + const getManifest = async (): Promise => { + let manifestJson: Manifest | undefined = + handler.manifestCache.get(manifestUrl); + if (manifestJson) return manifestJson; + try { + let res = await handler.loaderHook.lifecycle.fetch.emit( + manifestUrl, + {}, + ); + if (!res || !(res instanceof Response)) + res = await fetch(manifestUrl, {}); + manifestJson = (await res.json()) as Manifest; + } catch (err) { + manifestJson = + (await handler.HostInstance.remoteHandler.hooks.lifecycle.errorLoadRemote.emit( + { + id: manifestUrl, + error: err, + from: 'runtime', + lifecycle: 'afterResolve', + origin: handler.HostInstance, + }, + )) as Manifest | undefined; + if (!manifestJson) { + delete handler.manifestLoading[manifestUrl]; + error( + runtimeError( + RUNTIME_003, + { + manifestUrl, + moduleName: moduleInfo.name, + hostName: handler.HostInstance.options.name, + }, + `${err}`, + ), + ); + } + } + assert( + manifestJson.metaData && manifestJson.exposes && manifestJson.shared, + `${manifestUrl} is not a federation manifest`, + ); + handler.manifestCache.set(manifestUrl, manifestJson); + return manifestJson; + }; + const asyncLoadProcess = async () => { + const manifestJson = await getManifest(); + const remoteSnapshot = generateSnapshotFromManifest(manifestJson, { + version: manifestUrl, + }); + const { remoteSnapshot: remoteSnapshotRes } = + await handler.hooks.lifecycle.loadRemoteSnapshot.emit({ + options: handler.HostInstance.options, + moduleInfo, + manifestJson, + remoteSnapshot, + manifestUrl, + from: 'manifest', + }); + return remoteSnapshotRes; + }; + return yield* Effect.promise(() => + singleFlight(handler.manifestLoading, manifestUrl, asyncLoadProcess, { + clearOnReject: true, + }), + ); + }); +// eslint-disable-next-line max-lines-per-function +const loadRemoteSnapshotInfoEffect = ( + handler: SnapshotHandler, + params: { + moduleInfo: Remote; + id?: string; + expose?: string; + }, +): Effect.Effect<{ + remoteSnapshot: ModuleInfo; + globalSnapshot: GlobalModuleInfo; +}> => + Effect.gen(function* () { + const { moduleInfo, id } = params; + const { options } = handler.HostInstance; + yield* Effect.promise(() => + handler.hooks.lifecycle.beforeLoadRemoteSnapshot.emit({ + options, + moduleInfo, + }), + ); + let hostSnapshot = getGlobalSnapshotInfoByModuleInfo({ + name: handler.HostInstance.options.name, + version: handler.HostInstance.options.version, + }); + if (!hostSnapshot) { + hostSnapshot = { + version: handler.HostInstance.options.version || '', + remoteEntry: '', + remotesInfo: {}, + }; + addGlobalSnapshot({ + [handler.HostInstance.options.name]: hostSnapshot, + }); + } + if ( + hostSnapshot && + 'remotesInfo' in hostSnapshot && + !getInfoWithoutType(hostSnapshot.remotesInfo, moduleInfo.name).value && + ('version' in moduleInfo || 'entry' in moduleInfo) + ) { + hostSnapshot.remotesInfo = { + ...hostSnapshot?.remotesInfo, + [moduleInfo.name]: { + matchedVersion: + 'version' in moduleInfo ? moduleInfo.version : moduleInfo.entry, + }, + }; + } + const { hostGlobalSnapshot, remoteSnapshot, globalSnapshot } = + handler.getGlobalRemoteInfo(moduleInfo); + const { + remoteSnapshot: globalRemoteSnapshot, + globalSnapshot: globalSnapshotRes, + } = yield* Effect.promise(() => + handler.hooks.lifecycle.loadSnapshot.emit({ + options, + moduleInfo, + hostGlobalSnapshot, + remoteSnapshot, + globalSnapshot, + }), + ); + let mSnapshot; + let gSnapshot; + if (globalRemoteSnapshot) { + if (isManifestProvider(globalRemoteSnapshot)) { + const remoteEntry = isBrowserEnv() + ? globalRemoteSnapshot.remoteEntry + : globalRemoteSnapshot.ssrRemoteEntry || + globalRemoteSnapshot.remoteEntry || + ''; + const moduleSnapshot = yield* getManifestJsonEffect( + handler, + remoteEntry, + moduleInfo, + ); + gSnapshot = setGlobalSnapshotInfoByModuleInfo( + { ...moduleInfo, entry: remoteEntry }, + moduleSnapshot, + ); + mSnapshot = moduleSnapshot; + } else { + const { remoteSnapshot: rs } = yield* Effect.promise(() => + handler.hooks.lifecycle.loadRemoteSnapshot.emit({ + options: handler.HostInstance.options, + moduleInfo, + remoteSnapshot: globalRemoteSnapshot, + from: 'global', + }), + ); + mSnapshot = rs; + gSnapshot = globalSnapshotRes; + } + } else if (isRemoteInfoWithEntry(moduleInfo)) { + const moduleSnapshot = yield* getManifestJsonEffect( + handler, + moduleInfo.entry, + moduleInfo, + ); + gSnapshot = setGlobalSnapshotInfoByModuleInfo(moduleInfo, moduleSnapshot); + const { remoteSnapshot: rs } = yield* Effect.promise(() => + handler.hooks.lifecycle.loadRemoteSnapshot.emit({ + options: handler.HostInstance.options, + moduleInfo, + remoteSnapshot: moduleSnapshot, + from: 'global', + }), + ); + mSnapshot = rs; + } else { + error( + runtimeError(RUNTIME_007, { + hostName: moduleInfo.name, + hostVersion: moduleInfo.version, + globalSnapshot: JSON.stringify(globalSnapshotRes), + }), + ); + } + yield* Effect.promise(() => + handler.hooks.lifecycle.afterLoadSnapshot.emit({ + id, + host: handler.HostInstance, + options, + moduleInfo, + remoteSnapshot: mSnapshot, + }), + ); + return { remoteSnapshot: mSnapshot, globalSnapshot: gSnapshot }; + }); export class SnapshotHandler { loadingHostSnapshot: Promise | null = null; HostInstance: ModuleFederation; @@ -107,12 +306,10 @@ export class SnapshotHandler { loaderHook: ModuleFederation['loaderHook']; manifestLoading: Record> = Global.__FEDERATION__.__MANIFEST_LOADING__; - constructor(HostInstance: ModuleFederation) { this.HostInstance = HostInstance; this.loaderHook = HostInstance.loaderHook; } - // eslint-disable-next-line max-lines-per-function async loadRemoteSnapshotInfo({ moduleInfo, @@ -128,146 +325,10 @@ export class SnapshotHandler { globalSnapshot: GlobalModuleInfo; }> | never { - const { options } = this.HostInstance; - - await this.hooks.lifecycle.beforeLoadRemoteSnapshot.emit({ - options, - moduleInfo, - }); - - let hostSnapshot = getGlobalSnapshotInfoByModuleInfo({ - name: this.HostInstance.options.name, - version: this.HostInstance.options.version, - }); - - if (!hostSnapshot) { - hostSnapshot = { - version: this.HostInstance.options.version || '', - remoteEntry: '', - remotesInfo: {}, - }; - addGlobalSnapshot({ - [this.HostInstance.options.name]: hostSnapshot, - }); - } - - // In dynamic loadRemote scenarios, incomplete remotesInfo delivery may occur. In such cases, the remotesInfo in the host needs to be completed in the snapshot at runtime. - // This ensures the snapshot's integrity and helps the chrome plugin correctly identify all producer modules, ensuring that proxyable producer modules will not be missing. - if ( - hostSnapshot && - 'remotesInfo' in hostSnapshot && - !getInfoWithoutType(hostSnapshot.remotesInfo, moduleInfo.name).value - ) { - if ('version' in moduleInfo || 'entry' in moduleInfo) { - hostSnapshot.remotesInfo = { - ...hostSnapshot?.remotesInfo, - [moduleInfo.name]: { - matchedVersion: - 'version' in moduleInfo ? moduleInfo.version : moduleInfo.entry, - }, - }; - } - } - - const { hostGlobalSnapshot, remoteSnapshot, globalSnapshot } = - this.getGlobalRemoteInfo(moduleInfo); - const { - remoteSnapshot: globalRemoteSnapshot, - globalSnapshot: globalSnapshotRes, - } = await this.hooks.lifecycle.loadSnapshot.emit({ - options, - moduleInfo, - hostGlobalSnapshot, - remoteSnapshot, - globalSnapshot, - }); - - let mSnapshot; - let gSnapshot; - // global snapshot includes manifest or module info includes manifest - if (globalRemoteSnapshot) { - if (isManifestProvider(globalRemoteSnapshot)) { - const remoteEntry = isBrowserEnv() - ? globalRemoteSnapshot.remoteEntry - : globalRemoteSnapshot.ssrRemoteEntry || - globalRemoteSnapshot.remoteEntry || - ''; - const moduleSnapshot = await this.getManifestJson( - remoteEntry, - moduleInfo, - {}, - ); - // eslint-disable-next-line @typescript-eslint/no-shadow - const globalSnapshotRes = setGlobalSnapshotInfoByModuleInfo( - { - ...moduleInfo, - // The global remote may be overridden - // Therefore, set the snapshot key to the global address of the actual request - entry: remoteEntry, - }, - moduleSnapshot, - ); - mSnapshot = moduleSnapshot; - gSnapshot = globalSnapshotRes; - } else { - const { remoteSnapshot: remoteSnapshotRes } = - await this.hooks.lifecycle.loadRemoteSnapshot.emit({ - options: this.HostInstance.options, - moduleInfo, - remoteSnapshot: globalRemoteSnapshot, - from: 'global', - }); - mSnapshot = remoteSnapshotRes; - gSnapshot = globalSnapshotRes; - } - } else { - if (isRemoteInfoWithEntry(moduleInfo)) { - // get from manifest.json and merge remote info from remote server - const moduleSnapshot = await this.getManifestJson( - moduleInfo.entry, - moduleInfo, - {}, - ); - // eslint-disable-next-line @typescript-eslint/no-shadow - const globalSnapshotRes = setGlobalSnapshotInfoByModuleInfo( - moduleInfo, - moduleSnapshot, - ); - const { remoteSnapshot: remoteSnapshotRes } = - await this.hooks.lifecycle.loadRemoteSnapshot.emit({ - options: this.HostInstance.options, - moduleInfo, - remoteSnapshot: moduleSnapshot, - from: 'global', - }); - - mSnapshot = remoteSnapshotRes; - gSnapshot = globalSnapshotRes; - } else { - error( - getShortErrorMsg(RUNTIME_007, runtimeDescMap, { - hostName: moduleInfo.name, - hostVersion: moduleInfo.version, - globalSnapshot: JSON.stringify(globalSnapshotRes), - }), - ); - } - } - - await this.hooks.lifecycle.afterLoadSnapshot.emit({ - id, - host: this.HostInstance, - options, - moduleInfo, - remoteSnapshot: mSnapshot, - }); - - return { - remoteSnapshot: mSnapshot, - globalSnapshot: gSnapshot, - }; + return Effect.runPromise( + loadRemoteSnapshotInfoEffect(this, { moduleInfo, id, expose }), + ); } - getGlobalRemoteInfo(moduleInfo: Remote): { hostGlobalSnapshot: ModuleInfo | undefined; globalSnapshot: ReturnType; @@ -275,82 +336,4 @@ export class SnapshotHandler { } { return getGlobalRemoteInfo(moduleInfo, this.HostInstance); } - - private async getManifestJson( - manifestUrl: string, - moduleInfo: Remote, - extraOptions: Record, - ): Promise { - const getManifest = async (): Promise => { - let manifestJson: Manifest | undefined = - this.manifestCache.get(manifestUrl); - if (manifestJson) { - return manifestJson; - } - try { - let res = await this.loaderHook.lifecycle.fetch.emit(manifestUrl, {}); - if (!res || !(res instanceof Response)) { - res = await fetch(manifestUrl, {}); - } - manifestJson = (await res.json()) as Manifest; - } catch (err) { - manifestJson = - (await this.HostInstance.remoteHandler.hooks.lifecycle.errorLoadRemote.emit( - { - id: manifestUrl, - error: err, - from: 'runtime', - lifecycle: 'afterResolve', - origin: this.HostInstance, - }, - )) as Manifest | undefined; - - if (!manifestJson) { - delete this.manifestLoading[manifestUrl]; - error( - getShortErrorMsg( - RUNTIME_003, - runtimeDescMap, - { - manifestUrl, - moduleName: moduleInfo.name, - hostName: this.HostInstance.options.name, - }, - `${err}`, - ), - ); - } - } - - assert( - manifestJson.metaData && manifestJson.exposes && manifestJson.shared, - `${manifestUrl} is not a federation manifest`, - ); - this.manifestCache.set(manifestUrl, manifestJson); - return manifestJson; - }; - - const asyncLoadProcess = async () => { - const manifestJson = await getManifest(); - const remoteSnapshot = generateSnapshotFromManifest(manifestJson, { - version: manifestUrl, - }); - - const { remoteSnapshot: remoteSnapshotRes } = - await this.hooks.lifecycle.loadRemoteSnapshot.emit({ - options: this.HostInstance.options, - moduleInfo, - manifestJson, - remoteSnapshot, - manifestUrl, - from: 'manifest', - }); - return remoteSnapshotRes; - }; - - if (!this.manifestLoading[manifestUrl]) { - this.manifestLoading[manifestUrl] = asyncLoadProcess().then((res) => res); - } - return this.manifestLoading[manifestUrl]; - } } diff --git a/packages/runtime-core/src/remote/index.ts b/packages/runtime-core/src/remote/index.ts index bb101f25450..71ad2738738 100644 --- a/packages/runtime-core/src/remote/index.ts +++ b/packages/runtime-core/src/remote/index.ts @@ -2,14 +2,11 @@ import { isBrowserEnv, warn, composeKeyWithSeparator, + safeToString, ModuleInfo, GlobalModuleInfo, } from '@module-federation/sdk'; -import { - getShortErrorMsg, - RUNTIME_004, - runtimeDescMap, -} from '@module-federation/error-codes'; +import { RUNTIME_002, RUNTIME_004 } from '@module-federation/error-codes'; import { Global, getInfoWithoutType, @@ -25,7 +22,11 @@ import { Remote, RemoteInfo, RemoteEntryExports, + InitScope, + ShareScopeMap, + RemoteEntryInitOptions, CallFrom, + GlobalShareScopeMap, } from '../type'; import { ModuleFederation } from '../core'; import { @@ -37,16 +38,65 @@ import { } from '../utils/hooks'; import { assert, + error, + getFMId, + getRemoteEntry, getRemoteInfo, getRemoteEntryUniqueKey, matchRemoteWithNameAndExpose, + processModuleAlias, logger, + runtimeError, } from '../utils'; import { DEFAULT_REMOTE_TYPE, DEFAULT_SCOPE } from '../constant'; import { Module, ModuleOptions } from '../module'; import { formatPreloadArgs, preloadAssets } from '../utils/preload'; -import { getGlobalShareScope } from '../utils/share'; +import { getGlobalShareScope } from '../shared'; import { getGlobalRemoteInfo } from '../plugins/snapshot/SnapshotHandler'; +import { Effect } from '@module-federation/micro-effect'; + +function collectRemoteShareKeys( + globalShareScopeMap: GlobalShareScopeMap, + remoteName: string, +): { + keysToDelete: Array<[string, string, string, string]>; + allUnused: boolean; +} { + let allUnused = true; + const keysToDelete: Array<[string, string, string, string]> = []; + for (const instId of Object.keys(globalShareScopeMap)) { + const scopes = globalShareScopeMap[instId]; + if (!scopes) continue; + for (const scope of Object.keys(scopes)) { + const names = scopes[scope]; + if (!names) continue; + for (const name of Object.keys(names)) { + const versions = names[name]; + if (!versions) continue; + for (const version of Object.keys(versions)) { + const shared = versions[version]; + if ( + !shared || + typeof shared !== 'object' || + shared.from !== remoteName + ) + continue; + if (shared.loaded || shared.loading) { + shared.useIn = shared.useIn.filter((h) => h !== remoteName); + if (shared.useIn.length) { + allUnused = false; + } else { + keysToDelete.push([instId, scope, name, version]); + } + } else { + keysToDelete.push([instId, scope, name, version]); + } + } + } + } + } + return { keysToDelete, allUnused }; +} export interface LoadRemoteMatch { id: string; @@ -59,6 +109,45 @@ export interface LoadRemoteMatch { remoteSnapshot?: ModuleInfo; } +// --- Effect programs --- + +const preloadRemoteEffect = ( + handler: RemoteHandler, + preloadOptions: Array, +): Effect.Effect => + Effect.gen(function* () { + const { host } = handler; + yield* Effect.promise(() => + handler.hooks.lifecycle.beforePreloadRemote.emit({ + preloadOps: preloadOptions, + options: host.options, + origin: host, + }), + ); + yield* Effect.forEach( + formatPreloadArgs(host.options.remotes, preloadOptions), + (ops) => + Effect.promise(async () => { + const remoteInfo = getRemoteInfo(ops.remote); + const { globalSnapshot, remoteSnapshot } = + await host.snapshotHandler.loadRemoteSnapshotInfo({ + moduleInfo: ops.remote, + }); + const assets = + await handler.hooks.lifecycle.generatePreloadAssets.emit({ + origin: host, + preloadOptions: ops, + remote: ops.remote, + remoteInfo, + globalSnapshot, + remoteSnapshot, + }); + if (assets) preloadAssets(remoteInfo, host, assets); + }), + { concurrency: 'parallel' }, + ); + }); + export class RemoteHandler { host: ModuleFederation; idToRemoteMap: Record; @@ -145,12 +234,6 @@ export class RemoteHandler { ], Promise >('generatePreloadAssets'), - // not used yet - afterPreloadRemote: new AsyncHook<{ - preloadOps: Array; - options: Options; - origin: ModuleFederation; - }>(), // TODO: Move to loaderHook loadEntry: new AsyncHook< [ @@ -172,10 +255,12 @@ export class RemoteHandler { formatAndRegisterRemote(globalOptions: Options, userOptions: UserOptions) { const userRemotes = userOptions.remotes || []; - return userRemotes.reduce((res, remote) => { - this.registerRemote(remote, res, { force: false }); - return res; - }, globalOptions.remotes); + return userRemotes.reduce( + (res, remote) => ( + this.registerRemote(remote, res, { force: false }), res + ), + globalOptions.remotes, + ); } setIdToRemoteMap(id: string, remoteMatchInfo: LoadRemoteMatch) { @@ -202,16 +287,7 @@ export class RemoteHandler { ): Promise { const { host } = this; try { - const { loadFactory = true } = options || { - loadFactory: true, - }; - // 1. Validate the parameters of the retrieved module. There are two module request methods: pkgName + expose and alias + expose. - // 2. Request the snapshot information of the current host and globally store the obtained snapshot information. The retrieved module information is partially offline and partially online. The online module information will retrieve the modules used online. - // 3. Retrieve the detailed information of the current module from global (remoteEntry address, expose resource address) - // 4. After retrieving remoteEntry, call the init of the module, and then retrieve the exported content of the module through get - // id: pkgName(@federation/app1) + expose(button) = @federation/app1/button - // id: alias(app1) + expose(button) = app1/button - // id: alias(app1/utils) + expose(loadash/sort) = app1/utils/loadash/sort + const loadFactory = options?.loadFactory !== false; const { module, moduleOptions, remoteMatchInfo } = await this.getRemoteModuleAndOptions({ id, @@ -250,7 +326,7 @@ export class RemoteHandler { return moduleOrFactory; } catch (error) { - const { from = 'runtime' } = options || { from: 'runtime' }; + const from = options?.from ?? 'runtime'; const failOver = await this.hooks.lifecycle.errorLoadRemote.emit({ id, @@ -270,51 +346,16 @@ export class RemoteHandler { // eslint-disable-next-line @typescript-eslint/member-ordering async preloadRemote(preloadOptions: Array): Promise { - const { host } = this; - - await this.hooks.lifecycle.beforePreloadRemote.emit({ - preloadOps: preloadOptions, - options: host.options, - origin: host, - }); - - const preloadOps: PreloadOptions = formatPreloadArgs( - host.options.remotes, - preloadOptions, - ); - - await Promise.all( - preloadOps.map(async (ops) => { - const { remote } = ops; - const remoteInfo = getRemoteInfo(remote); - const { globalSnapshot, remoteSnapshot } = - await host.snapshotHandler.loadRemoteSnapshotInfo({ - moduleInfo: remote, - }); - - const assets = await this.hooks.lifecycle.generatePreloadAssets.emit({ - origin: host, - preloadOptions: ops, - remote, - remoteInfo, - globalSnapshot, - remoteSnapshot, - }); - if (!assets) { - return; - } - preloadAssets(remoteInfo, host, assets); - }), - ); + return Effect.runPromise(preloadRemoteEffect(this, preloadOptions)); } registerRemotes(remotes: Remote[], options?: { force?: boolean }): void { const { host } = this; - remotes.forEach((remote) => { + remotes.forEach((remote) => this.registerRemote(remote, host.options.remotes, { force: options?.force, - }); - }); + }), + ); } async getRemoteModuleAndOptions(options: { id: string }): Promise<{ @@ -324,32 +365,20 @@ export class RemoteHandler { }> { const { host } = this; const { id } = options; - let loadRemoteArgs; - - try { - loadRemoteArgs = await this.hooks.lifecycle.beforeRequest.emit({ - id, - options: host.options, - origin: host, + const loadRemoteArgs = await this.hooks.lifecycle.beforeRequest + .emit({ id, options: host.options, origin: host }) + .catch(async (error) => { + const fallback = (await this.hooks.lifecycle.errorLoadRemote.emit({ + id, + options: host.options, + origin: host, + from: 'runtime', + error, + lifecycle: 'beforeRequest', + })) as { id: string; options: Options; origin: ModuleFederation }; + if (!fallback) throw error; + return fallback; }); - } catch (error) { - loadRemoteArgs = (await this.hooks.lifecycle.errorLoadRemote.emit({ - id, - options: host.options, - origin: host, - from: 'runtime', - error, - lifecycle: 'beforeRequest', - })) as { - id: string; - options: Options; - origin: ModuleFederation; - }; - - if (!loadRemoteArgs) { - throw error; - } - } const { id: idRes } = loadRemoteArgs; @@ -359,14 +388,12 @@ export class RemoteHandler { ); assert( remoteSplitInfo, - getShortErrorMsg(RUNTIME_004, runtimeDescMap, { + runtimeError(RUNTIME_004, { hostName: host.options.name, requestId: idRes, }), ); - - const { remote: rawRemote } = remoteSplitInfo; - const remoteInfo = getRemoteInfo(rawRemote); + const remoteInfo = getRemoteInfo(remoteSplitInfo.remote); const matchInfo = await host.sharedHandler.hooks.lifecycle.afterResolve.emit({ id: idRes, @@ -381,17 +408,10 @@ export class RemoteHandler { remote && expose, `The 'beforeRequest' hook was executed, but it failed to return the correct 'remote' and 'expose' values while loading ${idRes}.`, ); - let module: Module | undefined = host.moduleCache.get(remote.name); - - const moduleOptions: ModuleOptions = { - host: host, - remoteInfo, - }; - - if (!module) { - module = new Module(moduleOptions); - host.moduleCache.set(remote.name, module); - } + const moduleOptions: ModuleOptions = { host, remoteInfo }; + const cached = host.moduleCache.get(remote.name); + const module = cached ?? new Module(moduleOptions); + if (!cached) host.moduleCache.set(remote.name, module); return { module, moduleOptions, @@ -407,8 +427,6 @@ export class RemoteHandler { const { host } = this; const normalizeRemote = () => { if (remote.alias) { - // Validate if alias equals the prefix of remote.name and remote.alias, if so, throw an error - // As multi-level path references cannot guarantee unique names, alias being a prefix of remote.name is not supported const findEqual = targetRemotes.find( (item) => remote.alias && @@ -417,184 +435,251 @@ export class RemoteHandler { ); assert( !findEqual, - `The alias ${remote.alias} of remote ${ - remote.name - } is not allowed to be the prefix of ${ - findEqual && findEqual.name - } name or alias`, + `The alias ${remote.alias} of remote ${remote.name} is not allowed to be the prefix of ${findEqual && findEqual.name} name or alias`, ); } - // Set the remote entry to a complete path if ('entry' in remote) { if (isBrowserEnv() && !remote.entry.startsWith('http')) { remote.entry = new URL(remote.entry, window.location.origin).href; } } - if (!remote.shareScope) { - remote.shareScope = DEFAULT_SCOPE; - } - if (!remote.type) { - remote.type = DEFAULT_REMOTE_TYPE; - } + if (!remote.shareScope) remote.shareScope = DEFAULT_SCOPE; + if (!remote.type) remote.type = DEFAULT_REMOTE_TYPE; }; this.hooks.lifecycle.beforeRegisterRemote.emit({ remote, origin: host }); const registeredRemote = targetRemotes.find( (item) => item.name === remote.name, ); - if (!registeredRemote) { - normalizeRemote(); - targetRemotes.push(remote); - this.hooks.lifecycle.registerRemote.emit({ remote, origin: host }); - } else { - const messages = [ - `The remote "${remote.name}" is already registered.`, - 'Please note that overriding it may cause unexpected errors.', - ]; - if (options?.force) { - // remove registered remote - this.removeRemote(registeredRemote); - normalizeRemote(); - targetRemotes.push(remote); - this.hooks.lifecycle.registerRemote.emit({ remote, origin: host }); - warn(messages.join(' ')); - } + if (registeredRemote) { + if (!options?.force) return; + this.removeRemote(registeredRemote); + warn( + `The remote "${remote.name}" is already registered. Please note that overriding it may cause unexpected errors.`, + ); } + normalizeRemote(); + targetRemotes.push(remote); + this.hooks.lifecycle.registerRemote.emit({ remote, origin: host }); } - private removeRemote(remote: Remote): void { - try { - const { host } = this; - const { name } = remote; - const remoteIndex = host.options.remotes.findIndex( - (item) => item.name === name, - ); - if (remoteIndex !== -1) { - host.options.remotes.splice(remoteIndex, 1); - } - const loadedModule = host.moduleCache.get(remote.name); - if (loadedModule) { - const remoteInfo = loadedModule.remoteInfo; - const key = remoteInfo.entryGlobalName as keyof typeof CurrentGlobal; + _ensureEntry( + mod: Module, + options: { + init?: boolean; + id?: string; + remoteSnapshot?: ModuleInfo; + } = {}, + ): Effect.Effect { + return Effect.gen(function* () { + let remoteEntryExports = mod.remoteEntryExports; + if (!remoteEntryExports) { + remoteEntryExports = (yield* Effect.promise(() => + getRemoteEntry({ + origin: mod.host, + remoteInfo: mod.remoteInfo, + remoteEntryExports: mod.remoteEntryExports, + }), + )) as RemoteEntryExports | undefined; - if (CurrentGlobal[key]) { - if ( - Object.getOwnPropertyDescriptor(CurrentGlobal, key)?.configurable - ) { - delete CurrentGlobal[key]; - } else { - // @ts-ignore - CurrentGlobal[key] = undefined; - } - } - const remoteEntryUniqueKey = getRemoteEntryUniqueKey( - loadedModule.remoteInfo, + assert( + remoteEntryExports, + `remoteEntryExports is undefined \n ${safeToString(mod.remoteInfo)}`, ); + mod.remoteEntryExports = remoteEntryExports; + } - if (globalLoading[remoteEntryUniqueKey]) { - delete globalLoading[remoteEntryUniqueKey]; + if (options.init && !mod.inited) { + if (mod.initPromise) { + yield* Effect.promise(() => mod.initPromise as Promise); + return remoteEntryExports; } - host.snapshotHandler.manifestCache.delete(remoteInfo.entry); - - // delete unloaded shared and instance - let remoteInsId = remoteInfo.buildVersion - ? composeKeyWithSeparator(remoteInfo.name, remoteInfo.buildVersion) - : remoteInfo.name; - const remoteInsIndex = - CurrentGlobal.__FEDERATION__.__INSTANCES__.findIndex((ins) => { - if (remoteInfo.buildVersion) { - return ins.options.id === remoteInsId; - } else { - return ins.name === remoteInsId; - } + mod.initing = true; + mod.initPromise = (async () => { + const hostShareScopeMap = mod.host.shareScopeMap; + const shareScopeValue = mod.remoteInfo.shareScope || 'default'; + const shareScopeKeys = Array.isArray(shareScopeValue) + ? shareScopeValue + : [shareScopeValue]; + shareScopeKeys.forEach((key) => { + hostShareScopeMap[key] ||= {}; }); - if (remoteInsIndex !== -1) { - const remoteIns = - CurrentGlobal.__FEDERATION__.__INSTANCES__[remoteInsIndex]; - remoteInsId = remoteIns.options.id || remoteInsId; - const globalShareScopeMap = getGlobalShareScope(); - - let isAllSharedNotUsed = true; - const needDeleteKeys: Array<[string, string, string, string]> = []; - Object.keys(globalShareScopeMap).forEach((instId) => { - const shareScopeMap = globalShareScopeMap[instId]; - shareScopeMap && - Object.keys(shareScopeMap).forEach((shareScope) => { - const shareScopeVal = shareScopeMap[shareScope]; - shareScopeVal && - Object.keys(shareScopeVal).forEach((shareName) => { - const sharedPkgs = shareScopeVal[shareName]; - sharedPkgs && - Object.keys(sharedPkgs).forEach((shareVersion) => { - const shared = sharedPkgs[shareVersion]; - if ( - shared && - typeof shared === 'object' && - shared.from === remoteInfo.name - ) { - if (shared.loaded || shared.loading) { - shared.useIn = shared.useIn.filter( - (usedHostName) => - usedHostName !== remoteInfo.name, - ); - if (shared.useIn.length) { - isAllSharedNotUsed = false; - } else { - needDeleteKeys.push([ - instId, - shareScope, - shareName, - shareVersion, - ]); - } - } else { - needDeleteKeys.push([ - instId, - shareScope, - shareName, - shareVersion, - ]); - } - } - }); - }); - }); + + const remoteEntryInitOptions: RemoteEntryInitOptions = { + version: mod.remoteInfo.version || '', + shareScopeKeys: Array.isArray(shareScopeValue) + ? shareScopeKeys + : shareScopeValue, + }; + Object.defineProperty(remoteEntryInitOptions, 'shareScopeMap', { + value: hostShareScopeMap, + enumerable: false, }); - if (isAllSharedNotUsed) { - remoteIns.shareScopeMap = {}; - delete globalShareScopeMap[remoteInsId]; + const initScope: InitScope = []; + const initContainerOptions = + await mod.host.hooks.lifecycle.beforeInitContainer.emit({ + shareScope: hostShareScopeMap[shareScopeKeys[0]], + // @ts-ignore shareScopeMap will be set by Object.defineProperty + remoteEntryInitOptions, + initScope, + remoteInfo: mod.remoteInfo, + origin: mod.host, + }); + + if (typeof remoteEntryExports?.init === 'undefined') { + error( + runtimeError(RUNTIME_002, { + hostName: mod.host.name, + remoteName: mod.remoteInfo.name, + remoteEntryUrl: mod.remoteInfo.entry, + remoteEntryKey: mod.remoteInfo.entryGlobalName, + }), + ); } - needDeleteKeys.forEach( - ([insId, shareScope, shareName, shareVersion]) => { - delete globalShareScopeMap[insId]?.[shareScope]?.[shareName]?.[ - shareVersion - ]; - }, + + await remoteEntryExports.init( + initContainerOptions.shareScope, + initContainerOptions.initScope, + initContainerOptions.remoteEntryInitOptions, ); - CurrentGlobal.__FEDERATION__.__INSTANCES__.splice(remoteInsIndex, 1); + + await mod.host.hooks.lifecycle.initContainer.emit({ + ...initContainerOptions, + id: options.id, + remoteSnapshot: options.remoteSnapshot, + remoteEntryExports, + }); + mod.inited = true; + })(); + + try { + yield* Effect.promise(() => mod.initPromise as Promise); + } finally { + mod.initing = false; + mod.initPromise = undefined; } + } - const { hostGlobalSnapshot } = getGlobalRemoteInfo(remote, host); - if (hostGlobalSnapshot) { - const remoteKey = - hostGlobalSnapshot && - 'remotesInfo' in hostGlobalSnapshot && - hostGlobalSnapshot.remotesInfo && - getInfoWithoutType(hostGlobalSnapshot.remotesInfo, remote.name).key; - if (remoteKey) { - delete hostGlobalSnapshot.remotesInfo[remoteKey]; - if ( - //eslint-disable-next-line no-extra-boolean-cast - Boolean(Global.__FEDERATION__.__MANIFEST_LOADING__[remoteKey]) - ) { - delete Global.__FEDERATION__.__MANIFEST_LOADING__[remoteKey]; - } - } + return remoteEntryExports; + }); + } + + _getModule( + mod: Module, + id: string, + expose: string, + options?: { loadFactory?: boolean }, + remoteSnapshot?: ModuleInfo, + ): Effect.Effect { + const handler = this; + return Effect.gen(function* () { + const { loadFactory = true } = options || { loadFactory: true }; + const remoteEntryExports = yield* handler._ensureEntry(mod, { + init: true, + id, + remoteSnapshot, + }); + mod.lib = remoteEntryExports; + + const moduleFactory = yield* Effect.promise(async () => { + const result = + await mod.host.loaderHook.lifecycle.getModuleFactory.emit({ + remoteEntryExports, + expose, + moduleInfo: mod.remoteInfo, + }); + return result || (await remoteEntryExports.get(expose)); + }); + + assert( + moduleFactory, + `${getFMId(mod.remoteInfo)} remote don't export ${expose}.`, + ); + const wrapModuleFactory = mod.wraperFactory( + moduleFactory, + processModuleAlias(mod.remoteInfo.name, expose), + ); + if (!loadFactory) return wrapModuleFactory; + return yield* Effect.promise(() => wrapModuleFactory()); + }); + } + + private removeRemote(remote: Remote): void { + try { + const { host } = this; + const { name } = remote; + const remoteIndex = host.options.remotes.findIndex( + (item) => item.name === name, + ); + if (remoteIndex !== -1) host.options.remotes.splice(remoteIndex, 1); + const loadedModule = host.moduleCache.get(remote.name); + if (!loadedModule) return; + const remoteInfo = loadedModule.remoteInfo; + const key = remoteInfo.entryGlobalName as keyof typeof CurrentGlobal; + + if (CurrentGlobal[key]) { + if (Object.getOwnPropertyDescriptor(CurrentGlobal, key)?.configurable) + delete CurrentGlobal[key]; + // @ts-ignore + else CurrentGlobal[key] = undefined; + } + const remoteEntryUniqueKey = getRemoteEntryUniqueKey( + loadedModule.remoteInfo, + ); + if (globalLoading[remoteEntryUniqueKey]) + delete globalLoading[remoteEntryUniqueKey]; + host.snapshotHandler.manifestCache.delete(remoteInfo.entry); + + let remoteInsId = remoteInfo.buildVersion + ? composeKeyWithSeparator(remoteInfo.name, remoteInfo.buildVersion) + : remoteInfo.name; + const remoteInsIndex = + CurrentGlobal.__FEDERATION__.__INSTANCES__.findIndex((ins) => + remoteInfo.buildVersion + ? ins.options.id === remoteInsId + : ins.name === remoteInsId, + ); + if (remoteInsIndex !== -1) { + const remoteIns = + CurrentGlobal.__FEDERATION__.__INSTANCES__[remoteInsIndex]; + remoteInsId = remoteIns.options.id || remoteInsId; + const globalShareScopeMap = getGlobalShareScope(); + + const { keysToDelete, allUnused } = collectRemoteShareKeys( + globalShareScopeMap, + remoteInfo.name, + ); + + if (allUnused) { + remoteIns.shareScopeMap = {}; + delete globalShareScopeMap[remoteInsId]; } + keysToDelete.forEach(([insId, shareScope, shareName, shareVersion]) => { + delete globalShareScopeMap[insId]?.[shareScope]?.[shareName]?.[ + shareVersion + ]; + }); + CurrentGlobal.__FEDERATION__.__INSTANCES__.splice(remoteInsIndex, 1); + } - host.moduleCache.delete(remote.name); + const { hostGlobalSnapshot } = getGlobalRemoteInfo(remote, host); + if ( + hostGlobalSnapshot && + 'remotesInfo' in hostGlobalSnapshot && + hostGlobalSnapshot.remotesInfo + ) { + const remoteKey = getInfoWithoutType( + hostGlobalSnapshot.remotesInfo, + remote.name, + ).key; + if (remoteKey) { + delete hostGlobalSnapshot.remotesInfo[remoteKey]; + delete Global.__FEDERATION__.__MANIFEST_LOADING__[remoteKey]; + } } + + host.moduleCache.delete(remote.name); } catch (err) { logger.log('removeRemote fail: ', err); } diff --git a/packages/runtime-core/src/shared/index.ts b/packages/runtime-core/src/shared/index.ts index c5caa5fe102..f7e1d3f28b9 100644 --- a/packages/runtime-core/src/shared/index.ts +++ b/packages/runtime-core/src/shared/index.ts @@ -1,15 +1,12 @@ -import { - getShortErrorMsg, - RUNTIME_005, - RUNTIME_006, - runtimeDescMap, -} from '@module-federation/error-codes'; -import { Federation } from '../global'; +import { RUNTIME_005, RUNTIME_006 } from '@module-federation/error-codes'; +import { TreeShakingStatus, satisfy } from '@module-federation/sdk'; +import { Global, Federation } from '../global'; import { Options, ShareScopeMap, ShareInfos, Shared, + ShareArgs, RemoteEntryExports, UserOptions, ShareStrategy, @@ -17,27 +14,567 @@ import { InitTokens, CallFrom, TreeShakingArgs, + SharedGetter, + GlobalShareScopeMap, + LoadShareExtraOptions, } from '../type'; import { ModuleFederation } from '../core'; import { PluginSystem, - AsyncHook, AsyncWaterfallHook, SyncWaterfallHook, } from '../utils/hooks'; -import { - formatShareConfigs, - getRegisteredShare, - getTargetSharedOptions, - getGlobalShareScope, - directShare, - shouldUseTreeShaking, - addUseIn, -} from '../utils/share'; -import { assert, addUniqueItem } from '../utils'; +import { warn, error } from '../utils/logger'; +import { addUniqueItem, arrayOptions, isPlainObject } from '../utils/tool'; +import { assert, runtimeError } from '../utils'; import { DEFAULT_SCOPE } from '../constant'; import { LoadRemoteMatch } from '../remote'; -import { createRemoteEntryInitOptions } from '../module'; +import { Effect } from '@module-federation/micro-effect'; + +function formatShare( + shareArgs: ShareArgs, + from: string, + name: string, + shareStrategy?: ShareStrategy, +): Shared { + const get: Shared['get'] = + 'get' in shareArgs + ? shareArgs.get + : 'lib' in shareArgs + ? () => Promise.resolve(shareArgs.lib) + : () => + Promise.resolve(() => { + throw new Error(`Can not get shared '${name}'!`); + }); + + if (shareArgs.shareConfig?.eager && shareArgs.treeShaking) + throw new Error( + 'Can not set "eager:true" and "treeShaking" at the same time!', + ); + + return { + deps: [], + useIn: [], + from, + loading: null, + ...shareArgs, + shareConfig: { + requiredVersion: `^${shareArgs.version}`, + singleton: false, + eager: false, + strictVersion: false, + ...shareArgs.shareConfig, + }, + get, + loaded: shareArgs.loaded ?? ('lib' in shareArgs ? true : undefined), + version: shareArgs.version ?? '0', + scope: Array.isArray(shareArgs.scope) + ? shareArgs.scope + : [shareArgs.scope ?? 'default'], + strategy: (shareArgs.strategy ?? shareStrategy) || 'version-first', + treeShaking: shareArgs.treeShaking + ? { + ...shareArgs.treeShaking, + mode: shareArgs.treeShaking.mode ?? 'server-calc', + status: shareArgs.treeShaking.status ?? TreeShakingStatus.UNKNOWN, + useIn: [], + } + : undefined, + }; +} + +export function formatShareConfigs( + prevOptions: Options, + newOptions: UserOptions, +) { + const shareArgs = newOptions.shared || {}; + const from = newOptions.name; + const newShareInfos = Object.keys(shareArgs).reduce((res, pkgName) => { + const arrayShareArgs = arrayOptions(shareArgs[pkgName]); + res[pkgName] = res[pkgName] || []; + arrayShareArgs.forEach((shareConfig) => { + res[pkgName].push( + formatShare(shareConfig, from, pkgName, newOptions.shareStrategy), + ); + }); + return res; + }, {} as ShareInfos); + const allShareInfos = { + ...prevOptions.shared, + }; + + Object.keys(newShareInfos).forEach((shareKey) => { + if (!allShareInfos[shareKey]) { + allShareInfos[shareKey] = newShareInfos[shareKey]; + } else { + newShareInfos[shareKey].forEach((newUserSharedOptions) => { + const isSameVersion = allShareInfos[shareKey].find( + (sharedVal) => sharedVal.version === newUserSharedOptions.version, + ); + if (!isSameVersion) { + allShareInfos[shareKey].push(newUserSharedOptions); + } + }); + } + }); + return { allShareInfos, newShareInfos }; +} +export function shouldUseTreeShaking( + treeShaking?: TreeShakingArgs, + usedExports?: string[], +) { + if (!treeShaking) { + return false; + } + const { status, mode } = treeShaking; + if (status === TreeShakingStatus.NO_USE) { + return false; + } + + if (status === TreeShakingStatus.CALCULATED) { + return true; + } + + // isMatchUsedExports inlined (Step 5d) + if (mode === 'runtime-infer') { + if (!usedExports) return true; + if (!treeShaking.usedExports) return false; + return usedExports.every((e) => treeShaking.usedExports!.includes(e)); + } + + return false; +} + +export function versionLt(a: string, b: string): boolean { + const normalize = (version: string) => { + if (Number.isNaN(Number(version))) return version; + const parts = version.split('.'); + return parts.length >= 3 + ? version + : `${version}${'.0'.repeat(3 - parts.length)}`; + }; + return satisfy(normalize(a), `<=${normalize(b)}`); +} + +export const findVersion = ( + shareVersionMap: ShareScopeMap[string][string], + cb?: (prev: string, cur: string) => boolean, +): string => { + const callback = cb || ((prev: string, cur: string) => versionLt(prev, cur)); + return Object.keys(shareVersionMap).reduce((prev: number | string, cur) => { + if (!prev || callback(prev as string, cur) || prev === '0') return cur; + return prev; + }, 0) as string; +}; + +export const isLoaded = (shared: { + loading?: null | Promise; + loaded?: boolean; + lib?: () => unknown; +}) => { + return Boolean(shared.loaded) || typeof shared.lib === 'function'; +}; + +// isLoadingOrLoaded kept as function (used in multiple places in singleton callbacks) (Step 5d) +const isLoadingOrLoaded = (s: { + loading?: null | Promise; + loaded?: boolean; + lib?: () => unknown; +}) => isLoaded(s) || Boolean(s.loading); + +function findSingletonVersion( + shareScopeMap: ShareScopeMap, + scope: string, + pkgName: string, + treeShaking: TreeShakingArgs | undefined, + makeCallback: ( + versions: ShareScopeMap[string][string], + useTreesShaking: boolean, + ) => (prev: string, cur: string) => boolean, +): { version: string; useTreesShaking: boolean } { + const versions = shareScopeMap[scope][pkgName]; + let useTreesShaking = shouldUseTreeShaking(treeShaking); + + if (useTreesShaking) { + const callback = makeCallback(versions, true); + const version = findVersion(versions, callback); + if (version) { + return { version, useTreesShaking }; + } + useTreesShaking = false; + } + + const callback = makeCallback(versions, false); + return { + version: findVersion(versions, callback), + useTreesShaking, + }; +} + +type ShareComparatorFactory = ( + versions: ShareScopeMap[string][string], + useTreesShaking: boolean, +) => (prev: string, cur: string) => boolean; + +const makeLoadedFirstComparator: ShareComparatorFactory = + (versions, useTreesShaking) => (prev, cur) => { + if (useTreesShaking) { + const prevTs = versions[prev].treeShaking; + const curTs = versions[cur].treeShaking; + if (!prevTs) return true; + if (!curTs) return false; + const curLoaded = isLoadingOrLoaded(curTs); + const prevLoaded = isLoadingOrLoaded(prevTs); + if (curLoaded) return prevLoaded ? versionLt(prev, cur) : true; + if (prevLoaded) return false; + } + const curLoaded = isLoadingOrLoaded(versions[cur]); + const prevLoaded = isLoadingOrLoaded(versions[prev]); + if (curLoaded) return prevLoaded ? versionLt(prev, cur) : true; + if (prevLoaded) return false; + return versionLt(prev, cur); + }; + +const makeVersionFirstComparator: ShareComparatorFactory = + (versions, useTreesShaking) => (prev, cur) => { + if (useTreesShaking) { + if (!versions[prev].treeShaking) return true; + if (!versions[cur].treeShaking) return false; + return !isLoaded(versions[prev].treeShaking) && versionLt(prev, cur); + } + return !isLoaded(versions[prev]) && versionLt(prev, cur); + }; + +// Step 5b & 5c: findSingletonVersionOrderByVersion, findSingletonVersionOrderByLoaded, +// and getFindShareFunction removed; callbacks inlined directly in getRegisteredShare. + +export function getRegisteredShare( + localShareScopeMap: ShareScopeMap, + pkgName: string, + shareInfo: Shared, + resolveShare: SyncWaterfallHook<{ + shareScopeMap: ShareScopeMap; + scope: string; + pkgName: string; + version: string; + shareInfo: Shared; + GlobalFederation: Federation; + resolver: () => { shared: Shared; useTreesShaking: boolean } | undefined; + }>, +): { shared: Shared; useTreesShaking: boolean } | void { + if (!localShareScopeMap) { + return; + } + const { + shareConfig, + scope = DEFAULT_SCOPE, + strategy, + treeShaking, + } = shareInfo; + const scopes = Array.isArray(scope) ? scope : [scope]; + + const comparatorFactory = + strategy === 'loaded-first' + ? makeLoadedFirstComparator + : makeVersionFirstComparator; + + for (const sc of scopes) { + if ( + shareConfig && + localShareScopeMap[sc] && + localShareScopeMap[sc][pkgName] + ) { + const { requiredVersion } = shareConfig; + const { version: maxOrSingletonVersion, useTreesShaking } = + findSingletonVersion( + localShareScopeMap, + sc, + pkgName, + treeShaking, + comparatorFactory, + ); + + // Step 5c: simplified defaultResolver + const defaultResolver = () => { + const shared = localShareScopeMap[sc][pkgName][maxOrSingletonVersion]; + if (shareConfig.singleton) { + if ( + typeof requiredVersion === 'string' && + !satisfy(maxOrSingletonVersion, requiredVersion) + ) { + const msg = `Version ${maxOrSingletonVersion} from ${ + maxOrSingletonVersion && shared.from + } of shared singleton module ${pkgName} does not satisfy the requirement of ${ + shareInfo.from + } which needs ${requiredVersion})`; + + if (shareConfig.strictVersion) { + error(msg); + } else { + warn(msg); + } + } + return { shared, useTreesShaking }; + } + + if (requiredVersion === false || requiredVersion === '*') { + return { shared, useTreesShaking }; + } + if (satisfy(maxOrSingletonVersion, requiredVersion)) { + return { shared, useTreesShaking }; + } + + const _usedTreeShaking = shouldUseTreeShaking(treeShaking); + for (const useTs of _usedTreeShaking ? [true, false] : [false]) { + for (const [versionKey, versionValue] of Object.entries( + localShareScopeMap[sc][pkgName], + )) { + if ( + useTs && + !shouldUseTreeShaking( + versionValue.treeShaking, + treeShaking?.usedExports, + ) + ) + continue; + if (satisfy(versionKey, requiredVersion)) { + return { shared: versionValue, useTreesShaking: useTs }; + } + } + } + return; + }; + const params = { + shareScopeMap: localShareScopeMap, + scope: sc, + pkgName, + version: maxOrSingletonVersion, + GlobalFederation: Global.__FEDERATION__, + shareInfo, + resolver: defaultResolver, + }; + const resolveShared = resolveShare.emit(params) || params; + return resolveShared.resolver(); + } + } +} + +export function getGlobalShareScope(): GlobalShareScopeMap { + return Global.__FEDERATION__.__SHARE__; +} + +export function getTargetSharedOptions(options: { + pkgName: string; + extraOptions?: LoadShareExtraOptions; + shareInfos: ShareInfos; +}) { + const { pkgName, extraOptions, shareInfos } = options; + const defaultResolver = (sharedOptions: ShareInfos[string]) => { + if (!sharedOptions) return undefined; + const shareVersionMap = Object.fromEntries( + sharedOptions.map((shared) => [shared.version, shared]), + ) as ShareScopeMap[string][string]; + const maxVersion = findVersion( + shareVersionMap, + (prev, cur) => !isLoaded(shareVersionMap[prev]) && versionLt(prev, cur), + ); + return shareVersionMap[maxVersion]; + }; + + const resolver = extraOptions?.resolver ?? defaultResolver; + + const merge = >( + ...sources: Array | undefined> + ): T => { + const out = {} as T; + for (const src of sources) { + if (!src) continue; + for (const [k, v] of Object.entries(src)) { + (out as any)[k] = + isPlainObject((out as any)[k]) && isPlainObject(v) + ? merge((out as any)[k], v) + : v !== undefined + ? v + : (out as any)[k]; + } + } + return out; + }; + + return merge(resolver(shareInfos[pkgName]), extraOptions?.customShareInfo); +} + +export const addUseIn = ( + shared: { useIn?: Array }, + from: string, +): void => { + shared.useIn ??= []; + addUniqueItem(shared.useIn, from); +}; + +export function directShare( + shared: Shared, + useTreesShaking?: boolean, +): Shared | TreeShakingArgs { + if (useTreesShaking && shared.treeShaking) return shared.treeShaking; + return shared; +} + +// --- SharedHandler and supporting functions --- + +function resolveRegistered( + handler: SharedHandler, + pkgName: string, + shareOptions: Shared, +) { + return ( + getRegisteredShare( + handler.shareScopeMap, + pkgName, + shareOptions, + handler.hooks.lifecycle.resolveShare, + ) || { shared: undefined, useTreesShaking: undefined } + ); +} + +function loadSharedAsync( + handler: SharedHandler, + pkgName: string, + shared: Shared, + targetShared: Shared | TreeShakingArgs, + useTreeShaking: boolean | undefined, + hostName: string, + resolveGlobal: boolean, +) { + const loading = (async () => { + const factory = await targetShared.get!(); + targetShared.lib = factory; + targetShared.loaded = true; + addUseIn(targetShared, hostName); + if (resolveGlobal) { + const { shared: gShared, useTreesShaking: gUseTreeShaking } = + resolveRegistered(handler, pkgName, shared); + if (gShared) { + const targetGShared = directShare(gShared, gUseTreeShaking); + targetGShared.lib = factory; + targetGShared.loaded = true; + gShared.from = shared.from; + } + } + return factory as () => T; + })(); + handler.setShared({ + pkgName, + loaded: false, + shared, + from: hostName, + lib: null, + loading, + treeShaking: useTreeShaking ? (targetShared as TreeShakingArgs) : undefined, + }); + return loading; +} + +const loadShareEffect = ( + handler: SharedHandler, + pkgName: string, + extraOptions?: { + customShareInfo?: Partial; + resolver?: (sharedOptions: ShareInfos[string]) => Shared; + }, +): Effect.Effect T | undefined)> => + Effect.gen(function* () { + const { host } = handler; + const hostName = host.options.name; + + const shareOptions = getTargetSharedOptions({ + pkgName, + extraOptions, + shareInfos: host.options.shared, + }); + + if (shareOptions?.scope) { + yield* Effect.forEach( + shareOptions.scope, + (shareScope: string) => + Effect.promise(() => + Promise.all( + handler.initializeSharing(shareScope, { + strategy: shareOptions.strategy, + }), + ), + ), + { concurrency: 'parallel' }, + ); + } + + const loadShareRes = yield* Effect.promise(() => + handler.hooks.lifecycle.beforeLoadShare.emit({ + pkgName, + shareInfo: shareOptions, + shared: host.options.shared, + origin: host, + }), + ); + + const { shareInfo: shareOptionsRes } = loadShareRes; + + assert( + shareOptionsRes, + `Cannot find ${pkgName} Share in the ${hostName}. Please ensure that the ${pkgName} Share parameters have been injected`, + ); + + const { shared: registeredShared, useTreesShaking } = resolveRegistered( + handler, + pkgName, + shareOptionsRes, + ); + + if (registeredShared) { + const targetShared = directShare(registeredShared, useTreesShaking); + if (targetShared.lib) { + addUseIn(targetShared, hostName); + return targetShared.lib as () => T; + } else if (targetShared.loading && !targetShared.loaded) { + const factory = yield* Effect.promise(() => targetShared.loading!); + targetShared.loaded = true; + if (!targetShared.lib) { + targetShared.lib = factory; + } + addUseIn(targetShared, hostName); + return factory; + } else { + return yield* Effect.promise(() => + loadSharedAsync( + handler, + pkgName, + registeredShared, + targetShared, + useTreesShaking, + hostName, + false, + ), + ); + } + } else { + if (extraOptions?.customShareInfo) { + return false; + } + const _useTreeShaking = shouldUseTreeShaking(shareOptionsRes.treeShaking); + const targetShared = directShare(shareOptionsRes, _useTreeShaking); + return yield* Effect.promise(() => + loadSharedAsync( + handler, + pkgName, + shareOptionsRes, + targetShared, + _useTreeShaking, + hostName, + true, + ), + ); + } + }); export class SharedHandler { host: ModuleFederation; @@ -55,8 +592,6 @@ export class SharedHandler { shared: Options['shared']; origin: ModuleFederation; }>('beforeLoadShare'), - // not used yet - loadShare: new AsyncHook<[ModuleFederation, string, ShareInfos]>(), resolveShare: new SyncWaterfallHook<{ shareScopeMap: ShareScopeMap; scope: string; @@ -66,7 +601,6 @@ export class SharedHandler { GlobalFederation: Federation; resolver: () => { shared: Shared; useTreesShaking: boolean } | undefined; }>('resolveShare'), - // maybe will change, temporarily for internal use only initContainerShareScopeMap: new SyncWaterfallHook<{ shareScope: ShareScopeMap[string]; options: Options; @@ -90,9 +624,7 @@ export class SharedHandler { userOptions, ); - const sharedKeys = Object.keys(newShareInfos); - sharedKeys.forEach((sharedKey) => { - const sharedVals = newShareInfos[sharedKey]; + Object.entries(newShareInfos).forEach(([sharedKey, sharedVals]) => { sharedVals.forEach((sharedVal) => { sharedVal.scope.forEach((sc) => { this.hooks.lifecycle.beforeRegisterShare.emit({ @@ -100,8 +632,7 @@ export class SharedHandler { pkgName: sharedKey, shared: sharedVal, }); - const registeredShared = this.shareScopeMap[sc]?.[sharedKey]; - if (!registeredShared) { + if (!this.shareScopeMap[sc]?.[sharedKey]) { this.setShared({ pkgName: sharedKey, lib: sharedVal.lib, @@ -128,129 +659,7 @@ export class SharedHandler { resolver?: (sharedOptions: ShareInfos[string]) => Shared; }, ): Promise T | undefined)> { - const { host } = this; - // This function performs the following steps: - // 1. Checks if the currently loaded share already exists, if not, it throws an error - // 2. Searches globally for a matching share, if found, it uses it directly - // 3. If not found, it retrieves it from the current share and stores the obtained share globally. - - const shareOptions = getTargetSharedOptions({ - pkgName, - extraOptions, - shareInfos: host.options.shared, - }); - - if (shareOptions?.scope) { - await Promise.all( - shareOptions.scope.map(async (shareScope) => { - await Promise.all( - this.initializeSharing(shareScope, { - strategy: shareOptions.strategy, - }), - ); - return; - }), - ); - } - const loadShareRes = await this.hooks.lifecycle.beforeLoadShare.emit({ - pkgName, - shareInfo: shareOptions, - shared: host.options.shared, - origin: host, - }); - - const { shareInfo: shareOptionsRes } = loadShareRes; - - // Assert that shareInfoRes exists, if not, throw an error - assert( - shareOptionsRes, - `Cannot find ${pkgName} Share in the ${host.options.name}. Please ensure that the ${pkgName} Share parameters have been injected`, - ); - - const { shared: registeredShared, useTreesShaking } = - getRegisteredShare( - this.shareScopeMap, - pkgName, - shareOptionsRes, - this.hooks.lifecycle.resolveShare, - ) || {}; - - if (registeredShared) { - const targetShared = directShare(registeredShared, useTreesShaking); - if (targetShared.lib) { - addUseIn(targetShared, host.options.name); - return targetShared.lib as () => T; - } else if (targetShared.loading && !targetShared.loaded) { - const factory = await targetShared.loading; - targetShared.loaded = true; - if (!targetShared.lib) { - targetShared.lib = factory; - } - addUseIn(targetShared, host.options.name); - return factory; - } else { - const asyncLoadProcess = async () => { - const factory = await targetShared.get!(); - addUseIn(targetShared, host.options.name); - targetShared.loaded = true; - targetShared.lib = factory; - return factory as () => T; - }; - const loading = asyncLoadProcess(); - this.setShared({ - pkgName, - loaded: false, - shared: registeredShared, - from: host.options.name, - lib: null, - loading, - treeShaking: useTreesShaking - ? (targetShared as TreeShakingArgs) - : undefined, - }); - return loading; - } - } else { - if (extraOptions?.customShareInfo) { - return false; - } - const _useTreeShaking = shouldUseTreeShaking(shareOptionsRes.treeShaking); - const targetShared = directShare(shareOptionsRes, _useTreeShaking); - - const asyncLoadProcess = async () => { - const factory = await targetShared.get!(); - targetShared.lib = factory; - targetShared.loaded = true; - addUseIn(targetShared, host.options.name); - const { shared: gShared, useTreesShaking: gUseTreeShaking } = - getRegisteredShare( - this.shareScopeMap, - pkgName, - shareOptionsRes, - this.hooks.lifecycle.resolveShare, - ) || {}; - if (gShared) { - const targetGShared = directShare(gShared, gUseTreeShaking); - targetGShared.lib = factory; - targetGShared.loaded = true; - gShared.from = shareOptionsRes.from; - } - return factory as () => T; - }; - const loading = asyncLoadProcess(); - this.setShared({ - pkgName, - loaded: false, - shared: shareOptionsRes, - from: host.options.name, - lib: null, - loading, - treeShaking: _useTreeShaking - ? (targetShared as TreeShakingArgs) - : undefined, - }); - return loading; - } + return Effect.runPromise(loadShareEffect(this, pkgName, extraOptions)); } /** @@ -274,7 +683,7 @@ export class SharedHandler { const promises: Promise[] = []; if (from !== 'build') { const { initTokens } = this; - if (!initScope) initScope = []; + initScope ??= []; let initToken = initTokens[shareScopeName]; if (!initToken) initToken = initTokens[shareScopeName] = { from: this.host.name }; @@ -282,31 +691,19 @@ export class SharedHandler { initScope.push(initToken); } - const shareScope = this.shareScopeMap; const hostName = host.options.name; - // Creates a new share scope if necessary - if (!shareScope[shareScopeName]) { - shareScope[shareScopeName] = {}; - } - // Executes all initialization snippets from all accessible modules - const scope = shareScope[shareScopeName]; + const scope = (this.shareScopeMap[shareScopeName] ||= {}); const register = (name: string, shared: Shared) => { const { version, eager } = shared; - scope[name] = scope[name] || {}; - const versions = scope[name]; - const activeVersion: Shared = + const versions = (scope[name] ||= {}); + const av = versions[version] && (directShare(versions[version]) as Shared); - const activeVersionEager = Boolean( - activeVersion && - (('eager' in activeVersion && activeVersion.eager) || - ('shareConfig' in activeVersion && - activeVersion.shareConfig?.eager)), - ); + const avEager = av && (av.eager || av.shareConfig?.eager); if ( - !activeVersion || - (activeVersion.strategy !== 'loaded-first' && - !activeVersion.loaded && - (Boolean(!eager) !== !activeVersionEager + !av || + (av.strategy !== 'loaded-first' && + !av.loaded && + (Boolean(!eager) !== !avEager ? eager : hostName > versions[version].from)) ) { @@ -339,14 +736,10 @@ export class SharedHandler { } }; Object.keys(host.options.shared).forEach((shareName) => { - const sharedArr = host.options.shared[shareName]; - sharedArr.forEach((shared) => { - if (shared.scope.includes(shareScopeName)) { - register(shareName, shared); - } + host.options.shared[shareName].forEach((shared) => { + if (shared.scope.includes(shareScopeName)) register(shareName, shared); }); }); - // TODO: strategy==='version-first' need to be removed in the future if ( host.options.shareStrategy === 'version-first' || strategy === 'version-first' @@ -365,6 +758,49 @@ export class SharedHandler { // 1. If the loaded shared already exists globally, then it will be reused // 2. If lib exists in local shared, it will be used directly // 3. If the local get returns something other than Promise, then it will be used directly + private resolveSyncShared( + pkgName: string, + shared: Shared | undefined, + hostName: string, + options: { + allowPromise: boolean; + shareOptions?: Shared; + from?: 'build' | 'runtime'; + }, + ): (() => T) | undefined { + if (!shared) return; + if (typeof shared.lib === 'function') { + addUseIn(shared, hostName); + if (!shared.loaded) { + shared.loaded = true; + if (options.shareOptions && shared.from === hostName) + options.shareOptions.loaded = true; + } + return shared.lib as () => T; + } + if (typeof shared.get !== 'function') return; + const module = shared.get(); + if (module instanceof Promise) { + if (!options.allowPromise) { + const errorCode = options.from === 'build' ? RUNTIME_005 : RUNTIME_006; + throw new Error( + runtimeError(errorCode, { hostName, sharedPkgName: pkgName }), + ); + } + return; + } + addUseIn(shared, hostName); + shared.lib = module; + this.setShared({ + pkgName, + loaded: true, + from: hostName, + lib: module, + shared, + }); + return module as () => T; + } + loadShareSync( pkgName: string, extraOptions?: { @@ -385,7 +821,7 @@ export class SharedHandler { this.initializeSharing(shareScope, { strategy: shareOptions.strategy }); }); } - const { shared: registeredShared, useTreesShaking } = + const { shared: registeredShared } = getRegisteredShare( this.shareScopeMap, pkgName, @@ -393,69 +829,29 @@ export class SharedHandler { this.hooks.lifecycle.resolveShare, ) || {}; - if (registeredShared) { - if (typeof registeredShared.lib === 'function') { - addUseIn(registeredShared, host.options.name); - if (!registeredShared.loaded) { - registeredShared.loaded = true; - if (registeredShared.from === host.options.name) { - shareOptions.loaded = true; - } - } - return registeredShared.lib as () => T; - } - if (typeof registeredShared.get === 'function') { - const module = registeredShared.get(); - if (!(module instanceof Promise)) { - addUseIn(registeredShared, host.options.name); - this.setShared({ - pkgName, - loaded: true, - from: host.options.name, - lib: module, - shared: registeredShared, - }); - return module; - } - } - } - - if (shareOptions.lib) { - if (!shareOptions.loaded) { - shareOptions.loaded = true; - } - return shareOptions.lib as () => T; - } - - if (shareOptions.get) { - const module = shareOptions.get(); - - if (module instanceof Promise) { - const errorCode = - extraOptions?.from === 'build' ? RUNTIME_005 : RUNTIME_006; - throw new Error( - getShortErrorMsg(errorCode, runtimeDescMap, { - hostName: host.options.name, - sharedPkgName: pkgName, - }), - ); - } - - shareOptions.lib = module; + const hostName = host.options.name; + const resolvedFromGlobal = this.resolveSyncShared( + pkgName, + registeredShared, + hostName, + { allowPromise: true, shareOptions }, + ); + if (resolvedFromGlobal) return resolvedFromGlobal; - this.setShared({ - pkgName, - loaded: true, - from: host.options.name, - lib: shareOptions.lib, - shared: shareOptions, - }); - return shareOptions.lib as () => T; - } + const resolvedLocal = this.resolveSyncShared( + pkgName, + shareOptions, + hostName, + { + allowPromise: false, + from: extraOptions?.from, + }, + ); + if (resolvedLocal) return resolvedLocal; throw new Error( - getShortErrorMsg(RUNTIME_006, runtimeDescMap, { - hostName: host.options.name, + runtimeError(RUNTIME_006, { + hostName, sharedPkgName: pkgName, }), ); @@ -477,7 +873,7 @@ export class SharedHandler { }); } - private setShared({ + setShared({ pkgName, shared, from, @@ -498,32 +894,10 @@ export class SharedHandler { }): void { const { version, scope = 'default', ...shareInfo } = shared; const scopes: string[] = Array.isArray(scope) ? scope : [scope]; - - const mergeAttrs = (shared: Shared) => { - const merge = ( - s: TreeShakingArgs, - key: K, - val: TreeShakingArgs[K], - ): void => { - if (val && !s[key]) { - s[key] = val; - } - }; - const targetShared = ( - treeShaking ? shared.treeShaking! : shared - ) as TreeShakingArgs; - merge(targetShared, 'loaded', loaded); - merge(targetShared, 'loading', loading); - merge(targetShared, 'get', get); - }; - scopes.forEach((sc) => { - if (!this.shareScopeMap[sc]) { - this.shareScopeMap[sc] = {}; - } - if (!this.shareScopeMap[sc][pkgName]) { + for (const sc of scopes) { + if (!this.shareScopeMap[sc]) this.shareScopeMap[sc] = {}; + if (!this.shareScopeMap[sc][pkgName]) this.shareScopeMap[sc][pkgName] = {}; - } - if (!this.shareScopeMap[sc][pkgName][version]) { this.shareScopeMap[sc][pkgName][version] = { version, @@ -532,13 +906,13 @@ export class SharedHandler { lib, }; } - - const registeredShared = this.shareScopeMap[sc][pkgName][version]; - mergeAttrs(registeredShared); - if (from && registeredShared.from !== from) { - registeredShared.from = from; - } - }); + const reg = this.shareScopeMap[sc][pkgName][version]; + const target = (treeShaking ? reg.treeShaking! : reg) as TreeShakingArgs; + if (loaded && !target.loaded) target.loaded = loaded; + if (loading && !target.loading) target.loading = loading; + if (get && !target.get) target.get = get; + if (from && reg.from !== from) reg.from = from; + } } private _setGlobalShareScopeMap(hostOptions: Options): void { diff --git a/packages/runtime-core/src/utils/hooks.ts b/packages/runtime-core/src/utils/hooks.ts new file mode 100644 index 00000000000..9cd1a79cebf --- /dev/null +++ b/packages/runtime-core/src/utils/hooks.ts @@ -0,0 +1,215 @@ +import type { ModuleFederation } from '../core'; +import { error, warn } from './logger'; +import { isObject, isPlainObject } from './tool'; +import { assert } from './logger'; + +// --- Types --- + +export type Callback = (...args: ArgsType) => K; +export type ArgsType = T extends Array ? T : Array; + +const checkReturnData = (orig: any, ret: any): boolean => + isObject(ret) && (orig === ret || Object.keys(orig).every((k) => k in ret)); + +// --- Unified Hook class --- + +type EmitStrategy = 'sync' | 'async' | 'syncWaterfall' | 'asyncWaterfall'; + +class Hook { + listeners = new Set>(); + onerror: (errMsg: string | Error | unknown) => void = error; + protected strategy: EmitStrategy; + + constructor( + strategy: EmitStrategy, + public type = '', + ) { + this.strategy = strategy; + } + + on(fn: Callback): void { + if (typeof fn === 'function') { + this.listeners.add(fn); + } + } + + once(fn: Callback): void { + const wrapper = (...args: ArgsType) => { + this.remove(wrapper as Callback); + return fn.apply(null, args); + }; + this.on(wrapper as Callback); + } + + remove(fn: Callback): void { + this.listeners.delete(fn); + } + + removeAll(): void { + this.listeners.clear(); + } + + emit(...data: ArgsType): any { + switch (this.strategy) { + case 'sync': + return this._emitSync(data); + case 'async': + return this._emitAsync(data); + case 'syncWaterfall': + return this._emitSyncWaterfall(data[0]); + case 'asyncWaterfall': + return this._emitAsyncWaterfall(data[0]); + } + } + + private _emitSync(data: ArgsType): void | K { + let result: void | K = undefined; + for (const fn of this.listeners) result = fn(...data); + return result; + } + + private _emitAsync(data: ArgsType): Promise { + const ls = Array.from(this.listeners); + if (!ls.length) return Promise.resolve(undefined as void | false | K); + let i = 0; + const call = (prev?: any): any => + prev === false + ? false + : i < ls.length + ? Promise.resolve(ls[i++].apply(null, data)).then(call) + : prev; + return Promise.resolve(call()); + } + + private _emitSyncWaterfall(data: any): any { + if (!isObject(data)) { + error(`The data for the "${this.type}" hook should be an object.`); + } + for (const fn of this.listeners) { + try { + const tempData = (fn as any)(data); + if (checkReturnData(data, tempData)) { + data = tempData; + } else { + this.onerror( + `A plugin returned an unacceptable value for the "${this.type}" type.`, + ); + break; + } + } catch (e) { + warn(e); + this.onerror(e); + } + } + return data; + } + + private _emitAsyncWaterfall(data: any): Promise { + if (!isObject(data)) + error(`The response data for the "${this.type}" hook must be an object.`); + const ls = Array.from(this.listeners); + if (!ls.length) return Promise.resolve(data); + let i = 0; + const onError = (e: any) => { + warn(e); + this.onerror(e); + return data; + }; + const call = (prevData: any): any => { + if (checkReturnData(data, prevData)) { + data = prevData; + if (i < ls.length) { + try { + return Promise.resolve((ls[i++] as any)(data)).then(call, onError); + } catch (e) { + return onError(e); + } + } + } else { + this.onerror( + `A plugin returned an incorrect value for the "${this.type}" type.`, + ); + } + return data; + }; + return Promise.resolve(call(data)); + } +} + +// --- Exported hook subclasses (backward-compatible with `new SyncHook(...)`) --- + +export class SyncHook extends Hook { + constructor(type?: string) { + super('sync', type); + } + declare emit: (...data: ArgsType) => void | K; +} + +export class AsyncHook< + T, + K = void | false | Promise, +> extends Hook { + constructor(type?: string) { + super('async', type); + } + declare emit: (...data: ArgsType) => Promise; +} + +export class SyncWaterfallHook> extends Hook< + [T], + T +> { + constructor(type: string) { + super('syncWaterfall', type); + } + declare emit: (data: T) => T; +} + +export class AsyncWaterfallHook> extends Hook< + [T], + T | Promise +> { + constructor(type: string) { + super('asyncWaterfall', type); + } + declare emit: (data: T) => Promise; +} + +// --- Plugin System --- + +export type Plugin> = { + [k in keyof T]?: Parameters[0]; +} & { + name: string; + version?: string; + apply?: (instance: ModuleFederation) => void; +}; + +export class PluginSystem> { + lifecycle: T; + registerPlugins: Record> = {}; + + constructor(lifecycle: T) { + this.lifecycle = lifecycle; + } + + applyPlugin(plugin: Plugin, instance: ModuleFederation): void { + assert(isPlainObject(plugin), 'Plugin configuration is invalid.'); + assert(plugin.name, 'A name must be provided by the plugin.'); + if (this.registerPlugins[plugin.name]) return; + this.registerPlugins[plugin.name] = plugin; + plugin.apply?.(instance); + for (const key of Object.keys(this.lifecycle)) { + if (plugin[key as string]) this.lifecycle[key].on(plugin[key as string]); + } + } + + removePlugin(pluginName: string): void { + assert(pluginName, 'A name is required.'); + const plugin = this.registerPlugins[pluginName]; + assert(plugin, `The plugin "${pluginName}" is not registered.`); + for (const key of Object.keys(plugin)) { + if (key !== 'name') this.lifecycle[key]?.remove(plugin[key as string]); + } + } +} diff --git a/packages/runtime-core/src/utils/hooks/asyncHook.ts b/packages/runtime-core/src/utils/hooks/asyncHook.ts deleted file mode 100644 index d9692b2d914..00000000000 --- a/packages/runtime-core/src/utils/hooks/asyncHook.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ArgsType, SyncHook } from './syncHook'; - -type CallbackReturnType = void | false | Promise; - -export class AsyncHook< - T, - ExternalEmitReturnType = CallbackReturnType, -> extends SyncHook { - override emit( - ...data: ArgsType - ): Promise { - let result; - const ls = Array.from(this.listeners); - if (ls.length > 0) { - let i = 0; - const call = (prev?: any): any => { - if (prev === false) { - return false; // Abort process - } else if (i < ls.length) { - return Promise.resolve(ls[i++].apply(null, data)).then(call); - } else { - return prev; - } - }; - result = call(); - } - return Promise.resolve(result); - } -} diff --git a/packages/runtime-core/src/utils/hooks/asyncWaterfallHooks.ts b/packages/runtime-core/src/utils/hooks/asyncWaterfallHooks.ts deleted file mode 100644 index 3660d449444..00000000000 --- a/packages/runtime-core/src/utils/hooks/asyncWaterfallHooks.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { error, warn } from '../logger'; -import { isObject } from '../tool'; -import { SyncHook } from './syncHook'; -import { checkReturnData } from './syncWaterfallHook'; - -type CallbackReturnType = T | Promise; - -export class AsyncWaterfallHook> extends SyncHook< - [T], - CallbackReturnType -> { - onerror: (errMsg: string | Error | unknown) => void = error; - constructor(type: string) { - super(); - this.type = type; - } - - override emit(data: T): Promise { - if (!isObject(data)) { - error(`The response data for the "${this.type}" hook must be an object.`); - } - const ls = Array.from(this.listeners); - - if (ls.length > 0) { - let i = 0; - const processError = (e: any) => { - warn(e); - this.onerror(e); - return data; - }; - - const call = (prevData: T): any => { - if (checkReturnData(data, prevData)) { - data = prevData as T; - if (i < ls.length) { - try { - return Promise.resolve(ls[i++](data)).then(call, processError); - } catch (e) { - return processError(e); - } - } - } else { - this.onerror( - `A plugin returned an incorrect value for the "${this.type}" type.`, - ); - } - return data; - }; - return Promise.resolve(call(data)); - } - return Promise.resolve(data); - } -} diff --git a/packages/runtime-core/src/utils/hooks/index.ts b/packages/runtime-core/src/utils/hooks/index.ts deleted file mode 100644 index 37a2b4a22e7..00000000000 --- a/packages/runtime-core/src/utils/hooks/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { SyncHook } from './syncHook'; -export { AsyncHook } from './asyncHook'; -export { SyncWaterfallHook } from './syncWaterfallHook'; -export { AsyncWaterfallHook } from './asyncWaterfallHooks'; -export { PluginSystem } from './pluginSystem'; -export type { Plugin } from './pluginSystem'; diff --git a/packages/runtime-core/src/utils/hooks/pluginSystem.ts b/packages/runtime-core/src/utils/hooks/pluginSystem.ts deleted file mode 100644 index e389c758081..00000000000 --- a/packages/runtime-core/src/utils/hooks/pluginSystem.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { ModuleFederation } from '../../core'; -import { assert, isPlainObject } from '../../utils'; - -export type Plugin> = { - [k in keyof T]?: Parameters[0]; -} & { - name: string; - version?: string; - apply?: (instance: ModuleFederation) => void; -}; - -export class PluginSystem> { - lifecycle: T; - lifecycleKeys: Array; - registerPlugins: Record> = {}; - - constructor(lifecycle: T) { - this.lifecycle = lifecycle; - this.lifecycleKeys = Object.keys(lifecycle); - } - - applyPlugin(plugin: Plugin, instance: ModuleFederation): void { - assert(isPlainObject(plugin), 'Plugin configuration is invalid.'); - // The plugin's name is mandatory and must be unique - const pluginName = plugin.name; - assert(pluginName, 'A name must be provided by the plugin.'); - - if (!this.registerPlugins[pluginName]) { - this.registerPlugins[pluginName] = plugin; - plugin.apply?.(instance); - - Object.keys(this.lifecycle).forEach((key) => { - const pluginLife = plugin[key as string]; - if (pluginLife) { - this.lifecycle[key].on(pluginLife); - } - }); - } - } - - removePlugin(pluginName: string): void { - assert(pluginName, 'A name is required.'); - const plugin = this.registerPlugins[pluginName]; - assert(plugin, `The plugin "${pluginName}" is not registered.`); - - Object.keys(plugin).forEach((key) => { - if (key !== 'name') { - this.lifecycle[key].remove(plugin[key as string]); - } - }); - } -} diff --git a/packages/runtime-core/src/utils/hooks/syncHook.ts b/packages/runtime-core/src/utils/hooks/syncHook.ts deleted file mode 100644 index 17f6dadb8c9..00000000000 --- a/packages/runtime-core/src/utils/hooks/syncHook.ts +++ /dev/null @@ -1,48 +0,0 @@ -export type Callback = (...args: ArgsType) => K; -export type ArgsType = T extends Array ? T : Array; - -export class SyncHook { - type = ''; - listeners = new Set>(); - - constructor(type?: string) { - if (type) { - this.type = type; - } - } - - on(fn: Callback): void { - if (typeof fn === 'function') { - this.listeners.add(fn); - } - } - - once(fn: Callback): void { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const self = this; - this.on(function wrapper(...args) { - self.remove(wrapper); - // eslint-disable-next-line prefer-spread - return fn.apply(null, args); - }); - } - - emit(...data: ArgsType): void | K | Promise { - let result; - if (this.listeners.size > 0) { - // eslint-disable-next-line prefer-spread - this.listeners.forEach((fn) => { - result = fn(...data); - }); - } - return result; - } - - remove(fn: Callback): void { - this.listeners.delete(fn); - } - - removeAll(): void { - this.listeners.clear(); - } -} diff --git a/packages/runtime-core/src/utils/hooks/syncWaterfallHook.ts b/packages/runtime-core/src/utils/hooks/syncWaterfallHook.ts deleted file mode 100644 index f37be00302b..00000000000 --- a/packages/runtime-core/src/utils/hooks/syncWaterfallHook.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { error, warn } from '../logger'; -import { isObject } from '../tool'; -import { SyncHook } from './syncHook'; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function checkReturnData(originalData: any, returnedData: any): boolean { - if (!isObject(returnedData)) { - return false; - } - if (originalData !== returnedData) { - // eslint-disable-next-line no-restricted-syntax - for (const key in originalData) { - if (!(key in returnedData)) { - return false; - } - } - } - return true; -} - -export class SyncWaterfallHook> extends SyncHook< - [T], - T -> { - onerror: (errMsg: string | Error | unknown) => void = error; - - constructor(type: string) { - super(); - this.type = type; - } - - override emit(data: T): T { - if (!isObject(data)) { - error(`The data for the "${this.type}" hook should be an object.`); - } - for (const fn of this.listeners) { - try { - const tempData = fn(data); - if (checkReturnData(data, tempData)) { - data = tempData; - } else { - this.onerror( - `A plugin returned an unacceptable value for the "${this.type}" type.`, - ); - break; - } - } catch (e) { - warn(e); - this.onerror(e); - } - } - return data; - } -} diff --git a/packages/runtime-core/src/utils/load.ts b/packages/runtime-core/src/utils/load.ts index a30ec65f45d..e8473d6a8ce 100644 --- a/packages/runtime-core/src/utils/load.ts +++ b/packages/runtime-core/src/utils/load.ts @@ -4,80 +4,104 @@ import { composeKeyWithSeparator, isBrowserEnv, } from '@module-federation/sdk'; +import { Effect } from '@module-federation/micro-effect'; import { DEFAULT_REMOTE_TYPE, DEFAULT_SCOPE } from '../constant'; import { ModuleFederation } from '../core'; import { globalLoading, getRemoteEntryExports } from '../global'; import { Remote, RemoteEntryExports, RemoteInfo } from '../type'; -import { assert } from './logger'; -import { - RUNTIME_001, - RUNTIME_008, - getShortErrorMsg, - runtimeDescMap, -} from '@module-federation/error-codes'; - +import { assert, runtimeError } from './logger'; +import { singleFlight } from './tool'; +import { RUNTIME_001, RUNTIME_008 } from '@module-federation/error-codes'; +import { ScriptLoadFailed } from '../effect/errors'; // Declare the ENV_TARGET constant that will be defined by DefinePlugin declare const ENV_TARGET: 'web' | 'node'; const importCallback = '.then(callbacks[0]).catch(callbacks[1])'; - -async function loadEsmEntry({ - entry, - remoteEntryExports, -}: { - entry: string; - remoteEntryExports: RemoteEntryExports | undefined; -}): Promise { - return new Promise((resolve, reject) => { - try { - if (!remoteEntryExports) { - if (typeof FEDERATION_ALLOW_NEW_FUNCTION !== 'undefined') { - new Function('callbacks', `import("${entry}")${importCallback}`)([ - resolve, - reject, - ]); - } else { - import(/* webpackIgnore: true */ /* @vite-ignore */ entry) - .then(resolve) - .catch(reject); - } - } else { - resolve(remoteEntryExports); - } - } catch (e) { - reject(e); - } - }); +// --- createScriptHook adapters --- +function makeDomCreateScriptAdapter( + loaderHook: ModuleFederation['loaderHook'], +) { + return (url: string, attrs: Record = {}) => { + const res = loaderHook.lifecycle.createScript.emit({ url, attrs }); + if (!res) return; + if (res instanceof HTMLScriptElement) return res; + return 'script' in res || 'timeout' in res ? res : undefined; + }; } - -async function loadSystemJsEntry({ - entry, - remoteEntryExports, -}: { - entry: string; - remoteEntryExports: RemoteEntryExports | undefined; -}): Promise { - return new Promise((resolve, reject) => { - try { - if (!remoteEntryExports) { - //@ts-ignore - if (typeof __system_context__ === 'undefined') { - //@ts-ignore - System.import(entry).then(resolve).catch(reject); - } else { - new Function( - 'callbacks', - `System.import("${entry}")${importCallback}`, - )([resolve, reject]); - } - } else { - resolve(remoteEntryExports); - } - } catch (e) { - reject(e); - } - }); +function makeNodeCreateScriptAdapter( + loaderHook: ModuleFederation['loaderHook'], +) { + return (url: string, attrs: Record = {}) => { + const res = loaderHook.lifecycle.createScript.emit({ url, attrs }); + if (!res) return; + return 'url' in res ? res : undefined; + }; } - +// --- Effect programs for each loader function --- +const createDynamicImportEffect = + ( + doImport: ( + entry: string, + resolve: (value: RemoteEntryExports) => void, + reject: (reason?: any) => void, + ) => void, + errorLabel: string, + ) => + ({ + entry, + remoteEntryExports, + }: { + entry: string; + remoteEntryExports: RemoteEntryExports | undefined; + }): Effect.Effect => + Effect.tryPromise({ + try: () => + new Promise((resolve, reject) => { + try { + if (!remoteEntryExports) { + doImport(entry, resolve, reject); + } else { + resolve(remoteEntryExports); + } + } catch (e) { + reject(e); + } + }), + catch: () => + new ScriptLoadFailed({ + remoteName: errorLabel, + resourceUrl: entry, + }), + }); +const loadEsmEntryEffect = createDynamicImportEffect( + (entry, resolve, reject) => { + if (typeof FEDERATION_ALLOW_NEW_FUNCTION !== 'undefined') { + new Function('callbacks', `import("${entry}")${importCallback}`)([ + resolve, + reject, + ]); + } else { + import(/* webpackIgnore: true */ /* @vite-ignore */ entry) + .then(resolve) + .catch(reject); + } + }, + 'esm-entry', +); +const loadSystemJsEntryEffect = createDynamicImportEffect( + (entry, resolve, reject) => { + //@ts-ignore + if (typeof __system_context__ === 'undefined') { + //@ts-ignore + System.import(entry).then(resolve).catch(reject); + } else { + new Function('callbacks', `System.import("${entry}")${importCallback}`)([ + resolve, + reject, + ]); + } + }, + 'system-entry', +); function handleRemoteEntryLoaded( name: string, globalName: string, @@ -87,20 +111,17 @@ function handleRemoteEntryLoaded( name, globalName, ); - assert( entryExports, - getShortErrorMsg(RUNTIME_001, runtimeDescMap, { + runtimeError(RUNTIME_001, { remoteName: name, remoteEntryUrl: entry, remoteEntryKey, }), ); - return entryExports; } - -async function loadEntryScript({ +const loadEntryScriptEffect = ({ name, globalName, entry, @@ -112,51 +133,30 @@ async function loadEntryScript({ entry: string; loaderHook: ModuleFederation['loaderHook']; getEntryUrl?: (url: string) => string; -}): Promise { - const { entryExports: remoteEntryExports } = getRemoteEntryExports( - name, - globalName, - ); - - if (remoteEntryExports) { - return remoteEntryExports; - } - - // if getEntryUrl is passed, use the getEntryUrl to get the entry url - const url = getEntryUrl ? getEntryUrl(entry) : entry; - return loadScript(url, { - attrs: {}, - createScriptHook: (url, attrs) => { - const res = loaderHook.lifecycle.createScript.emit({ url, attrs }); - - if (!res) return; - - if (res instanceof HTMLScriptElement) { - return res; - } - - if ('script' in res || 'timeout' in res) { - return res; - } - - return; - }, - }) - .then(() => { - return handleRemoteEntryLoaded(name, globalName, entry); - }) - .catch((e) => { - assert( - undefined, - getShortErrorMsg(RUNTIME_008, runtimeDescMap, { +}): Effect.Effect => + Effect.gen(function* () { + const { entryExports: remoteEntryExports } = getRemoteEntryExports( + name, + globalName, + ); + if (remoteEntryExports) { + return remoteEntryExports; + } + const url = getEntryUrl ? getEntryUrl(entry) : entry; + return yield* Effect.tryPromise({ + try: () => + loadScript(url, { + attrs: {}, + createScriptHook: makeDomCreateScriptAdapter(loaderHook), + }).then(() => handleRemoteEntryLoaded(name, globalName, entry)), + catch: () => + new ScriptLoadFailed({ remoteName: name, resourceUrl: entry, }), - ); - throw e; }); -} -async function loadEntryDom({ + }); +const loadEntryDomEffect = ({ remoteInfo, remoteEntryExports, loaderHook, @@ -166,16 +166,16 @@ async function loadEntryDom({ remoteEntryExports?: RemoteEntryExports; loaderHook: ModuleFederation['loaderHook']; getEntryUrl?: (url: string) => string; -}) { +}): Effect.Effect => { const { entry, entryGlobalName: globalName, name, type } = remoteInfo; switch (type) { case 'esm': case 'module': - return loadEsmEntry({ entry, remoteEntryExports }); + return loadEsmEntryEffect({ entry, remoteEntryExports }); case 'system': - return loadSystemJsEntry({ entry, remoteEntryExports }); + return loadSystemJsEntryEffect({ entry, remoteEntryExports }); default: - return loadEntryScript({ + return loadEntryScriptEffect({ entry, globalName, name, @@ -183,54 +183,76 @@ async function loadEntryDom({ getEntryUrl, }); } -} - -async function loadEntryNode({ +}; +const loadEntryNodeEffect = ({ remoteInfo, loaderHook, }: { remoteInfo: RemoteInfo; loaderHook: ModuleFederation['loaderHook']; -}) { +}): Effect.Effect => { const { entry, entryGlobalName: globalName, name, type } = remoteInfo; const { entryExports: remoteEntryExports } = getRemoteEntryExports( name, globalName, ); - if (remoteEntryExports) { - return remoteEntryExports; + return Effect.succeed(remoteEntryExports); } - - return loadScriptNode(entry, { - attrs: { name, globalName, type }, - loaderHook: { - createScriptHook: (url: string, attrs: Record = {}) => { - const res = loaderHook.lifecycle.createScript.emit({ url, attrs }); - - if (!res) return; - - if ('url' in res) { - return res; - } - - return; - }, - }, - }) - .then(() => { - return handleRemoteEntryLoaded(name, globalName, entry); - }) - .catch((e) => { - throw e; + return Effect.tryPromise({ + try: () => + loadScriptNode(entry, { + attrs: { name, globalName, type }, + loaderHook: { + createScriptHook: makeNodeCreateScriptAdapter(loaderHook), + }, + }).then(() => handleRemoteEntryLoaded(name, globalName, entry)), + catch: () => + new ScriptLoadFailed({ + remoteName: name, + resourceUrl: entry, + }), + }); +}; +// --- Effect program for getRemoteEntry --- +const getRemoteEntryEffect = (params: { + origin: ModuleFederation; + remoteInfo: RemoteInfo; + remoteEntryExports?: RemoteEntryExports | undefined; + loaderHook: ModuleFederation['loaderHook']; + loadEntryHook: ModuleFederation['remoteHandler']['hooks']['lifecycle']['loadEntry']; + getEntryUrl?: (url: string) => string; +}): Effect.Effect => + Effect.gen(function* () { + const hookResult = yield* Effect.promise(async () => { + const res = await params.loadEntryHook.emit({ + loaderHook: params.loaderHook, + remoteInfo: params.remoteInfo, + remoteEntryExports: params.remoteEntryExports, + }); + return res as unknown as RemoteEntryExports | undefined; }); -} - + if (hookResult) return hookResult; + const isWeb = + typeof ENV_TARGET !== 'undefined' ? ENV_TARGET === 'web' : isBrowserEnv(); + if (isWeb) { + return yield* loadEntryDomEffect({ + remoteInfo: params.remoteInfo, + remoteEntryExports: params.remoteEntryExports, + loaderHook: params.loaderHook, + getEntryUrl: params.getEntryUrl, + }); + } + return yield* loadEntryNodeEffect({ + remoteInfo: params.remoteInfo, + loaderHook: params.loaderHook, + }); + }); +// --- Public API (signatures unchanged) --- export function getRemoteEntryUniqueKey(remoteInfo: RemoteInfo): string { const { entry, name } = remoteInfo; return composeKeyWithSeparator(name, entry); } - export async function getRemoteEntry(params: { origin: ModuleFederation; remoteInfo: RemoteInfo; @@ -249,69 +271,76 @@ export async function getRemoteEntry(params: { if (remoteEntryExports) { return remoteEntryExports; } - - if (!globalLoading[uniqueKey]) { + return singleFlight(globalLoading, uniqueKey, () => { const loadEntryHook = origin.remoteHandler.hooks.lifecycle.loadEntry; const loaderHook = origin.loaderHook; - - globalLoading[uniqueKey] = loadEntryHook - .emit({ - loaderHook, - remoteInfo, - remoteEntryExports, - }) - .then((res) => { - if (res) { - return res; - } - // Use ENV_TARGET if defined, otherwise fallback to isBrowserEnv, must keep this - const isWebEnvironment = - typeof ENV_TARGET !== 'undefined' - ? ENV_TARGET === 'web' - : isBrowserEnv(); - - return isWebEnvironment - ? loadEntryDom({ - remoteInfo, - remoteEntryExports, - loaderHook, - getEntryUrl, - }) - : loadEntryNode({ remoteInfo, loaderHook }); - }) - .catch(async (err) => { - const uniqueKey = getRemoteEntryUniqueKey(remoteInfo); - const isScriptLoadError = - err instanceof Error && err.message.includes(RUNTIME_008); - - if (isScriptLoadError && !_inErrorHandling) { - const wrappedGetRemoteEntry = ( - params: Parameters[0], - ) => { - return getRemoteEntry({ ...params, _inErrorHandling: true }); - }; - - const RemoteEntryExports = - await origin.loaderHook.lifecycle.loadEntryError.emit({ - getRemoteEntry: wrappedGetRemoteEntry, - origin, - remoteInfo: remoteInfo, - remoteEntryExports, - globalLoading, - uniqueKey, - }); - - if (RemoteEntryExports) { - return RemoteEntryExports; + const markLoadEntryError = (error: Error) => { + (error as Error & { __mfLoadEntryError?: true }).__mfLoadEntryError = + true; + return error; + }; + const recoverFromLoadEntryError = ( + resourceUrl: string | undefined, + remoteName: string, + ) => + Effect.tryPromise({ + try: async () => { + if (!_inErrorHandling) { + const recovered = + await origin.loaderHook.lifecycle.loadEntryError.emit({ + getRemoteEntry: (p) => + getRemoteEntry({ ...p, _inErrorHandling: true }), + origin, + remoteInfo, + remoteEntryExports, + globalLoading, + uniqueKey, + }); + if (recovered) + return recovered as RemoteEntryExports | void | undefined; } - } - throw err; + throw markLoadEntryError( + new Error( + runtimeError(RUNTIME_008, { + remoteName, + resourceUrl, + }), + ), + ); + }, + catch: (e) => e as Error, }); - } - - return globalLoading[uniqueKey]; + // Run the Effect program, bridging back to Promise for the globalLoading cache + const effectProgram = getRemoteEntryEffect({ + origin, + remoteInfo, + remoteEntryExports, + loaderHook, + loadEntryHook, + getEntryUrl, + }).pipe( + Effect.catchTag('ScriptLoadFailed', (err) => + recoverFromLoadEntryError(err.resourceUrl, err.remoteName), + ), + Effect.catch((err) => { + if ( + !(err as any)?.__mfLoadEntryError && + err instanceof Error && + err.message.includes(RUNTIME_008) + ) { + return recoverFromLoadEntryError( + getEntryUrl ? getEntryUrl(remoteInfo.entry) : remoteInfo.entry, + remoteInfo.name, + ); + } + return Effect.fail(err as Error); + }), + ); + return Effect.runPromise(effectProgram).catch((err) => { + throw err; + }) as Promise; + }); } - export function getRemoteInfo(remote: Remote): RemoteInfo { return { ...remote, diff --git a/packages/runtime-core/src/utils/logger.ts b/packages/runtime-core/src/utils/logger.ts index b9b61d342b0..ce847f76996 100644 --- a/packages/runtime-core/src/utils/logger.ts +++ b/packages/runtime-core/src/utils/logger.ts @@ -1,4 +1,8 @@ import { createLogger } from '@module-federation/sdk'; +import { + getShortErrorMsg, + runtimeDescMap, +} from '@module-federation/error-codes'; const LOG_CATEGORY = '[ Federation Runtime ]'; // FIXME: pre-bundle ? @@ -38,4 +42,12 @@ export function log(...args: unknown[]) { logger.log(...args); } +export function runtimeError( + code: string, + context: Record, + suffix?: string, +): string { + return getShortErrorMsg(code, runtimeDescMap, context, suffix); +} + export { logger }; diff --git a/packages/runtime-core/src/utils/manifest.ts b/packages/runtime-core/src/utils/manifest.ts index 0f150aa645b..6f2d949282c 100644 --- a/packages/runtime-core/src/utils/manifest.ts +++ b/packages/runtime-core/src/utils/manifest.ts @@ -1,81 +1,36 @@ import { Remote } from '../type'; -// Function to match a remote with its name and expose -// id: pkgName(@federation/app1) + expose(button) = @federation/app1/button -// id: alias(app1) + expose(button) = app1/button -// id: alias(app1/utils) + expose(loadash/sort) = app1/utils/loadash/sort +function tryMatch( + id: string, + prefix: string, + remote: Remote, +): { pkgNameOrAlias: string; expose: string; remote: Remote } | undefined { + if (!id.startsWith(prefix)) return; + const rest = id.slice(prefix.length); + if (rest === '') return { pkgNameOrAlias: prefix, expose: '.', remote }; + if (rest.startsWith('/')) + return { pkgNameOrAlias: prefix, expose: `.${rest}`, remote }; + return undefined; +} + export function matchRemoteWithNameAndExpose( remotes: Array, id: string, -): - | { - pkgNameOrAlias: string; - expose: string; - remote: Remote; - } - | undefined { +): { pkgNameOrAlias: string; expose: string; remote: Remote } | undefined { for (const remote of remotes) { - // match pkgName - const isNameMatched = id.startsWith(remote.name); - let expose = id.replace(remote.name, ''); - if (isNameMatched) { - if (expose.startsWith('/')) { - const pkgNameOrAlias = remote.name; - expose = `.${expose}`; - return { - pkgNameOrAlias, - expose, - remote, - }; - } else if (expose === '') { - return { - pkgNameOrAlias: remote.name, - expose: '.', - remote, - }; - } - } - - // match alias - const isAliasMatched = remote.alias && id.startsWith(remote.alias); - let exposeWithAlias = remote.alias && id.replace(remote.alias, ''); - if (remote.alias && isAliasMatched) { - if (exposeWithAlias && exposeWithAlias.startsWith('/')) { - const pkgNameOrAlias = remote.alias; - exposeWithAlias = `.${exposeWithAlias}`; - return { - pkgNameOrAlias, - expose: exposeWithAlias, - remote, - }; - } else if (exposeWithAlias === '') { - return { - pkgNameOrAlias: remote.alias, - expose: '.', - remote, - }; - } + const byName = tryMatch(id, remote.name, remote); + if (byName) return byName; + if (remote.alias) { + const byAlias = tryMatch(id, remote.alias, remote); + if (byAlias) return byAlias; } } - - return; + return undefined; } -// Function to match a remote with its name or alias export function matchRemote( remotes: Array, nameOrAlias: string, ): Remote | undefined { - for (const remote of remotes) { - const isNameMatched = nameOrAlias === remote.name; - if (isNameMatched) { - return remote; - } - - const isAliasMatched = remote.alias && nameOrAlias === remote.alias; - if (isAliasMatched) { - return remote; - } - } - return; + return remotes.find((r) => r.name === nameOrAlias || r.alias === nameOrAlias); } diff --git a/packages/runtime-core/src/utils/preload.ts b/packages/runtime-core/src/utils/preload.ts index cadca6251c8..f3df9877995 100644 --- a/packages/runtime-core/src/utils/preload.ts +++ b/packages/runtime-core/src/utils/preload.ts @@ -30,37 +30,61 @@ export function formatPreloadArgs( preloadArgs: Array, ): PreloadOptions { return preloadArgs.map((args) => { - const remoteInfo = matchRemote(remotes, args.nameOrAlias); + const remote = matchRemote(remotes, args.nameOrAlias); assert( - remoteInfo, - `Unable to preload ${args.nameOrAlias} as it is not included in ${ - !remoteInfo && - safeToString({ - remoteInfo, - remotes, - }) - }`, + remote, + `Unable to preload ${args.nameOrAlias} as it is not included in ${!remote && safeToString({ remoteInfo: remote, remotes })}`, ); - return { - remote: remoteInfo, - preloadConfig: defaultPreloadArgs(args), - }; + return { remote, preloadConfig: defaultPreloadArgs(args) }; }); } export function normalizePreloadExposes(exposes?: string[]): string[] { - if (!exposes) { - return []; - } + if (!exposes) return []; + return exposes.map((e) => + e === '.' ? e : e.startsWith('./') ? e.slice(2) : e, + ); +} - return exposes.map((expose) => { - if (expose === '.') { - return expose; - } - if (expose.startsWith('./')) { - return expose.replace('./', ''); +function attachElements( + type: 'link' | 'script', + urls: string[], + host: ModuleFederation, + attrs: Record, + extraOpts?: { needDeleteLink?: boolean; needDeleteScript?: boolean }, +): void { + urls.forEach((url) => { + if (type === 'link') { + const { link, needAttach } = createLink({ + url, + cb: () => {}, + attrs, + createLinkHook: (u, a) => { + const res = host.loaderHook.lifecycle.createLink.emit({ + url: u, + attrs: a, + }); + return res instanceof HTMLLinkElement ? res : undefined; + }, + ...extraOpts, + }); + needAttach && document.head.appendChild(link); + } else { + const { script, needAttach } = createScript({ + url, + cb: () => {}, + attrs, + createScriptHook: (u: string, a: any) => { + const res = host.loaderHook.lifecycle.createScript.emit({ + url: u, + attrs: a, + }); + return res instanceof HTMLScriptElement ? res : undefined; + }, + ...extraOpts, + }); + needAttach && document.head.appendChild(script); } - return expose; }); } @@ -68,7 +92,6 @@ export function preloadAssets( remoteInfo: RemoteInfo, host: ModuleFederation, assets: PreloadAssets, - // It is used to distinguish preload from load remote parallel loading useLinkPreload = true, ): void { const { cssAssets, jsAssetsWithoutEntry, entryAssets } = assets; @@ -77,127 +100,37 @@ export function preloadAssets( entryAssets.forEach((asset) => { const { moduleInfo } = asset; const module = host.moduleCache.get(remoteInfo.name); - if (module) { - getRemoteEntry({ - origin: host, - remoteInfo: moduleInfo, - remoteEntryExports: module.remoteEntryExports, - }); - } else { - getRemoteEntry({ - origin: host, - remoteInfo: moduleInfo, - remoteEntryExports: undefined, - }); - } - }); - - if (useLinkPreload) { - const defaultAttrs = { - rel: 'preload', - as: 'style', - }; - cssAssets.forEach((cssUrl) => { - const { link: cssEl, needAttach } = createLink({ - url: cssUrl, - cb: () => { - // noop - }, - attrs: defaultAttrs, - createLinkHook: (url, attrs) => { - const res = host.loaderHook.lifecycle.createLink.emit({ - url, - attrs, - }); - if (res instanceof HTMLLinkElement) { - return res; - } - return; - }, - }); - - needAttach && document.head.appendChild(cssEl); + getRemoteEntry({ + origin: host, + remoteInfo: moduleInfo, + remoteEntryExports: module ? module.remoteEntryExports : undefined, }); - } else { - const defaultAttrs = { - rel: 'stylesheet', - type: 'text/css', - }; - cssAssets.forEach((cssUrl) => { - const { link: cssEl, needAttach } = createLink({ - url: cssUrl, - cb: () => { - // noop - }, - attrs: defaultAttrs, - createLinkHook: (url, attrs) => { - const res = host.loaderHook.lifecycle.createLink.emit({ - url, - attrs, - }); - if (res instanceof HTMLLinkElement) { - return res; - } - return; - }, - needDeleteLink: false, - }); - - needAttach && document.head.appendChild(cssEl); - }); - } + }); if (useLinkPreload) { - const defaultAttrs = { + attachElements('link', cssAssets, host, { rel: 'preload', as: 'style' }); + attachElements('link', jsAssetsWithoutEntry, host, { rel: 'preload', as: 'script', - }; - jsAssetsWithoutEntry.forEach((jsUrl) => { - const { link: linkEl, needAttach } = createLink({ - url: jsUrl, - cb: () => { - // noop - }, - attrs: defaultAttrs, - createLinkHook: (url: string, attrs) => { - const res = host.loaderHook.lifecycle.createLink.emit({ - url, - attrs, - }); - if (res instanceof HTMLLinkElement) { - return res; - } - return; - }, - }); - needAttach && document.head.appendChild(linkEl); }); } else { - const defaultAttrs = { - fetchpriority: 'high', - type: remoteInfo?.type === 'module' ? 'module' : 'text/javascript', - }; - jsAssetsWithoutEntry.forEach((jsUrl) => { - const { script: scriptEl, needAttach } = createScript({ - url: jsUrl, - cb: () => { - // noop - }, - attrs: defaultAttrs, - createScriptHook: (url: string, attrs: any) => { - const res = host.loaderHook.lifecycle.createScript.emit({ - url, - attrs, - }); - if (res instanceof HTMLScriptElement) { - return res; - } - return; - }, - needDeleteScript: true, - }); - needAttach && document.head.appendChild(scriptEl); - }); + attachElements( + 'link', + cssAssets, + host, + { rel: 'stylesheet', type: 'text/css' }, + { needDeleteLink: false }, + ); + attachElements( + 'script', + jsAssetsWithoutEntry, + host, + { + fetchpriority: 'high', + type: remoteInfo?.type === 'module' ? 'module' : 'text/javascript', + }, + { needDeleteScript: true }, + ); } } } diff --git a/packages/runtime-core/src/utils/semver/compare.ts b/packages/runtime-core/src/utils/semver/compare.ts deleted file mode 100644 index d0f6879f47a..00000000000 --- a/packages/runtime-core/src/utils/semver/compare.ts +++ /dev/null @@ -1,125 +0,0 @@ -// fork from https://github.com/originjs/vite-plugin-federation/blob/v1.1.12/packages/lib/src/utils/semver/index.ts -// Copyright (c) -// vite-plugin-federation is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan PSL v2. -// You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -export interface CompareAtom { - operator: string; - version: string; - major: string; - minor: string; - patch: string; - preRelease?: string[]; -} - -function compareAtom( - rangeAtom: string | number, - versionAtom: string | number, -): number { - rangeAtom = Number(rangeAtom) || rangeAtom; - versionAtom = Number(versionAtom) || versionAtom; - - if (rangeAtom > versionAtom) { - return 1; - } - - if (rangeAtom === versionAtom) { - return 0; - } - - return -1; -} - -function comparePreRelease( - rangeAtom: CompareAtom, - versionAtom: CompareAtom, -): number { - const { preRelease: rangePreRelease } = rangeAtom; - const { preRelease: versionPreRelease } = versionAtom; - - if (rangePreRelease === undefined && Boolean(versionPreRelease)) { - return 1; - } - - if (Boolean(rangePreRelease) && versionPreRelease === undefined) { - return -1; - } - - if (rangePreRelease === undefined && versionPreRelease === undefined) { - return 0; - } - - for (let i = 0, n = rangePreRelease!.length; i <= n; i++) { - const rangeElement = rangePreRelease![i]; - const versionElement = versionPreRelease![i]; - - if (rangeElement === versionElement) { - continue; - } - - if (rangeElement === undefined && versionElement === undefined) { - return 0; - } - - if (!rangeElement) { - return 1; - } - - if (!versionElement) { - return -1; - } - - return compareAtom(rangeElement, versionElement); - } - - return 0; -} - -function compareVersion( - rangeAtom: CompareAtom, - versionAtom: CompareAtom, -): number { - return ( - compareAtom(rangeAtom.major, versionAtom.major) || - compareAtom(rangeAtom.minor, versionAtom.minor) || - compareAtom(rangeAtom.patch, versionAtom.patch) || - comparePreRelease(rangeAtom, versionAtom) - ); -} - -function eq(rangeAtom: CompareAtom, versionAtom: CompareAtom): boolean { - return rangeAtom.version === versionAtom.version; -} - -export function compare( - rangeAtom: CompareAtom, - versionAtom: CompareAtom, -): boolean { - switch (rangeAtom.operator) { - case '': - case '=': - return eq(rangeAtom, versionAtom); - case '>': - return compareVersion(rangeAtom, versionAtom) < 0; - case '>=': - return ( - eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) < 0 - ); - case '<': - return compareVersion(rangeAtom, versionAtom) > 0; - case '<=': - return ( - eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) > 0 - ); - case undefined: { - // mean * or x -> all versions - return true; - } - default: - return false; - } -} diff --git a/packages/runtime-core/src/utils/semver/constants.ts b/packages/runtime-core/src/utils/semver/constants.ts deleted file mode 100644 index a4d20d8127e..00000000000 --- a/packages/runtime-core/src/utils/semver/constants.ts +++ /dev/null @@ -1,39 +0,0 @@ -// fork from https://github.com/originjs/vite-plugin-federation/blob/v1.1.12/packages/lib/src/utils/semver/index.ts -// those constants are based on https://www.rubydoc.info/gems/semantic_range/3.0.0/SemanticRange#BUILDIDENTIFIER-constant -// Copyright (c) -// vite-plugin-federation is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan PSL v2. -// You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -const buildIdentifier = '[0-9A-Za-z-]+'; -const build = `(?:\\+(${buildIdentifier}(?:\\.${buildIdentifier})*))`; -const numericIdentifier = '0|[1-9]\\d*'; -const numericIdentifierLoose = '[0-9]+'; -const nonNumericIdentifier = '\\d*[a-zA-Z-][a-zA-Z0-9-]*'; -const preReleaseIdentifierLoose = `(?:${numericIdentifierLoose}|${nonNumericIdentifier})`; -const preReleaseLoose = `(?:-?(${preReleaseIdentifierLoose}(?:\\.${preReleaseIdentifierLoose})*))`; -const preReleaseIdentifier = `(?:${numericIdentifier}|${nonNumericIdentifier})`; -const preRelease = `(?:-(${preReleaseIdentifier}(?:\\.${preReleaseIdentifier})*))`; -const xRangeIdentifier = `${numericIdentifier}|x|X|\\*`; -const xRangePlain = `[v=\\s]*(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:${preRelease})?${build}?)?)?`; -export const hyphenRange = `^\\s*(${xRangePlain})\\s+-\\s+(${xRangePlain})\\s*$`; -const mainVersionLoose = `(${numericIdentifierLoose})\\.(${numericIdentifierLoose})\\.(${numericIdentifierLoose})`; -const loosePlain = `[v=\\s]*${mainVersionLoose}${preReleaseLoose}?${build}?`; -const gtlt = '((?:<|>)?=?)'; -export const comparatorTrim = `(\\s*)${gtlt}\\s*(${loosePlain}|${xRangePlain})`; -const loneTilde = '(?:~>?)'; -export const tildeTrim = `(\\s*)${loneTilde}\\s+`; -const loneCaret = '(?:\\^)'; -export const caretTrim = `(\\s*)${loneCaret}\\s+`; -export const star = '(<|>)?=?\\s*\\*'; -export const caret = `^${loneCaret}${xRangePlain}$`; -const mainVersion = `(${numericIdentifier})\\.(${numericIdentifier})\\.(${numericIdentifier})`; -const fullPlain = `v?${mainVersion}${preRelease}?${build}?`; -export const tilde = `^${loneTilde}${xRangePlain}$`; -export const xRange = `^${gtlt}\\s*${xRangePlain}$`; -export const comparator = `^${gtlt}\\s*(${fullPlain})$|^$`; -// copy from semver package -export const gte0 = '^\\s*>=\\s*0.0.0\\s*$'; diff --git a/packages/runtime-core/src/utils/semver/index.ts b/packages/runtime-core/src/utils/semver/index.ts deleted file mode 100644 index f974cfa45ae..00000000000 --- a/packages/runtime-core/src/utils/semver/index.ts +++ /dev/null @@ -1,214 +0,0 @@ -// fork from https://github.com/originjs/vite-plugin-federation/blob/v1.1.12/packages/lib/src/utils/semver/index.ts -// Copyright (c) -// vite-plugin-federation is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan PSL v2. -// You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -import { combineVersion, extractComparator, pipe } from './utils'; -import { - parseHyphen, - parseComparatorTrim, - parseTildeTrim, - parseCaretTrim, - parseCarets, - parseTildes, - parseXRanges, - parseStar, - parseGTE0, -} from './parser'; -import { compare } from './compare'; -import type { CompareAtom } from './compare'; - -function parseComparatorString(range: string): string { - return pipe( - // handle caret - // ^ --> * (any, kinda silly) - // ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0-0 - // ^2.0, ^2.0.x --> >=2.0.0 <3.0.0-0 - // ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0 - // ^1.2.3 --> >=1.2.3 <2.0.0-0 - // ^1.2.0 --> >=1.2.0 <2.0.0-0 - parseCarets, - // handle tilde - // ~, ~> --> * (any, kinda silly) - // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0 - // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0 - // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0 - // ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0 - // ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0 - parseTildes, - parseXRanges, - parseStar, - )(range); -} - -function parseRange(range: string) { - return pipe( - // handle hyphenRange - // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` - parseHyphen, - // handle trim comparator - // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` - parseComparatorTrim, - // handle trim tilde - // `~ 1.2.3` => `~1.2.3` - parseTildeTrim, - // handle trim caret - // `^ 1.2.3` => `^1.2.3` - parseCaretTrim, - )(range.trim()) - .split(/\s+/) - .join(' '); -} - -export function satisfy(version: string, range: string): boolean { - if (!version) { - return false; - } - - // Extract version details once - const extractedVersion = extractComparator(version); - if (!extractedVersion) { - // If the version string is invalid, it can't satisfy any range - return false; - } - const [ - , - versionOperator, - , - versionMajor, - versionMinor, - versionPatch, - versionPreRelease, - ] = extractedVersion; - const versionAtom: CompareAtom = { - operator: versionOperator, - version: combineVersion( - versionMajor, - versionMinor, - versionPatch, - versionPreRelease, - ), // exclude build atom - major: versionMajor, - minor: versionMinor, - patch: versionPatch, - preRelease: versionPreRelease?.split('.'), - }; - - // Split the range by || to handle OR conditions - const orRanges = range.split('||'); - - for (const orRange of orRanges) { - const trimmedOrRange = orRange.trim(); - if (!trimmedOrRange) { - // An empty range string signifies wildcard *, satisfy any valid version - // (We already checked if the version itself is valid) - return true; - } - - // Handle simple wildcards explicitly before complex parsing - if (trimmedOrRange === '*' || trimmedOrRange === 'x') { - return true; - } - - try { - // Apply existing parsing logic to the current OR sub-range - const parsedSubRange = parseRange(trimmedOrRange); // Handles hyphens, trims etc. - - // Check if the result of initial parsing is empty, which can happen - // for some wildcard cases handled by parseRange/parseComparatorString. - // E.g. `parseStar` used in `parseComparatorString` returns ''. - if (!parsedSubRange.trim()) { - // If parsing results in empty string, treat as wildcard match - return true; - } - - const parsedComparatorString = parsedSubRange - .split(' ') - .map((rangeVersion) => parseComparatorString(rangeVersion)) // Expands ^, ~ - .join(' '); - - // Check again if the comparator string became empty after specific parsing like ^ or ~ - if (!parsedComparatorString.trim()) { - return true; - } - - // Split the sub-range by space for implicit AND conditions - const comparators = parsedComparatorString - .split(/\s+/) - .map((comparator) => parseGTE0(comparator)) - // Filter out empty strings that might result from multiple spaces - .filter(Boolean); - - // If a sub-range becomes empty after parsing (e.g., invalid characters), - // it cannot be satisfied. This check might be redundant now but kept for safety. - if (comparators.length === 0) { - continue; - } - - let subRangeSatisfied = true; - for (const comparator of comparators) { - const extractedComparator = extractComparator(comparator); - - // If any part of the AND sub-range is invalid, the sub-range is not satisfied - if (!extractedComparator) { - subRangeSatisfied = false; - break; - } - - const [ - , - rangeOperator, - , - rangeMajor, - rangeMinor, - rangePatch, - rangePreRelease, - ] = extractedComparator; - const rangeAtom: CompareAtom = { - operator: rangeOperator, - version: combineVersion( - rangeMajor, - rangeMinor, - rangePatch, - rangePreRelease, - ), - major: rangeMajor, - minor: rangeMinor, - patch: rangePatch, - preRelease: rangePreRelease?.split('.'), - }; - - // Check if the version satisfies this specific comparator in the AND chain - if (!compare(rangeAtom, versionAtom)) { - subRangeSatisfied = false; // This part of the AND condition failed - break; // No need to check further comparators in this sub-range - } - } - - // If all AND conditions within this OR sub-range were met, the overall range is satisfied - if (subRangeSatisfied) { - return true; - } - } catch (e) { - // Log error and treat this sub-range as unsatisfied - console.error( - `[semver] Error processing range part "${trimmedOrRange}":`, - e, - ); - continue; - } - } - - // If none of the OR sub-ranges were satisfied - return false; -} - -export function isLegallyVersion(version: string): boolean { - const semverRegex = - /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/; - return semverRegex.test(version); -} diff --git a/packages/runtime-core/src/utils/semver/parser.ts b/packages/runtime-core/src/utils/semver/parser.ts deleted file mode 100644 index 39f7efe4eaa..00000000000 --- a/packages/runtime-core/src/utils/semver/parser.ts +++ /dev/null @@ -1,249 +0,0 @@ -// fork from https://github.com/originjs/vite-plugin-federation/blob/v1.1.12/packages/lib/src/utils/semver/index.ts -// Copyright (c) -// vite-plugin-federation is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan PSL v2. -// You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -import { isXVersion, parseRegex } from './utils'; -import { - caret, - caretTrim, - comparatorTrim, - gte0, - hyphenRange, - star, - tilde, - tildeTrim, - xRange, -} from './constants'; - -export function parseHyphen(range: string): string { - return range.replace( - parseRegex(hyphenRange), - ( - _range, - from, - fromMajor, - fromMinor, - fromPatch, - _fromPreRelease, - _fromBuild, - to, - toMajor, - toMinor, - toPatch, - toPreRelease, - ) => { - if (isXVersion(fromMajor)) { - from = ''; - } else if (isXVersion(fromMinor)) { - from = `>=${fromMajor}.0.0`; - } else if (isXVersion(fromPatch)) { - from = `>=${fromMajor}.${fromMinor}.0`; - } else { - from = `>=${from}`; - } - - if (isXVersion(toMajor)) { - to = ''; - } else if (isXVersion(toMinor)) { - to = `<${Number(toMajor) + 1}.0.0-0`; - } else if (isXVersion(toPatch)) { - to = `<${toMajor}.${Number(toMinor) + 1}.0-0`; - } else if (toPreRelease) { - to = `<=${toMajor}.${toMinor}.${toPatch}-${toPreRelease}`; - } else { - to = `<=${to}`; - } - - return `${from} ${to}`.trim(); - }, - ); -} - -export function parseComparatorTrim(range: string): string { - return range.replace(parseRegex(comparatorTrim), '$1$2$3'); -} - -export function parseTildeTrim(range: string): string { - return range.replace(parseRegex(tildeTrim), '$1~'); -} - -export function parseCaretTrim(range: string): string { - return range.replace(parseRegex(caretTrim), '$1^'); -} - -export function parseCarets(range: string): string { - return range - .trim() - .split(/\s+/) - .map((rangeVersion) => - rangeVersion.replace( - parseRegex(caret), - (_, major, minor, patch, preRelease) => { - if (isXVersion(major)) { - return ''; - } else if (isXVersion(minor)) { - return `>=${major}.0.0 <${Number(major) + 1}.0.0-0`; - } else if (isXVersion(patch)) { - if (major === '0') { - return `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0-0`; - } else { - return `>=${major}.${minor}.0 <${Number(major) + 1}.0.0-0`; - } - } else if (preRelease) { - if (major === '0') { - if (minor === '0') { - return `>=${major}.${minor}.${patch}-${preRelease} <${major}.${minor}.${ - Number(patch) + 1 - }-0`; - } else { - return `>=${major}.${minor}.${patch}-${preRelease} <${major}.${ - Number(minor) + 1 - }.0-0`; - } - } else { - return `>=${major}.${minor}.${patch}-${preRelease} <${ - Number(major) + 1 - }.0.0-0`; - } - } else { - if (major === '0') { - if (minor === '0') { - return `>=${major}.${minor}.${patch} <${major}.${minor}.${ - Number(patch) + 1 - }-0`; - } else { - return `>=${major}.${minor}.${patch} <${major}.${ - Number(minor) + 1 - }.0-0`; - } - } - - return `>=${major}.${minor}.${patch} <${Number(major) + 1}.0.0-0`; - } - }, - ), - ) - .join(' '); -} - -export function parseTildes(range: string): string { - return range - .trim() - .split(/\s+/) - .map((rangeVersion) => - rangeVersion.replace( - parseRegex(tilde), - (_, major, minor, patch, preRelease) => { - if (isXVersion(major)) { - return ''; - } else if (isXVersion(minor)) { - return `>=${major}.0.0 <${Number(major) + 1}.0.0-0`; - } else if (isXVersion(patch)) { - return `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0-0`; - } else if (preRelease) { - return `>=${major}.${minor}.${patch}-${preRelease} <${major}.${ - Number(minor) + 1 - }.0-0`; - } - - return `>=${major}.${minor}.${patch} <${major}.${ - Number(minor) + 1 - }.0-0`; - }, - ), - ) - .join(' '); -} - -export function parseXRanges(range: string): string { - return range - .split(/\s+/) - .map((rangeVersion) => - rangeVersion - .trim() - .replace( - parseRegex(xRange), - (ret, gtlt, major, minor, patch, preRelease) => { - const isXMajor = isXVersion(major); - const isXMinor = isXMajor || isXVersion(minor); - const isXPatch = isXMinor || isXVersion(patch); - - if (gtlt === '=' && isXPatch) { - gtlt = ''; - } - - preRelease = ''; - - if (isXMajor) { - if (gtlt === '>' || gtlt === '<') { - // nothing is allowed - return '<0.0.0-0'; - } else { - // nothing is forbidden - return '*'; - } - } else if (gtlt && isXPatch) { - // replace X with 0 - if (isXMinor) { - minor = 0; - } - - patch = 0; - - if (gtlt === '>') { - // >1 => >=2.0.0 - // >1.2 => >=1.3.0 - gtlt = '>='; - - if (isXMinor) { - major = Number(major) + 1; - minor = 0; - patch = 0; - } else { - minor = Number(minor) + 1; - patch = 0; - } - } else if (gtlt === '<=') { - // <=0.7.x is actually <0.8.0, since any 0.7.x should pass - // Similarly, <=7.x is actually <8.0.0, etc. - gtlt = '<'; - - if (isXMinor) { - major = Number(major) + 1; - } else { - minor = Number(minor) + 1; - } - } - - if (gtlt === '<') { - preRelease = '-0'; - } - - return `${gtlt + major}.${minor}.${patch}${preRelease}`; - } else if (isXMinor) { - return `>=${major}.0.0${preRelease} <${Number(major) + 1}.0.0-0`; - } else if (isXPatch) { - return `>=${major}.${minor}.0${preRelease} <${major}.${ - Number(minor) + 1 - }.0-0`; - } - - return ret; - }, - ), - ) - .join(' '); -} - -export function parseStar(range: string): string { - return range.trim().replace(parseRegex(star), ''); -} - -export function parseGTE0(comparatorString: string): string { - return comparatorString.trim().replace(parseRegex(gte0), ''); -} diff --git a/packages/runtime-core/src/utils/semver/utils.ts b/packages/runtime-core/src/utils/semver/utils.ts deleted file mode 100644 index 196ed2cc816..00000000000 --- a/packages/runtime-core/src/utils/semver/utils.ts +++ /dev/null @@ -1,85 +0,0 @@ -// fork from https://github.com/originjs/vite-plugin-federation/blob/v1.1.12/packages/lib/src/utils/semver/index.ts -// Copyright (c) -// vite-plugin-federation is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan PSL v2. -// You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -import { comparator } from './constants'; - -export function parseRegex(source: string): RegExp { - return new RegExp(source); -} - -export function isXVersion(version: string): boolean { - return !version || version.toLowerCase() === 'x' || version === '*'; -} - -export function pipe( - f1: (...args: TArgs) => R1, - f2: (a: R1) => R2, - f3: (a: R2) => R3, - f4: (a: R3) => R4, - f5: (a: R4) => R5, - f6: (a: R5) => R6, - f7: (a: R6) => R7, -): (...args: TArgs) => R7; -export function pipe( - f1: (...args: TArgs) => R1, - f2: (a: R1) => R2, - f3: (a: R2) => R3, - f4: (a: R3) => R4, - f5: (a: R4) => R5, - f6: (a: R5) => R6, -): (...args: TArgs) => R6; -export function pipe( - f1: (...args: TArgs) => R1, - f2: (a: R1) => R2, - f3: (a: R2) => R3, - f4: (a: R3) => R4, - f5: (a: R4) => R5, -): (...args: TArgs) => R5; -export function pipe( - f1: (...args: TArgs) => R1, - f2: (a: R1) => R2, - f3: (a: R2) => R3, - f4: (a: R3) => R4, -): (...args: TArgs) => R4; -export function pipe( - f1: (...args: TArgs) => R1, - f2: (a: R1) => R2, - f3: (a: R2) => R3, -): (...args: TArgs) => R3; -export function pipe( - f1: (...args: TArgs) => R1, - f2: (a: R1) => R2, -): (...args: TArgs) => R2; -export function pipe( - f1: (...args: TArgs) => R1, -): (...args: TArgs) => R1; -export function pipe(...fns: ((params: any) => any)[]) { - return (x: unknown): any => fns.reduce((v, f) => f(v), x); -} - -export function extractComparator( - comparatorString: string, -): RegExpMatchArray | null { - return comparatorString.match(parseRegex(comparator)); -} - -export function combineVersion( - major: string, - minor: string, - patch: string, - preRelease: string, -): string { - const mainVersion = `${major}.${minor}.${patch}`; - - if (preRelease) { - return `${mainVersion}-${preRelease}`; - } - - return mainVersion; -} diff --git a/packages/runtime-core/src/utils/share.ts b/packages/runtime-core/src/utils/share.ts deleted file mode 100644 index 007a7b2ef8e..00000000000 --- a/packages/runtime-core/src/utils/share.ts +++ /dev/null @@ -1,548 +0,0 @@ -import { DEFAULT_SCOPE } from '../constant'; -import { TreeShakingStatus } from '@module-federation/sdk'; -import { Global, Federation } from '../global'; -import { - GlobalShareScopeMap, - Shared, - ShareArgs, - ShareInfos, - ShareScopeMap, - LoadShareExtraOptions, - UserOptions, - Options, - ShareStrategy, - TreeShakingArgs, - SharedGetter, -} from '../type'; -import { warn, error } from './logger'; -import { satisfy } from './semver'; -import { SyncWaterfallHook } from './hooks'; -import { addUniqueItem, arrayOptions } from './tool'; - -function formatShare( - shareArgs: ShareArgs, - from: string, - name: string, - shareStrategy?: ShareStrategy, -): Shared { - let get: Shared['get']; - if ('get' in shareArgs) { - // eslint-disable-next-line prefer-destructuring - get = shareArgs.get; - } else if ('lib' in shareArgs) { - get = () => Promise.resolve(shareArgs.lib); - } else { - get = () => - Promise.resolve(() => { - throw new Error(`Can not get shared '${name}'!`); - }); - } - - if (shareArgs.shareConfig?.eager && shareArgs.treeShaking?.mode) { - throw new Error( - 'Can not set "eager:true" and "treeShaking" at the same time!', - ); - } - - return { - deps: [], - useIn: [], - from, - loading: null, - ...shareArgs, - shareConfig: { - requiredVersion: `^${shareArgs.version}`, - singleton: false, - eager: false, - strictVersion: false, - ...shareArgs.shareConfig, - }, - get, - loaded: shareArgs?.loaded || 'lib' in shareArgs ? true : undefined, - version: shareArgs.version ?? '0', - scope: Array.isArray(shareArgs.scope) - ? shareArgs.scope - : [shareArgs.scope ?? 'default'], - strategy: (shareArgs.strategy ?? shareStrategy) || 'version-first', - treeShaking: shareArgs.treeShaking - ? { - ...shareArgs.treeShaking, - mode: shareArgs.treeShaking.mode ?? 'server-calc', - status: shareArgs.treeShaking.status ?? TreeShakingStatus.UNKNOWN, - useIn: [], - } - : undefined, - }; -} - -export function formatShareConfigs( - prevOptions: Options, - newOptions: UserOptions, -) { - const shareArgs = newOptions.shared || {}; - const from = newOptions.name; - - const newShareInfos = Object.keys(shareArgs).reduce((res, pkgName) => { - const arrayShareArgs = arrayOptions(shareArgs[pkgName]); - res[pkgName] = res[pkgName] || []; - arrayShareArgs.forEach((shareConfig) => { - res[pkgName].push( - formatShare(shareConfig, from, pkgName, newOptions.shareStrategy), - ); - }); - return res; - }, {} as ShareInfos); - - const allShareInfos = { - ...prevOptions.shared, - }; - - Object.keys(newShareInfos).forEach((shareKey) => { - if (!allShareInfos[shareKey]) { - allShareInfos[shareKey] = newShareInfos[shareKey]; - } else { - newShareInfos[shareKey].forEach((newUserSharedOptions) => { - const isSameVersion = allShareInfos[shareKey].find( - (sharedVal) => sharedVal.version === newUserSharedOptions.version, - ); - if (!isSameVersion) { - allShareInfos[shareKey].push(newUserSharedOptions); - } - }); - } - }); - return { allShareInfos, newShareInfos }; -} - -export function shouldUseTreeShaking( - treeShaking?: TreeShakingArgs, - usedExports?: string[], -) { - if (!treeShaking) { - return false; - } - const { status, mode } = treeShaking; - if (status === TreeShakingStatus.NO_USE) { - return false; - } - - if (status === TreeShakingStatus.CALCULATED) { - return true; - } - - if (mode === 'runtime-infer') { - if (!usedExports) { - return true; - } - return isMatchUsedExports(treeShaking, usedExports); - } - - return false; -} - -/** - * compare version a and b, return true if a is less than b - */ -export function versionLt(a: string, b: string): boolean { - const transformInvalidVersion = (version: string) => { - const isNumberVersion = !Number.isNaN(Number(version)); - if (isNumberVersion) { - const splitArr = version.split('.'); - let validVersion = version; - for (let i = 0; i < 3 - splitArr.length; i++) { - validVersion += '.0'; - } - return validVersion; - } - return version; - }; - if (satisfy(transformInvalidVersion(a), `<=${transformInvalidVersion(b)}`)) { - return true; - } else { - return false; - } -} - -export const findVersion = ( - shareVersionMap: ShareScopeMap[string][string], - cb?: (prev: string, cur: string) => boolean, -): string => { - const callback = - cb || - function (prev: string, cur: string): boolean { - return versionLt(prev, cur); - }; - - return Object.keys(shareVersionMap).reduce((prev: number | string, cur) => { - if (!prev) { - return cur; - } - if (callback(prev as string, cur)) { - return cur; - } - - // default version is '0' https://github.com/webpack/webpack/blob/main/lib/sharing/ProvideSharedModule.js#L136 - if (prev === '0') { - return cur; - } - - return prev; - }, 0) as string; -}; - -export const isLoaded = (shared: { - loading?: null | Promise; - loaded?: boolean; - lib?: () => unknown; -}) => { - return Boolean(shared.loaded) || typeof shared.lib === 'function'; -}; - -const isLoading = (shared: { - loading?: null | Promise; - loaded?: boolean; - lib?: () => unknown; -}) => { - return Boolean(shared.loading); -}; - -const isMatchUsedExports = ( - treeShaking?: TreeShakingArgs, - usedExports?: string[], -) => { - if (!treeShaking || !usedExports) { - return false; - } - - const { usedExports: treeShakingUsedExports } = treeShaking; - - if (!treeShakingUsedExports) { - return false; - } - - if (usedExports.every((e) => treeShakingUsedExports.includes(e))) { - return true; - } - - return false; -}; - -function findSingletonVersionOrderByVersion( - shareScopeMap: ShareScopeMap, - scope: string, - pkgName: string, - treeShaking?: TreeShakingArgs, -): { - version: string; - useTreesShaking: boolean; -} { - const versions = shareScopeMap[scope][pkgName]; - let version = ''; - let useTreesShaking = shouldUseTreeShaking(treeShaking); - // return false means use prev version - const callback = function (prev: string, cur: string): boolean { - if (useTreesShaking) { - if (!versions[prev].treeShaking) { - return true; - } - if (!versions[cur].treeShaking) { - return false; - } - return !isLoaded(versions[prev].treeShaking) && versionLt(prev, cur); - } - return !isLoaded(versions[prev]) && versionLt(prev, cur); - }; - - if (useTreesShaking) { - version = findVersion(shareScopeMap[scope][pkgName], callback); - if (version) { - return { - version, - useTreesShaking, - }; - } - useTreesShaking = false; - } - - return { - version: findVersion(shareScopeMap[scope][pkgName], callback), - useTreesShaking, - }; -} - -const isLoadingOrLoaded = (shared: { - loading?: null | Promise; - loaded?: boolean; - lib?: () => unknown; -}) => { - return isLoaded(shared) || isLoading(shared); -}; - -function findSingletonVersionOrderByLoaded( - shareScopeMap: ShareScopeMap, - scope: string, - pkgName: string, - treeShaking?: TreeShakingArgs, -): { - version: string; - useTreesShaking: boolean; -} { - const versions = shareScopeMap[scope][pkgName]; - let version = ''; - let useTreesShaking = shouldUseTreeShaking(treeShaking); - - // return false means use prev version - const callback = function (prev: string, cur: string): boolean { - if (useTreesShaking) { - if (!versions[prev].treeShaking) { - return true; - } - if (!versions[cur].treeShaking) { - return false; - } - if (isLoadingOrLoaded(versions[cur].treeShaking)) { - if (isLoadingOrLoaded(versions[prev].treeShaking)) { - return Boolean(versionLt(prev, cur)); - } else { - return true; - } - } - if (isLoadingOrLoaded(versions[prev].treeShaking)) { - return false; - } - } - - if (isLoadingOrLoaded(versions[cur])) { - if (isLoadingOrLoaded(versions[prev])) { - return Boolean(versionLt(prev, cur)); - } else { - return true; - } - } - if (isLoadingOrLoaded(versions[prev])) { - return false; - } - return versionLt(prev, cur); - }; - - if (useTreesShaking) { - version = findVersion(shareScopeMap[scope][pkgName], callback); - if (version) { - return { - version, - useTreesShaking, - }; - } - useTreesShaking = false; - } - - return { - version: findVersion(shareScopeMap[scope][pkgName], callback), - useTreesShaking, - }; -} - -function getFindShareFunction(strategy: Shared['strategy']) { - if (strategy === 'loaded-first') { - return findSingletonVersionOrderByLoaded; - } - return findSingletonVersionOrderByVersion; -} - -export function getRegisteredShare( - localShareScopeMap: ShareScopeMap, - pkgName: string, - shareInfo: Shared, - resolveShare: SyncWaterfallHook<{ - shareScopeMap: ShareScopeMap; - scope: string; - pkgName: string; - version: string; - shareInfo: Shared; - GlobalFederation: Federation; - resolver: () => { shared: Shared; useTreesShaking: boolean } | undefined; - }>, -): { shared: Shared; useTreesShaking: boolean } | void { - if (!localShareScopeMap) { - return; - } - const { - shareConfig, - scope = DEFAULT_SCOPE, - strategy, - treeShaking, - } = shareInfo; - const scopes = Array.isArray(scope) ? scope : [scope]; - for (const sc of scopes) { - if ( - shareConfig && - localShareScopeMap[sc] && - localShareScopeMap[sc][pkgName] - ) { - const { requiredVersion } = shareConfig; - const findShareFunction = getFindShareFunction(strategy); - const { version: maxOrSingletonVersion, useTreesShaking } = - findShareFunction(localShareScopeMap, sc, pkgName, treeShaking); - - const defaultResolver = () => { - const shared = localShareScopeMap[sc][pkgName][maxOrSingletonVersion]; - if (shareConfig.singleton) { - if ( - typeof requiredVersion === 'string' && - !satisfy(maxOrSingletonVersion, requiredVersion) - ) { - const msg = `Version ${maxOrSingletonVersion} from ${ - maxOrSingletonVersion && shared.from - } of shared singleton module ${pkgName} does not satisfy the requirement of ${ - shareInfo.from - } which needs ${requiredVersion})`; - - if (shareConfig.strictVersion) { - error(msg); - } else { - warn(msg); - } - } - return { - shared, - useTreesShaking, - }; - } else { - if (requiredVersion === false || requiredVersion === '*') { - return { - shared, - useTreesShaking, - }; - } - if (satisfy(maxOrSingletonVersion, requiredVersion)) { - return { - shared, - useTreesShaking, - }; - } - - const _usedTreeShaking = shouldUseTreeShaking(treeShaking); - if (_usedTreeShaking) { - for (const [versionKey, versionValue] of Object.entries( - localShareScopeMap[sc][pkgName], - )) { - if ( - !shouldUseTreeShaking( - versionValue.treeShaking, - treeShaking?.usedExports, - ) - ) { - continue; - } - - if (satisfy(versionKey, requiredVersion)) { - return { - shared: versionValue, - useTreesShaking: _usedTreeShaking, - }; - } - } - } - for (const [versionKey, versionValue] of Object.entries( - localShareScopeMap[sc][pkgName], - )) { - if (satisfy(versionKey, requiredVersion)) { - return { - shared: versionValue, - useTreesShaking: false, - }; - } - } - } - return; - }; - const params = { - shareScopeMap: localShareScopeMap, - scope: sc, - pkgName, - version: maxOrSingletonVersion, - GlobalFederation: Global.__FEDERATION__, - shareInfo, - resolver: defaultResolver, - }; - const resolveShared = resolveShare.emit(params) || params; - return resolveShared.resolver(); - } - } -} - -export function getGlobalShareScope(): GlobalShareScopeMap { - return Global.__FEDERATION__.__SHARE__; -} - -export function getTargetSharedOptions(options: { - pkgName: string; - extraOptions?: LoadShareExtraOptions; - shareInfos: ShareInfos; -}) { - const { pkgName, extraOptions, shareInfos } = options; - const defaultResolver = (sharedOptions: ShareInfos[string]) => { - if (!sharedOptions) { - return undefined; - } - const shareVersionMap: ShareScopeMap[string][string] = {}; - sharedOptions.forEach((shared) => { - shareVersionMap[shared.version] = shared; - }); - const callback = function (prev: string, cur: string): boolean { - return ( - // TODO: consider multiple treeShaking shared scenes - !isLoaded(shareVersionMap[prev]) && versionLt(prev, cur) - ); - }; - - const maxVersion = findVersion(shareVersionMap, callback); - return shareVersionMap[maxVersion]; - }; - - const resolver = extraOptions?.resolver ?? defaultResolver; - const isPlainObject = (val: unknown): val is Record => { - return val !== null && typeof val === 'object' && !Array.isArray(val); - }; - - const merge = >( - ...sources: Array | undefined> - ): T => { - const out = {} as T; - for (const src of sources) { - if (!src) continue; - for (const [key, value] of Object.entries(src)) { - const prev = (out as any)[key]; - if (isPlainObject(prev) && isPlainObject(value)) { - (out as any)[key] = merge(prev, value); - } else if (value !== undefined) { - (out as any)[key] = value; - } - } - } - return out; - }; - - return merge(resolver(shareInfos[pkgName]), extraOptions?.customShareInfo); -} - -export const addUseIn = ( - shared: { useIn?: Array }, - from: string, -): void => { - if (!shared.useIn) { - shared.useIn = []; - } - addUniqueItem(shared.useIn, from); -}; - -export function directShare( - shared: Shared, - useTreesShaking?: boolean, -): Shared | TreeShakingArgs { - if (useTreesShaking && shared.treeShaking) { - return shared.treeShaking; - } - - return shared; -} diff --git a/packages/runtime-core/src/utils/tool.ts b/packages/runtime-core/src/utils/tool.ts index 96a92a1e4e6..bc16d552879 100644 --- a/packages/runtime-core/src/utils/tool.ts +++ b/packages/runtime-core/src/utils/tool.ts @@ -9,22 +9,17 @@ import { Remote, RemoteInfoOptionalVersion } from '../type'; import { warn } from './logger'; export function addUniqueItem(arr: Array, item: string): Array { - if (arr.findIndex((name) => name === item) === -1) { - arr.push(item); - } + if (!arr.includes(item)) arr.push(item); return arr; } export function getFMId( remoteInfo: RemoteInfoOptionalVersion | RemoteWithEntry, ): string { - if ('version' in remoteInfo && remoteInfo.version) { - return `${remoteInfo.name}:${remoteInfo.version}`; - } else if ('entry' in remoteInfo && remoteInfo.entry) { - return `${remoteInfo.name}:${remoteInfo.entry}`; - } else { - return `${remoteInfo.name}`; - } + const suffix = + ('version' in remoteInfo && remoteInfo.version) || + ('entry' in remoteInfo && remoteInfo.entry); + return suffix ? `${remoteInfo.name}:${suffix}` : remoteInfo.name; } export function isRemoteInfoWithEntry( @@ -43,11 +38,10 @@ export async function safeWrapper) => any>( disableWarn?: boolean, ): Promise | undefined> { try { - const res = await callback(); - return res; + return await callback(); } catch (e) { - !disableWarn && warn(e); - return; + if (!disableWarn) warn(e); + return undefined; } } @@ -59,15 +53,13 @@ export const objectToString = Object.prototype.toString; // eslint-disable-next-line @typescript-eslint/ban-types export function isPlainObject(val: any): val is object { - return objectToString.call(val) === '[object Object]'; + return Object.prototype.toString.call(val) === '[object Object]'; } export function isStaticResourcesEqual(url1: string, url2: string): boolean { const REG_EXP = /^(https?:)?\/\//i; - // Transform url1 and url2 into relative paths const relativeUrl1 = url1.replace(REG_EXP, '').replace(/\/$/, ''); const relativeUrl2 = url2.replace(REG_EXP, '').replace(/\/$/, ''); - // Check if the relative paths are identical return relativeUrl1 === relativeUrl2; } @@ -80,13 +72,9 @@ export function getRemoteEntryInfoFromSnapshot(snapshot: ModuleInfo): { type: RemoteEntryType; globalName: string; } { - const defaultRemoteEntryInfo: { - url: string; - type: RemoteEntryType; - globalName: string; - } = { + const defaults = { url: '', - type: 'global', + type: 'global' as RemoteEntryType, globalName: '', }; if (isBrowserEnv() || isReactNativeEnv() || !('ssrRemoteEntry' in snapshot)) { @@ -96,30 +84,31 @@ export function getRemoteEntryInfoFromSnapshot(snapshot: ModuleInfo): { type: snapshot.remoteEntryType, globalName: snapshot.globalName, } - : defaultRemoteEntryInfo; - } - if ('ssrRemoteEntry' in snapshot) { - return { - url: snapshot.ssrRemoteEntry || defaultRemoteEntryInfo.url, - type: snapshot.ssrRemoteEntryType || defaultRemoteEntryInfo.type, - globalName: snapshot.globalName, - }; + : defaults; } - return defaultRemoteEntryInfo; + return { + url: snapshot.ssrRemoteEntry || defaults.url, + type: snapshot.ssrRemoteEntryType || defaults.type, + globalName: snapshot.globalName, + }; } -export const processModuleAlias = (name: string, subPath: string) => { - // @host/ ./button -> @host/button - let moduleName; - if (name.endsWith('/')) { - moduleName = name.slice(0, -1); - } else { - moduleName = name; +export function singleFlight( + cache: Record | undefined>, + key: string, + fn: () => Promise, + options?: { clearOnReject?: boolean }, +): Promise { + if (!cache[key]) { + cache[key] = fn(); + if (options?.clearOnReject) { + cache[key]!.catch(() => { + delete cache[key]; + }); + } } + return cache[key]!; +} - if (subPath.startsWith('.')) { - subPath = subPath.slice(1); - } - moduleName = moduleName + subPath; - return moduleName; -}; +export const processModuleAlias = (name: string, subPath: string): string => + name.replace(/\/$/, '') + subPath.replace(/^\./, ''); diff --git a/packages/runtime-core/vitest.config.ts b/packages/runtime-core/vitest.config.ts index 148b62d42b7..6ba3a3e08e2 100644 --- a/packages/runtime-core/vitest.config.ts +++ b/packages/runtime-core/vitest.config.ts @@ -8,6 +8,14 @@ export default defineConfig({ __BROWSER__: false, __VERSION__: '"unknown"', }, + resolve: { + alias: { + '@module-federation/micro-effect': path.resolve( + __dirname, + '../micro-effect/src/index.ts', + ), + }, + }, plugins: [nxViteTsPaths()], test: { environment: 'jsdom', diff --git a/packages/runtime-plugins/inject-external-runtime-core-plugin/src/index.ts b/packages/runtime-plugins/inject-external-runtime-core-plugin/src/index.ts index 202005c053b..e574325d0f6 100644 --- a/packages/runtime-plugins/inject-external-runtime-core-plugin/src/index.ts +++ b/packages/runtime-plugins/inject-external-runtime-core-plugin/src/index.ts @@ -1,9 +1,6 @@ -import runtimeCoreDefault, * as runtimeCoreNamespace from '@module-federation/runtime-tools/runtime-core'; +import * as runtimeCore from '@module-federation/runtime-tools/runtime-core'; import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime-tools/runtime-core'; -const runtimeCore = - (runtimeCoreDefault as typeof runtimeCoreNamespace | undefined) ?? - runtimeCoreNamespace; declare global { var __VERSION__: string; var _FEDERATION_RUNTIME_CORE: typeof runtimeCore; @@ -18,29 +15,21 @@ function injectExternalRuntimeCorePlugin(): ModuleFederationRuntimePlugin { name: 'inject-external-runtime-core-plugin', version: __VERSION__, beforeInit(args) { - const globalRef = ( - runtimeCore as typeof runtimeCore & { - Global?: typeof runtimeCore.Global; - } - ).Global; - if (!globalRef || typeof globalRef !== 'object') { - return args; - } const name = args.options.name; const version = __VERSION__; if ( - globalRef._FEDERATION_RUNTIME_CORE && - globalRef._FEDERATION_RUNTIME_CORE_FROM && - (globalRef._FEDERATION_RUNTIME_CORE_FROM.name !== name || - globalRef._FEDERATION_RUNTIME_CORE_FROM.version !== version) + runtimeCore.Global._FEDERATION_RUNTIME_CORE && + runtimeCore.Global._FEDERATION_RUNTIME_CORE_FROM && + (runtimeCore.Global._FEDERATION_RUNTIME_CORE_FROM.name !== name || + runtimeCore.Global._FEDERATION_RUNTIME_CORE_FROM.version !== version) ) { console.warn( - `Detect multiple module federation runtime! Injected runtime from ${globalRef._FEDERATION_RUNTIME_CORE_FROM.name}@${globalRef._FEDERATION_RUNTIME_CORE_FROM.version} and current is ${name}@${version}, pleasure ensure there is only one consumer to provider runtime!`, + `Detect multiple module federation runtime! Injected runtime from ${runtimeCore.Global._FEDERATION_RUNTIME_CORE_FROM.name}@${runtimeCore.Global._FEDERATION_RUNTIME_CORE_FROM.version} and current is ${name}@${version}, pleasure ensure there is only one consumer to provider runtime!`, ); return args; } - globalRef._FEDERATION_RUNTIME_CORE = runtimeCore; - globalRef._FEDERATION_RUNTIME_CORE_FROM = { + runtimeCore.Global._FEDERATION_RUNTIME_CORE = runtimeCore; + runtimeCore.Global._FEDERATION_RUNTIME_CORE_FROM = { version, name, }; diff --git a/packages/runtime-tools/src/webpack-bundler-runtime.ts b/packages/runtime-tools/src/webpack-bundler-runtime.ts index bf870059e25..7ada0a527ca 100644 --- a/packages/runtime-tools/src/webpack-bundler-runtime.ts +++ b/packages/runtime-tools/src/webpack-bundler-runtime.ts @@ -1,8 +1 @@ -import webpackBundlerRuntime from '@module-federation/webpack-bundler-runtime'; - -const normalizedWebpackBundlerRuntime = - // Support both CJS module.exports payload and transpiled default payload. - (webpackBundlerRuntime as { default?: unknown }).default ?? - webpackBundlerRuntime; - -export default normalizedWebpackBundlerRuntime; +export { default } from '@module-federation/webpack-bundler-runtime'; diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 3c4f1f33236..94e90efa16a 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -6,9 +6,6 @@ "main": "./dist/index.cjs", "module": "./dist/index.js", "types": "./dist/index.d.ts", - "browser": { - "url": false - }, "license": "MIT", "publishConfig": { "access": "public" diff --git a/packages/runtime/src/helpers.ts b/packages/runtime/src/helpers.ts index de002cdea27..5a252c2846f 100644 --- a/packages/runtime/src/helpers.ts +++ b/packages/runtime/src/helpers.ts @@ -1,8 +1,4 @@ -import { - helpers, - type IGlobalUtils, - type IShareUtils, -} from '@module-federation/runtime-core'; +import { helpers } from '@module-federation/runtime-core'; import { getGlobalFederationInstance } from './utils'; export type { @@ -10,33 +6,16 @@ export type { IShareUtils, } from '@module-federation/runtime-core'; -type RuntimeGlobalUtils = IGlobalUtils & { - getGlobalFederationInstance: typeof getGlobalFederationInstance; -}; - -export const global: RuntimeGlobalUtils = { - ...helpers.global, - getGlobalFederationInstance, +export default { + ...helpers, + global: { + ...helpers.global, + getGlobalFederationInstance, + }, +} as { + global: typeof helpers.global & { + getGlobalFederationInstance: typeof getGlobalFederationInstance; + }; + share: typeof helpers.share; + utils: typeof helpers.utils; }; - -export const share: IShareUtils = helpers.share; - -export interface IRuntimeUtils { - matchRemoteWithNameAndExpose: typeof import('@module-federation/runtime-core').matchRemoteWithNameAndExpose; - preloadAssets: (...args: any[]) => void; - getRemoteInfo: typeof import('@module-federation/runtime-core').getRemoteInfo; -} - -export const utils: IRuntimeUtils = helpers.utils; - -const runtimeHelpers: { - global: RuntimeGlobalUtils; - share: IShareUtils; - utils: IRuntimeUtils; -} = { - global, - share, - utils, -}; - -export default runtimeHelpers; diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 8359ce47ada..7473457b5e7 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -26,9 +26,6 @@ "main": "./dist/index.cjs", "module": "./dist/index.js", "types": "./dist/index.d.ts", - "browser": { - "url": false - }, "exports": { ".": { "import": { diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 91408c4a527..931be5748c5 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -17,6 +17,7 @@ export { } from './logger'; export type { Logger, InfrastructureLogger } from './logger'; export * from './env'; +export * from './semver'; export * from './dom'; export * from './node'; export * from './normalizeOptions'; diff --git a/packages/sdk/src/semver/index.ts b/packages/sdk/src/semver/index.ts new file mode 100644 index 00000000000..253a5c2de02 --- /dev/null +++ b/packages/sdk/src/semver/index.ts @@ -0,0 +1,185 @@ +// fork from https://github.com/originjs/vite-plugin-federation/blob/v1.1.12/packages/lib/src/utils/semver/index.ts +// Copyright (c) +// vite-plugin-federation is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. +import { + comparator, + parseHyphen, + parseComparatorTrim, + parseTildeTrim, + parseCaretTrim, + parseCarets, + parseTildes, + parseXRanges, + parseStar, + parseGTE0, + isXVersion, +} from './parser'; +// --- utils (formerly utils.ts) --- +export function pipe( + ...fns: Array<(input: string) => string> +): (input: string) => string { + return (x: string): string => fns.reduce((v, f) => f(v), x); +} +export function extractComparator( + comparatorString: string, +): RegExpMatchArray | null { + return comparatorString.match(comparator); +} +export function combineVersion( + major: string, + minor: string, + patch: string, + preRelease: string, +): string { + const mainVersion = `${major}.${minor}.${patch}`; + if (preRelease) { + return `${mainVersion}-${preRelease}`; + } + return mainVersion; +} +// --- compare (formerly compare.ts) --- +export interface CompareAtom { + operator: string; + version: string; + major: string; + minor: string; + patch: string; + preRelease?: string[]; +} +function compareAtom( + rangeAtom: string | number, + versionAtom: string | number, +): number { + rangeAtom = Number(rangeAtom) || rangeAtom; + versionAtom = Number(versionAtom) || versionAtom; + return rangeAtom > versionAtom ? 1 : rangeAtom === versionAtom ? 0 : -1; +} +function comparePreRelease( + rangeAtom: CompareAtom, + versionAtom: CompareAtom, +): number { + const { preRelease: rangePreRelease } = rangeAtom; + const { preRelease: versionPreRelease } = versionAtom; + if (rangePreRelease === undefined) return versionPreRelease ? 1 : 0; + if (!versionPreRelease) return -1; + for (let i = 0, n = rangePreRelease!.length; i <= n; i++) { + const rangeElement = rangePreRelease![i]; + const versionElement = versionPreRelease![i]; + if (rangeElement === versionElement) { + continue; + } + if (rangeElement === undefined && versionElement === undefined) return 0; + if (!rangeElement) return 1; + if (!versionElement) return -1; + return compareAtom(rangeElement, versionElement); + } + return 0; +} +function compareVersion( + rangeAtom: CompareAtom, + versionAtom: CompareAtom, +): number { + return ( + compareAtom(rangeAtom.major, versionAtom.major) || + compareAtom(rangeAtom.minor, versionAtom.minor) || + compareAtom(rangeAtom.patch, versionAtom.patch) || + comparePreRelease(rangeAtom, versionAtom) + ); +} +export function compare( + rangeAtom: CompareAtom, + versionAtom: CompareAtom, +): boolean { + switch (rangeAtom.operator) { + case '': + case '=': + return rangeAtom.version === versionAtom.version; + case '>': + return compareVersion(rangeAtom, versionAtom) < 0; + case '>=': + return ( + rangeAtom.version === versionAtom.version || + compareVersion(rangeAtom, versionAtom) < 0 + ); + case '<': + return compareVersion(rangeAtom, versionAtom) > 0; + case '<=': + return ( + rangeAtom.version === versionAtom.version || + compareVersion(rangeAtom, versionAtom) > 0 + ); + case undefined: { + return true; + } + default: + return false; + } +} +// --- range parsing & satisfy (public API) --- +function parseRange(range: string): string { + return pipe( + parseHyphen, + parseComparatorTrim, + parseTildeTrim, + parseCaretTrim, + )(range.trim()) + .split(/\s+/) + .join(' ') + .split(' ') + .map((rv) => pipe(parseCarets, parseTildes, parseXRanges, parseStar)(rv)) + .join(' '); +} +function buildAtom(extracted: RegExpMatchArray): CompareAtom { + const [, operator, , major, minor, patch, preRelease] = extracted; + return { + operator, + version: combineVersion(major, minor, patch, preRelease), + major, + minor, + patch, + preRelease: preRelease?.split('.'), + }; +} +export function satisfy(version: string, range: string): boolean { + if (!version) return false; + const extractedVersion = extractComparator(version); + if (!extractedVersion) return false; + const versionAtom = buildAtom(extractedVersion); + for (const orRange of range.split('||')) { + const trimmed = orRange.trim(); + if (!trimmed || trimmed === '*' || trimmed === 'x') return true; + try { + const parsedRange = parseRange(trimmed); + if (!parsedRange.trim()) return true; + const comparators = parsedRange + .split(/\s+/) + .map((c) => parseGTE0(c)) + .filter(Boolean); + if (comparators.length === 0) continue; + let satisfied = true; + for (const comp of comparators) { + const extracted = extractComparator(comp); + if (!extracted || !compare(buildAtom(extracted), versionAtom)) { + satisfied = false; + break; + } + } + if (satisfied) return true; + } catch (e) { + console.error(`[semver] Error processing range part "${trimmed}":`, e); + continue; + } + } + return false; +} +export { isXVersion }; +export function isLegallyVersion(version: string): boolean { + const semverRegex = + /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/; + return semverRegex.test(version); +} diff --git a/packages/sdk/src/semver/parser.ts b/packages/sdk/src/semver/parser.ts new file mode 100644 index 00000000000..d74f4c10924 --- /dev/null +++ b/packages/sdk/src/semver/parser.ts @@ -0,0 +1,182 @@ +// fork from https://github.com/originjs/vite-plugin-federation/blob/v1.1.12/packages/lib/src/utils/semver/index.ts +// those constants are based on https://www.rubydoc.info/gems/semantic_range/3.0.0/SemanticRange#BUILDIDENTIFIER-constant +// Copyright (c) +// vite-plugin-federation is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. +export function isXVersion(version: string): boolean { + return !version || version.toLowerCase() === 'x' || version === '*'; +} +// --- regex constants (formerly constants.ts) --- +const buildIdentifier = '[0-9A-Za-z-]+'; +const build = `(?:\\+(${buildIdentifier}(?:\\.${buildIdentifier})*))`; +const numericIdentifier = '0|[1-9]\\d*'; +const numericIdentifierLoose = '[0-9]+'; +const nonNumericIdentifier = '\\d*[a-zA-Z-][a-zA-Z0-9-]*'; +const preReleaseIdentifierLoose = `(?:${numericIdentifierLoose}|${nonNumericIdentifier})`; +const preReleaseLoose = `(?:-?(${preReleaseIdentifierLoose}(?:\\.${preReleaseIdentifierLoose})*))`; +const preReleaseIdentifier = `(?:${numericIdentifier}|${nonNumericIdentifier})`; +const preRelease = `(?:-(${preReleaseIdentifier}(?:\\.${preReleaseIdentifier})*))`; +const xRangeIdentifier = `${numericIdentifier}|x|X|\\*`; +const xRangePlain = `[v=\\s]*(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:${preRelease})?${build}?)?)?`; +const hyphenRange = new RegExp( + `^\\s*(${xRangePlain})\\s+-\\s+(${xRangePlain})\\s*$`, +); +const mainVersionLoose = `(${numericIdentifierLoose})\\.(${numericIdentifierLoose})\\.(${numericIdentifierLoose})`; +const loosePlain = `[v=\\s]*${mainVersionLoose}${preReleaseLoose}?${build}?`; +const gtlt = '((?:<|>)?=?)'; +const comparatorTrim = new RegExp( + `(\\s*)${gtlt}\\s*(${loosePlain}|${xRangePlain})`, +); +const loneTilde = '(?:~>?)'; +const tildeTrim = new RegExp(`(\\s*)${loneTilde}\\s+`); +const loneCaret = '(?:\\^)'; +const caretTrim = new RegExp(`(\\s*)${loneCaret}\\s+`); +const star = new RegExp('(<|>)?=?\\s*\\*'); +const caret = new RegExp(`^${loneCaret}${xRangePlain}$`); +const mainVersion = `(${numericIdentifier})\\.(${numericIdentifier})\\.(${numericIdentifier})`; +const fullPlain = `v?${mainVersion}${preRelease}?${build}?`; +const tilde = new RegExp(`^${loneTilde}${xRangePlain}$`); +const xRange = new RegExp(`^${gtlt}\\s*${xRangePlain}$`); +export const comparator = new RegExp(`^${gtlt}\\s*(${fullPlain})$|^$`); +const gte0 = new RegExp('^\\s*>=\\s*0.0.0\\s*$'); +export { + hyphenRange, + comparatorTrim, + tildeTrim, + caretTrim, + star, + caret, + tilde, + xRange, + gte0, +}; +// --- parser functions --- +function applyRangeRule( + range: string, + regex: RegExp, + replacer: (...args: string[]) => string, +): string { + return range + .trim() + .split(/\s+/) + .map((rv) => rv.trim().replace(regex, replacer)) + .join(' '); +} +export function parseHyphen(range: string): string { + return range.replace( + hyphenRange, + (_range, from, fM, fMi, fP, _fPR, _fB, to, tM, tMi, tP, tPR) => { + from = isXVersion(fM) + ? '' + : isXVersion(fMi) + ? `>=${fM}.0.0` + : isXVersion(fP) + ? `>=${fM}.${fMi}.0` + : `>=${from}`; + to = isXVersion(tM) + ? '' + : isXVersion(tMi) + ? `<${Number(tM) + 1}.0.0-0` + : isXVersion(tP) + ? `<${tM}.${Number(tMi) + 1}.0-0` + : tPR + ? `<=${tM}.${tMi}.${tP}-${tPR}` + : `<=${to}`; + return `${from} ${to}`.trim(); + }, + ); +} +export function parseComparatorTrim(range: string): string { + return range.replace(comparatorTrim, '$1$2$3'); +} +export function parseTildeTrim(range: string): string { + return range.replace(tildeTrim, '$1~'); +} +export function parseCaretTrim(range: string): string { + return range.replace(caretTrim, '$1^'); +} +export function parseCarets(range: string): string { + return applyRangeRule(range, caret, (_, major, minor, patch, preRelease) => { + if (isXVersion(major)) return ''; + if (isXVersion(minor)) return `>=${major}.0.0 <${Number(major) + 1}.0.0-0`; + if (isXVersion(patch)) { + return major === '0' + ? `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0-0` + : `>=${major}.${minor}.0 <${Number(major) + 1}.0.0-0`; + } + const pre = preRelease ? `-${preRelease}` : ''; + const gte = `>=${major}.${minor}.${patch}${pre}`; + const lt = + major !== '0' + ? `<${Number(major) + 1}.0.0-0` + : minor !== '0' + ? `<${major}.${Number(minor) + 1}.0-0` + : `<${major}.${minor}.${Number(patch) + 1}-0`; + return `${gte} ${lt}`; + }); +} +export function parseTildes(range: string): string { + return applyRangeRule(range, tilde, (_, major, minor, patch, preRelease) => { + if (isXVersion(major)) { + return ''; + } else if (isXVersion(minor)) { + return `>=${major}.0.0 <${Number(major) + 1}.0.0-0`; + } else if (isXVersion(patch)) { + return `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0-0`; + } else if (preRelease) { + return `>=${major}.${minor}.${patch}-${preRelease} <${major}.${ + Number(minor) + 1 + }.0-0`; + } + return `>=${major}.${minor}.${patch} <${major}.${Number(minor) + 1}.0-0`; + }); +} +export function parseXRanges(range: string): string { + return range + .split(/\s+/) + .map((rv) => + rv.trim().replace(xRange, (ret, gtlt, major, minor, patch, _pr) => { + const xM = isXVersion(major), + xMi = xM || isXVersion(minor), + xP = xMi || isXVersion(patch); + if (gtlt === '=' && xP) gtlt = ''; + let pre = ''; + if (xM) return gtlt === '>' || gtlt === '<' ? '<0.0.0-0' : '*'; + if (gtlt && xP) { + if (xMi) minor = 0; + patch = 0; + if (gtlt === '>') { + gtlt = '>='; + if (xMi) { + major = Number(major) + 1; + minor = 0; + } else { + minor = Number(minor) + 1; + } + patch = 0; + } else if (gtlt === '<=') { + gtlt = '<'; + if (xMi) major = Number(major) + 1; + else minor = Number(minor) + 1; + } + if (gtlt === '<') pre = '-0'; + return `${gtlt + major}.${minor}.${patch}${pre}`; + } + if (xMi) return `>=${major}.0.0 <${Number(major) + 1}.0.0-0`; + if (xP) + return `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0-0`; + return ret; + }), + ) + .join(' '); +} +export function parseStar(range: string): string { + return range.trim().replace(star, ''); +} +export function parseGTE0(comparatorString: string): string { + return comparatorString.trim().replace(gte0, ''); +} diff --git a/packages/utilities/project.json b/packages/utilities/project.json index 24913fe966a..cd917b82524 100644 --- a/packages/utilities/project.json +++ b/packages/utilities/project.json @@ -3,21 +3,37 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "packages/utilities/src", "projectType": "library", + "pre": [ + { + "executor": "@nx/js:tsc", + "options": { + "commands": [ + { + "command": "rm -f dist" + } + ] + } + } + ], "tags": ["type:pkg"], "targets": { "build": { - "executor": "nx:run-commands", - "outputs": ["{workspaceRoot}/packages/utilities/dist"], + "executor": "@nx/rollup:rollup", + "outputs": ["{options.outputPath}"], "options": { - "command": "rslib build", - "cwd": "packages/utilities" - }, - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ] + "outputPath": "packages/utilities/dist", + "main": "packages/utilities/src/index.ts", + "tsConfig": "packages/utilities/tsconfig.lib.json", + "assets": [], + "generateExportsField": true, + "project": "packages/utilities/package.json", + "additionalEntryPoints": ["packages/utilities/src/types/types.ts"], + "external": ["@module-federation/*"], + "compiler": "tsc", + "format": ["cjs", "esm"], + "generatePackageJson": false, + "useLegacyTypescriptPlugin": false + } }, "lint": { "executor": "@nx/eslint:lint", diff --git a/packages/utilities/rslib.config.ts b/packages/utilities/rslib.config.ts index daa3b5d5b5a..6898d97ebfe 100644 --- a/packages/utilities/rslib.config.ts +++ b/packages/utilities/rslib.config.ts @@ -2,7 +2,8 @@ import { pluginPublint } from 'rsbuild-plugin-publint'; import { defineConfig } from '@rslib/core'; export default defineConfig({ - plugins: [pluginPublint()], + // Cast to bridge transient rsbuild type version skew in CI lockfile combos. + plugins: [pluginPublint() as unknown as never], lib: [ { format: 'esm', diff --git a/packages/utilities/tsconfig.lib.json b/packages/utilities/tsconfig.lib.json index 43234423ad3..5ed83001388 100644 --- a/packages/utilities/tsconfig.lib.json +++ b/packages/utilities/tsconfig.lib.json @@ -2,11 +2,10 @@ "extends": "./tsconfig.json", "compilerOptions": { "jsx": "react", - "rootDir": "./src", "declaration": true, "types": ["node"] }, - "include": ["src/**/*.ts", "src/**/*.tsx"], + "include": ["**/*.ts", "**/*.tsx"], "exclude": [ "jest.config.ts", "**/*.spec.ts", diff --git a/packages/webpack-bundler-runtime/src/index.ts b/packages/webpack-bundler-runtime/src/index.ts index dc9b130f0eb..d611983f45a 100644 --- a/packages/webpack-bundler-runtime/src/index.ts +++ b/packages/webpack-bundler-runtime/src/index.ts @@ -28,12 +28,4 @@ const federation: Federation = { attachShareScopeMap, bundlerRuntimeOptions: {}, }; - -// Keep CJS interop stable for consumers that iterate required keys directly. -export { runtime, attachShareScopeMap }; -export const instance = federation.instance; -export const initOptions = federation.initOptions; -export const bundlerRuntime = federation.bundlerRuntime; -export const bundlerRuntimeOptions = federation.bundlerRuntimeOptions; - export default federation; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 191c25d70e3..614ff7fc596 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -929,7 +929,7 @@ importers: version: 0.80.0(@babel/core@7.28.6) '@react-native/eslint-config': specifier: 0.80.0 - version: 0.80.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)))(prettier@2.8.8)(typescript@5.0.4) + version: 0.80.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)))(prettier@2.8.8)(typescript@5.0.4) '@react-native/gradle-plugin': specifier: 0.80.0 version: 0.80.0 @@ -968,7 +968,7 @@ importers: version: 9.26.0(hono@4.11.10)(jiti@2.6.1) jest: specifier: ^29.6.3 - version: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)) + version: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)) nodemon: specifier: ^3.1.9 version: 3.1.11 @@ -1020,7 +1020,7 @@ importers: version: 0.80.0(@babel/core@7.28.6) '@react-native/eslint-config': specifier: 0.80.0 - version: 0.80.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)))(prettier@2.8.8)(typescript@5.0.4) + version: 0.80.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)))(prettier@2.8.8)(typescript@5.0.4) '@react-native/metro-config': specifier: 0.80.0 version: 0.80.0(@babel/core@7.28.6) @@ -1056,7 +1056,7 @@ importers: version: 9.26.0(hono@4.11.10)(jiti@2.6.1) jest: specifier: ^29.6.3 - version: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)) + version: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)) nodemon: specifier: ^3.1.9 version: 3.1.11 @@ -1108,7 +1108,7 @@ importers: version: 0.80.0(@babel/core@7.28.6) '@react-native/eslint-config': specifier: 0.80.0 - version: 0.80.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)))(prettier@2.8.8)(typescript@5.0.4) + version: 0.80.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)))(prettier@2.8.8)(typescript@5.0.4) '@react-native/metro-config': specifier: 0.80.0 version: 0.80.0(@babel/core@7.28.6) @@ -1144,7 +1144,7 @@ importers: version: 9.26.0(hono@4.11.10)(jiti@2.6.1) jest: specifier: ^29.6.3 - version: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)) + version: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)) nodemon: specifier: ^3.1.9 version: 3.1.11 @@ -2754,9 +2754,6 @@ importers: react-dom: specifier: 19.1.1 version: 19.1.1(react@19.1.1) - react-router-dom: - specifier: ^7.6.1 - version: 7.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) tailwindcss: specifier: ^3.2.7 version: 3.4.13(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)) @@ -3158,7 +3155,7 @@ importers: version: 18.0.38 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)) + version: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 @@ -3179,10 +3176,10 @@ importers: version: 18.3.1(react@18.3.1) ts-jest: specifier: 29.0.1 - version: 29.0.1(@babel/core@7.29.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(esbuild@0.25.0)(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)))(typescript@5.8.2) + version: 29.0.1(@babel/core@7.29.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(esbuild@0.25.0)(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)))(typescript@5.8.2) webpack: specifier: 5.104.1 - version: 5.104.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.0)(webpack-cli@5.1.4) + version: 5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.25.0)(webpack-cli@5.1.4) packages/dts-plugin: dependencies: @@ -3506,6 +3503,8 @@ importers: specifier: ^5.8.3 version: 5.9.3 + packages/micro-effect: {} + packages/modernjs: dependencies: '@modern-js/utils': @@ -3903,8 +3902,8 @@ importers: specifier: workspace:* version: link:../sdk '@rspress/shared': - specifier: 2.0.3 - version: 2.0.3(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0) + specifier: 2.0.1 + version: 2.0.1(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0) cheerio: specifier: 1.0.0-rc.12 version: 1.0.0-rc.12 @@ -3922,8 +3921,8 @@ importers: specifier: ^0.9.2 version: 0.9.2(@microsoft/api-extractor@7.55.2(@types/node@22.19.9))(typescript@5.9.3) '@rspress/core': - specifier: 2.0.3 - version: 2.0.3(@module-federation/runtime-tools@2.0.1)(@types/react@18.3.11)(core-js@3.48.0)(webpack-hot-middleware@2.26.1) + specifier: 2.0.1 + version: 2.0.1(@module-federation/runtime-tools@2.0.1)(@types/react@18.3.11)(core-js@3.48.0)(webpack-hot-middleware@2.26.1) '@types/html-to-text': specifier: ^9.0.4 version: 9.0.4 @@ -3951,6 +3950,9 @@ importers: '@module-federation/error-codes': specifier: workspace:* version: link:../error-codes + '@module-federation/micro-effect': + specifier: workspace:* + version: link:../micro-effect '@module-federation/sdk': specifier: workspace:* version: link:../sdk @@ -4658,8 +4660,8 @@ packages: resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} - '@babel/generator@8.0.0-rc.1': - resolution: {integrity: sha512-3ypWOOiC4AYHKr8vYRVtWtWmyvcoItHtVqF8paFax+ydpmUdPsJpLBkBBs5ItmhdrwC3a0ZSqqFAdzls4ODP3w==} + '@babel/generator@8.0.0-rc.2': + resolution: {integrity: sha512-oCQ1IKPwkzCeJzAPb7Fv8rQ9k5+1sG8mf2uoHiMInPYvkRfrDJxbTIbH51U+jstlkghus0vAi3EBvkfvEsYNLQ==} engines: {node: ^20.19.0 || >=22.12.0} '@babel/helper-annotate-as-pure@7.27.3': @@ -4744,8 +4746,8 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@8.0.0-rc.1': - resolution: {integrity: sha512-I4YnARytXC2RzkLNVnf5qFNFMzp679qZpmtw/V3Jt2uGnWiIxyJtaukjG7R8pSx8nG2NamICpGfljQsogj+FbQ==} + '@babel/helper-validator-identifier@8.0.0-rc.2': + resolution: {integrity: sha512-xExUBkuXWJjVuIbO7z6q7/BA9bgfJDEhVL0ggrggLMbg0IzCUWGT1hZGE8qUH7Il7/RD/a6cZ3AAFrrlp1LF/A==} engines: {node: ^20.19.0 || >=22.12.0} '@babel/helper-validator-option@7.27.1': @@ -4770,8 +4772,8 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@8.0.0-rc.1': - resolution: {integrity: sha512-6HyyU5l1yK/7h9Ki52i5h6mDAx4qJdiLQO4FdCyJNoB/gy3T3GGJdhQzzbZgvgZCugYBvwtQiWRt94QKedHnkA==} + '@babel/parser@8.0.0-rc.2': + resolution: {integrity: sha512-29AhEtcq4x8Dp3T72qvUMZHx0OMXCj4Jy/TEReQa+KWLln524Cj1fWb3QFi0l/xSpptQBR6y9RNEXuxpFvwiUQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -5443,8 +5445,8 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@babel/types@8.0.0-rc.1': - resolution: {integrity: sha512-ubmJ6TShyaD69VE9DQrlXcdkvJbmwWPB8qYj0H2kaJi29O7vJT9ajSdBd2W8CG34pwL9pYA74fi7RHC1qbLoVQ==} + '@babel/types@8.0.0-rc.2': + resolution: {integrity: sha512-91gAaWRznDwSX4E2tZ1YjBuIfnQVOFDCQ2r0Toby0gu4XEbyF623kXLMA8d4ZbCu+fINcrudkmEcwSUHgDDkNw==} engines: {node: ^20.19.0 || >=22.12.0} '@base2/pretty-print-object@1.0.1': @@ -5782,12 +5784,15 @@ packages: '@dinero.js/calculator-number@2.0.0-alpha.10': resolution: {integrity: sha512-EdKG0yykukigfdq+TsxZ9r0Wrg5flYAncKWSfr2snWDXurFsg8JE0oazVraCBA3Vb5LN4vGuFEpTFTH+dIrRCg==} + deprecated: This package has been consolidated into dinero.js. See https://v2.dinerojs.com/getting-started/upgrade-guide '@dinero.js/core@2.0.0-alpha.10': resolution: {integrity: sha512-vjeGXQbNvDXlXK54zaWDydEXyFAvLDj6LCfwO4CTZJIqn3+PaXakaEd5S0AXC6hluPatxnQa5J63x3WQ/Imrjw==} + deprecated: This package has been consolidated into dinero.js. See https://v2.dinerojs.com/getting-started/upgrade-guide '@dinero.js/currencies@2.0.0-alpha.10': resolution: {integrity: sha512-IDKaAh0YcJh700uLCrvWtIRCl5sItc3S2rk4IfVJBbms3j+NBDOlVFJnwru+UrMh7VpqU9GlZRsHcHf0NxYE9A==} + deprecated: This package has been consolidated into dinero.js. See https://v2.dinerojs.com/getting-started/upgrade-guide '@discoveryjs/json-ext@0.5.7': resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} @@ -5900,6 +5905,12 @@ packages: '@emotion/weak-memoize@0.4.0': resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + '@esbuild/aix-ppc64@0.19.12': + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -5930,6 +5941,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.19.12': + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.19.2': resolution: {integrity: sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw==} engines: {node: '>=12'} @@ -5966,6 +5983,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.19.12': + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.19.2': resolution: {integrity: sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q==} engines: {node: '>=12'} @@ -6002,6 +6025,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.19.12': + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.19.2': resolution: {integrity: sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w==} engines: {node: '>=12'} @@ -6038,6 +6067,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.19.12': + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.19.2': resolution: {integrity: sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA==} engines: {node: '>=12'} @@ -6074,6 +6109,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.19.12': + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.19.2': resolution: {integrity: sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw==} engines: {node: '>=12'} @@ -6110,6 +6151,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.19.12': + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.19.2': resolution: {integrity: sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ==} engines: {node: '>=12'} @@ -6146,6 +6193,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.19.12': + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.19.2': resolution: {integrity: sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw==} engines: {node: '>=12'} @@ -6182,6 +6235,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.19.12': + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.19.2': resolution: {integrity: sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg==} engines: {node: '>=12'} @@ -6218,6 +6277,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.19.12': + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.19.2': resolution: {integrity: sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg==} engines: {node: '>=12'} @@ -6254,6 +6319,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.19.12': + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.19.2': resolution: {integrity: sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ==} engines: {node: '>=12'} @@ -6290,6 +6361,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.19.12': + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.19.2': resolution: {integrity: sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw==} engines: {node: '>=12'} @@ -6326,6 +6403,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.19.12': + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.19.2': resolution: {integrity: sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg==} engines: {node: '>=12'} @@ -6362,6 +6445,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.19.12': + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.19.2': resolution: {integrity: sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw==} engines: {node: '>=12'} @@ -6398,6 +6487,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.19.12': + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.19.2': resolution: {integrity: sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw==} engines: {node: '>=12'} @@ -6434,6 +6529,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.19.12': + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.19.2': resolution: {integrity: sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g==} engines: {node: '>=12'} @@ -6470,6 +6571,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.19.12': + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.19.2': resolution: {integrity: sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ==} engines: {node: '>=12'} @@ -6518,6 +6625,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.19.12': + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.19.2': resolution: {integrity: sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ==} engines: {node: '>=12'} @@ -6566,6 +6679,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.19.12': + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.19.2': resolution: {integrity: sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw==} engines: {node: '>=12'} @@ -6602,6 +6721,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.19.12': + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.19.2': resolution: {integrity: sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw==} engines: {node: '>=12'} @@ -6638,6 +6763,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.19.12': + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.19.2': resolution: {integrity: sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg==} engines: {node: '>=12'} @@ -6674,6 +6805,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.19.12': + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.19.2': resolution: {integrity: sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA==} engines: {node: '>=12'} @@ -6710,6 +6847,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.19.12': + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.19.2': resolution: {integrity: sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw==} engines: {node: '>=12'} @@ -8867,6 +9010,9 @@ packages: '@oxc-project/types@0.112.0': resolution: {integrity: sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ==} + '@oxc-project/types@0.114.0': + resolution: {integrity: sha512-//nBfbzHQHvJs8oFIjv6coZ6uxQ4alLfiPe6D5vit6c4pmxATHHlVwgB1k+Hv4yoAMyncdxgRBF5K4BYWUCzvA==} + '@oxc-resolver/binding-darwin-arm64@5.3.0': resolution: {integrity: sha512-hXem5ZAguS7IlSiHg/LK0tEfLj4eUo+9U6DaFwwBEGd0L0VIF9LmuiHydRyOrdnnmi9iAAFMAn/wl2cUoiuruA==} cpu: [arm64] @@ -10685,77 +10831,154 @@ packages: cpu: [arm64] os: [android] + '@rolldown/binding-android-arm64@1.0.0-rc.5': + resolution: {integrity: sha512-zCEmUrt1bggwgBgeKLxNj217J1OrChrp3jJt24VK9jAharSTeVaHODNL+LpcQVhRz+FktYWfT9cjo5oZ99ZLpg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-darwin-arm64@1.0.0-rc.3': resolution: {integrity: sha512-JWWLzvcmc/3pe7qdJqPpuPk91SoE/N+f3PcWx/6ZwuyDVyungAEJPvKm/eEldiDdwTmaEzWfIR+HORxYWrCi1A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.0-rc.5': + resolution: {integrity: sha512-ZP9xb9lPAex36pvkNWCjSEJW/Gfdm9I3ssiqOFLmpZ/vosPXgpoGxCmh+dX1Qs+/bWQE6toNFXWWL8vYoKoK9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.3': resolution: {integrity: sha512-MTakBxfx3tde5WSmbHxuqlDsIW0EzQym+PJYGF4P6lG2NmKzi128OGynoFUqoD5ryCySEY85dug4v+LWGBElIw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.5': + resolution: {integrity: sha512-7IdrPunf6dp9mywMgTOKMMGDnMHQ6+h5gRl6LW8rhD8WK2kXX0IwzcM5Zc0B5J7xQs8QWOlKjv8BJsU/1CD3pg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-freebsd-x64@1.0.0-rc.3': resolution: {integrity: sha512-jje3oopyOLs7IwfvXoS6Lxnmie5JJO7vW29fdGFu5YGY1EDbVDhD+P9vDihqS5X6fFiqL3ZQZCMBg6jyHkSVww==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.0-rc.5': + resolution: {integrity: sha512-o/JCk+dL0IN68EBhZ4DqfsfvxPfMeoM6cJtxORC1YYoxGHZyth2Kb2maXDb4oddw2wu8iIbnYXYPEzBtAF5CAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3': resolution: {integrity: sha512-A0n8P3hdLAaqzSFrQoA42p23ZKBYQOw+8EH5r15Sa9X1kD9/JXe0YT2gph2QTWvdr0CVK2BOXiK6ENfy6DXOag==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.5': + resolution: {integrity: sha512-IIBwTtA6VwxQLcEgq2mfrUgam7VvPZjhd/jxmeS1npM+edWsrrpRLHUdze+sk4rhb8/xpP3flemgcZXXUW6ukw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': resolution: {integrity: sha512-kWXkoxxarYISBJ4bLNf5vFkEbb4JvccOwxWDxuK9yee8lg5XA7OpvlTptfRuwEvYcOZf+7VS69Uenpmpyo5Bjw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.5': + resolution: {integrity: sha512-KSol1De1spMZL+Xg7K5IBWXIvRWv7+pveaxFWXpezezAG7CS6ojzRjtCGCiLxQricutTAi/LkNWKMsd2wNhMKQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': resolution: {integrity: sha512-Z03/wrqau9Bicfgb3Dbs6SYTHliELk2PM2LpG2nFd+cGupTMF5kanLEcj2vuuJLLhptNyS61rtk7SOZ+lPsTUA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.5': + resolution: {integrity: sha512-WFljyDkxtXRlWxMjxeegf7xMYXxUr8u7JdXlOEWKYgDqEgxUnSEsVDxBiNWQ1D5kQKwf8Wo4sVKEYPRhCdsjwA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': resolution: {integrity: sha512-iSXXZsQp08CSilff/DCTFZHSVEpEwdicV3W8idHyrByrcsRDVh9sGC3sev6d8BygSGj3vt8GvUKBPCoyMA4tgQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.5': + resolution: {integrity: sha512-CUlplTujmbDWp2gamvrqVKi2Or8lmngXT1WxsizJfts7JrvfGhZObciaY/+CbdbS9qNnskvwMZNEhTPrn7b+WA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': resolution: {integrity: sha512-qaj+MFudtdCv9xZo9znFvkgoajLdc+vwf0Kz5N44g+LU5XMe+IsACgn3UG7uTRlCCvhMAGXm1XlpEA5bZBrOcw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + '@rolldown/binding-linux-x64-musl@1.0.0-rc.5': + resolution: {integrity: sha512-wdf7g9NbVZCeAo2iGhsjJb7I8ZFfs6X8bumfrWg82VK+8P6AlLXwk48a1ASiJQDTS7Svq2xVzZg3sGO2aXpHRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.3': resolution: {integrity: sha512-U662UnMETyjT65gFmG9ma+XziENrs7BBnENi/27swZPYagubfHRirXHG2oMl+pEax2WvO7Kb9gHZmMakpYqBHQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.5': + resolution: {integrity: sha512-0CWY7ubu12nhzz+tkpHjoG3IRSTlWYe0wrfJRf4qqjqQSGtAYgoL9kwzdvlhaFdZ5ffVeyYw9qLsChcjUMEloQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.3': resolution: {integrity: sha512-gekrQ3Q2HiC1T5njGyuUJoGpK/l6B/TNXKed3fZXNf9YRTJn3L5MOZsFBn4bN2+UX+8+7hgdlTcEsexX988G4g==} engines: {node: '>=14.0.0'} cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.5': + resolution: {integrity: sha512-LztXnGzv6t2u830mnZrFLRVqT/DPJ9DL4ZTz/y93rqUVkeHjMMYIYaFj+BUthiYxbVH9dH0SZYufETspKY/NhA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3': resolution: {integrity: sha512-85y5JifyMgs8m5K2XzR/VDsapKbiFiohl7s5lEj7nmNGO0pkTXE7q6TQScei96BNAsoK7JC3pA7ukA8WRHVJpg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.5': + resolution: {integrity: sha512-jUct1XVeGtyjqJXEAfvdFa8xoigYZ2rge7nYEm70ppQxpfH9ze2fbIrpHmP2tNM2vL/F6Dd0CpXhpjPbC6bSxQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.3': resolution: {integrity: sha512-a4VUQZH7LxGbUJ3qJ/TzQG8HxdHvf+jOnqf7B7oFx1TEBm+j2KNL2zr5SQ7wHkNAcaPevF6gf9tQnVBnC4mD+A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.5': + resolution: {integrity: sha512-VQ8F9ld5gw29epjnVGdrx8ugiLTe8BMqmhDYy7nGbdeDo4HAt4bgdZvLbViEhg7DZyHLpiEUlO5/jPSUrIuxRQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -10765,6 +10988,9 @@ packages: '@rolldown/pluginutils@1.0.0-rc.3': resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} + '@rolldown/pluginutils@1.0.0-rc.5': + resolution: {integrity: sha512-RxlLX/DPoarZ9PtxVrQgZhPoor987YtKQqCo5zkjX+0S0yLJ7Vv515Wk6+xtTL67VONKJKxETWZwuZjss2idYw==} + '@rollup/plugin-alias@3.1.9': resolution: {integrity: sha512-QI5fsEvm9bDzt32k39wpOwZhVzRcL5ydcffUHMyLVaVaLeC70I8TJZ17F1z1eMoLu4E/UOcH9BWVkKpIKdrfiw==} engines: {node: '>=8.0.0'} @@ -11057,6 +11283,16 @@ packages: engines: {node: '>=18.12.0'} hasBin: true + '@rsbuild/core@2.0.0-beta.0': + resolution: {integrity: sha512-TT9gR1fr1EWe6t5FewTqqkJtoVIFVJ/xnnYEMJARpPfuPB+Shi75OZmeLvNH1pHF6q/vyRT76MgvpBS7axcGIw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + core-js: '>= 3.0.0' + peerDependenciesMeta: + core-js: + optional: true + '@rsbuild/core@2.0.0-beta.2': resolution: {integrity: sha512-VVRtw+/UqGuXrsI5l7kiPwDvoDJHOi3LwBb3T+1obxRZJkbZmGlXv8b9u//O/2pMN1Etx5lxKq/8QkcE+uD7Pg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -11437,6 +11673,11 @@ packages: cpu: [arm64] os: [darwin] + '@rspack/binding-darwin-arm64@2.0.0-alpha.1': + resolution: {integrity: sha512-+6E6pYgpKvs41cyOlqRjpCT3djjL9hnntF61JumM/TNo1aTYXMNNG4b8ZsLMpBq5ZwCy9Dg8oEDe8AZ84rfM7A==} + cpu: [arm64] + os: [darwin] + '@rspack/binding-darwin-arm64@2.0.0-beta.0': resolution: {integrity: sha512-PPx1+SPEROSvDKmBuCbsE7W9tk07ajPosyvyuafv2wbBI6PW2rNcz62uzpIFS+FTgwwZ5u/06WXRtlD2xW9bKg==} cpu: [arm64] @@ -11487,6 +11728,11 @@ packages: cpu: [x64] os: [darwin] + '@rspack/binding-darwin-x64@2.0.0-alpha.1': + resolution: {integrity: sha512-Ccf9NNupVe67vlaS9zKQJ+BvsAn385uBC1vXnYaUxxHoY/tEwNJf6t+XyDARt7mCtT7+Bu4L/iJ/JEF/MsO5zg==} + cpu: [x64] + os: [darwin] + '@rspack/binding-darwin-x64@2.0.0-beta.0': resolution: {integrity: sha512-GucsfjrSKBZ9cuOTXmHWxeY2wPmaNyvGNxTyzttjRcfwqOWz8r+ku6PCsMSXUqxZRYWW1L9mvtTdlDrzTYJZ0w==} cpu: [x64] @@ -11537,6 +11783,11 @@ packages: cpu: [arm64] os: [linux] + '@rspack/binding-linux-arm64-gnu@2.0.0-alpha.1': + resolution: {integrity: sha512-B7omNsPSsinOq2VRD4d4VFrLgHceMQobqlLg0txFUZ7PDjE307gpTcGViWQlUhNCbkZXMPzDeXBFa5ZlEmxgnA==} + cpu: [arm64] + os: [linux] + '@rspack/binding-linux-arm64-gnu@2.0.0-beta.0': resolution: {integrity: sha512-nTtYtklRZD4sb2RIFCF9YS8tZ/MjpqIBKVS3YIvdXcfHUdVfmQHTZGtwEuZGg6AxTC5L1hcvkYmTXCG0ok7auw==} cpu: [arm64] @@ -11587,6 +11838,11 @@ packages: cpu: [arm64] os: [linux] + '@rspack/binding-linux-arm64-musl@2.0.0-alpha.1': + resolution: {integrity: sha512-NCG401ofZcDKlTWD8VHv76Y+02Stmd9Nu5MRbVUBOCTVgXMj8Mgrm5XsGBWUjzd5J/Mvo2hstCKIZxNzmPd8uQ==} + cpu: [arm64] + os: [linux] + '@rspack/binding-linux-arm64-musl@2.0.0-beta.0': resolution: {integrity: sha512-S2fshx0Rf7/XYwoMLaqFsVg4y+VAfHzubrczy8AW5xIs6UNC3eRLVTgShLerUPtF6SG+v6NQxQ9JI3vOo2qPOA==} cpu: [arm64] @@ -11637,6 +11893,11 @@ packages: cpu: [x64] os: [linux] + '@rspack/binding-linux-x64-gnu@2.0.0-alpha.1': + resolution: {integrity: sha512-Xgp8wJ5gjpPG8I3VMEsVAesfckWryQVUhJkHcxPfNi72QTv8UkMER7Jl+JrlQk7K7nMO5ltokx/VGl1c3tMx+w==} + cpu: [x64] + os: [linux] + '@rspack/binding-linux-x64-gnu@2.0.0-beta.0': resolution: {integrity: sha512-yx5Fk1gl7lfkvqcjolNLCNeduIs6C2alMsQ/kZ1pLeP5MPquVOYNqs6EcDPIp+fUjo3lZYtnJBiZKK+QosbzYg==} cpu: [x64] @@ -11687,6 +11948,11 @@ packages: cpu: [x64] os: [linux] + '@rspack/binding-linux-x64-musl@2.0.0-alpha.1': + resolution: {integrity: sha512-lrYKcOgsPA1UMswxzFAV37ofkznbtTLCcEas6lxtlT3Dr28P6VRzC8TgVbIiprkm10I0BlThQWDJ3aGzzLj9Kg==} + cpu: [x64] + os: [linux] + '@rspack/binding-linux-x64-musl@2.0.0-beta.0': resolution: {integrity: sha512-sBX4b2W0PgehlAVT224k0Q6GaH6t9HP+hBNDrbX/g6d0hfxZN56gm5NfOTOD1Rien4v7OBEejJ3/uFbm1WjwYQ==} cpu: [x64] @@ -11708,6 +11974,10 @@ packages: resolution: {integrity: sha512-rGNHrk2QuLFfwOTib91skuLh2aMYeTP4lgM4zanDhtt95DLDlwioETFY7FzY1WmS+Z3qnEyrgQIRp8osy0NKTw==} cpu: [wasm32] + '@rspack/binding-wasm32-wasi@2.0.0-alpha.1': + resolution: {integrity: sha512-rppGiT7CtXlM8st+IgzBDqb7V//1xx5Oe0SY1sxxw0cfOGMpIQCwhJqx/uI6ioqJLZLGX/obt359+hPXyqGl4w==} + cpu: [wasm32] + '@rspack/binding-wasm32-wasi@2.0.0-beta.0': resolution: {integrity: sha512-o6OatnNvb4kCzXbCaomhENGaCsO3naIyAqqErew90HeAwa1lfY3NhRfDLeIyuANQ+xqFl34/R7n8q3ZDx3nd4Q==} cpu: [wasm32] @@ -11757,6 +12027,11 @@ packages: cpu: [arm64] os: [win32] + '@rspack/binding-win32-arm64-msvc@2.0.0-alpha.1': + resolution: {integrity: sha512-yD2g1JmnCxrix/344r7lBn+RH+Nv8uWP0UDP8kwv4kQGCWr4U7IP8PKFpoyulVOgOUjvJpgImeyrDJ7R8he+5w==} + cpu: [arm64] + os: [win32] + '@rspack/binding-win32-arm64-msvc@2.0.0-beta.0': resolution: {integrity: sha512-neCzVllXzIqM8p8qKb89qV7wyk233gC/V9VrHIKbGeQjAEzpBsk5GOWlFbq5DDL6tivQ+uzYaTrZWm9tb2qxXg==} cpu: [arm64] @@ -11807,6 +12082,11 @@ packages: cpu: [ia32] os: [win32] + '@rspack/binding-win32-ia32-msvc@2.0.0-alpha.1': + resolution: {integrity: sha512-5qpQL5Qz3uYb56pwffEGzznXSX9TNkLpigQbIObfnUwX7WkdjgTT7oTHpjn2sRSLLNiJ/jCp2r4ZHvjmnNRsRA==} + cpu: [ia32] + os: [win32] + '@rspack/binding-win32-ia32-msvc@2.0.0-beta.0': resolution: {integrity: sha512-/f0n2eO+DxMKQm9IebeMQJITx8M/+RvY/i8d3sAQZBgR53izn8y7EcDlidXpr24/2DvkLbiub8IyCKPlhLB+1A==} cpu: [ia32] @@ -11857,6 +12137,11 @@ packages: cpu: [x64] os: [win32] + '@rspack/binding-win32-x64-msvc@2.0.0-alpha.1': + resolution: {integrity: sha512-dZ76NN9tXLaF2gnB/pU+PcK4Adf9tj8dY06KcWk5F81ur2V4UbrMfkWJkQprur8cgL/F49YtFMRWa4yp/qNbpQ==} + cpu: [x64] + os: [win32] + '@rspack/binding-win32-x64-msvc@2.0.0-beta.0': resolution: {integrity: sha512-dx4zgiAT88EQE7kEUpr7Z9EZAwLnO5FhzWzvd/cDK4bkqYsx+rTklgf/c0EYPBeroXCxlGiMsuC9wHAFNK7sFw==} cpu: [x64] @@ -11889,6 +12174,9 @@ packages: '@rspack/binding@1.7.5': resolution: {integrity: sha512-tlZfDHfGu765FBL3hIyjrr8slJZztv7rCM+KIczZS7UlJQDl1+WsDKUe/+E1Fw9SlmorLWK40+y3rLTHmMrN2A==} + '@rspack/binding@2.0.0-alpha.1': + resolution: {integrity: sha512-Glz0SNFYPtNVM+ExJ4ocSzW+oQhb1iHTmxqVEAILbL17Hq3N/nwZpo1cWEs6hJjn8cosJIb1VKbbgb/1goEtCQ==} + '@rspack/binding@2.0.0-beta.0': resolution: {integrity: sha512-L6PPqhwZWC2vzwdhBItNPXw+7V4sq+MBDRXLdd8NMqaJSCB5iKdJIbpbEQucST9Nn7V28IYoQTXs6+ol5vWUBA==} @@ -11979,6 +12267,18 @@ packages: '@swc/helpers': optional: true + '@rspack/core@2.0.0-alpha.1': + resolution: {integrity: sha512-2KK3hbxrRqzxtzg+ka7LsiEKIWIGIQz317k9HHC2U4IC5yLJ31K8y/vQfA1aIT2QcFls9gW7GyRjp8A4X5cvLA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@module-federation/runtime-tools': '>=0.22.0' + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@module-federation/runtime-tools': + optional: true + '@swc/helpers': + optional: true + '@rspack/core@2.0.0-beta.0': resolution: {integrity: sha512-aEqlQQjiXixT5i9S4DFtiAap8ZjF6pOgfY2ALHOizins/QqWyB8dyLxSoXdzt7JixmKcFmHkbL9XahO28BlVUA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -12021,6 +12321,11 @@ packages: webpack-hot-middleware: optional: true + '@rspress/core@2.0.1': + resolution: {integrity: sha512-Zaj74QJeaZe2ZprGCQumJPlmIM41MQQbfTkYjpcTp4BCxOw1thabcoCL/2QcquqNs2g3rRv5l0LghFxV1WmhYw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + '@rspress/core@2.0.3': resolution: {integrity: sha512-a+JJFiALqMxGJBqR38/lkN6tas42UF4jRIhu6RilC/3DdqpfqR8j6jjQFOmqoNKo6ZGXW2W+i1Pscn6drvoG3w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -12032,6 +12337,9 @@ packages: peerDependencies: '@rspress/core': ^2.0.1 + '@rspress/shared@2.0.1': + resolution: {integrity: sha512-0t12G/WSPuoXbut5kX7jSHsv1YUlsGVVz6Lc9XirgbTSJ01/K5WEA2j3Me3x8+d2ICcbVQp2oeCo+iTtVCZQwg==} + '@rspress/shared@2.0.3': resolution: {integrity: sha512-yI9G4P165fSsmm6QoYTUrdgUis1aFnDh04GcM4SQIpL3itvEZhGtItgoeGkX9EWbnEjhriwI8mTqDDJIp+vrGA==} @@ -17252,6 +17560,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.19.2: resolution: {integrity: sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg==} engines: {node: '>=12'} @@ -18372,8 +18685,11 @@ packages: get-them-args@1.3.2: resolution: {integrity: sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw==} - get-tsconfig@4.13.4: - resolution: {integrity: sha512-gKvvu/fh0hxWmR/Ty0Goc3u/GADL9IgyhNAPD8hElRVO9dTOawCuyGNURCjaSTB4ZNP/OAUaSXmR2LhitzkLug==} + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + get-tsconfig@4.13.6: + resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} get-value@2.0.6: resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} @@ -18399,6 +18715,7 @@ packages: git-raw-commits@4.0.0: resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} engines: {node: '>=16'} + deprecated: This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead. hasBin: true github-slugger@1.5.0: @@ -24457,14 +24774,14 @@ packages: resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==} engines: {node: '>= 0.8'} - rolldown-plugin-dts@0.22.1: - resolution: {integrity: sha512-5E0AiM5RSQhU6cjtkDFWH6laW4IrMu0j1Mo8x04Xo1ALHmaRMs9/7zej7P3RrryVHW/DdZAp85MA7Be55p0iUw==} + rolldown-plugin-dts@0.22.3: + resolution: {integrity: sha512-APIGZGChvLVu05f+7bMmgf+lpvhjIvELhkOsg7c/95IVdOgULVFOX9iciaHJLaBfZeTthIgp+gryGBjgyKNA1A==} engines: {node: '>=20.19.0'} peerDependencies: '@ts-macro/tsc': ^0.3.6 '@typescript/native-preview': '>=7.0.0-dev.20250601.1' rolldown: ^1.0.0-rc.3 - typescript: ^5.0.0 + typescript: ^5.0.0 || ^6.0.0-beta vue-tsc: ~3.2.0 peerDependenciesMeta: '@ts-macro/tsc': @@ -24481,6 +24798,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rolldown@1.0.0-rc.5: + resolution: {integrity: sha512-0AdalTs6hNTioaCYIkAa7+xsmHBfU5hCNclZnM/lp7lGGDuUOb6N4BVNtwiomybbencDjq/waKjTImqiGCs5sw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup-plugin-copy@3.5.0: resolution: {integrity: sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==} engines: {node: '>=8.3'} @@ -26749,8 +27071,8 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - unrun@0.2.27: - resolution: {integrity: sha512-Mmur1UJpIbfxasLOhPRvox/QS4xBiDii71hMP7smfRthGcwFL2OAmYRgduLANOAU4LUkvVamuP+02U+c90jlrw==} + unrun@0.2.28: + resolution: {integrity: sha512-LqMrI3ZEUMZ2476aCsbUTfy95CHByqez05nju4AQv4XFPkxh5yai7Di1/Qb0FoELHEEPDWhQi23EJeFyrBV0Og==} engines: {node: '>=20.19.0'} hasBin: true peerDependencies: @@ -28086,10 +28408,10 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/generator@8.0.0-rc.1': + '@babel/generator@8.0.0-rc.2': dependencies: - '@babel/parser': 8.0.0-rc.1 - '@babel/types': 8.0.0-rc.1 + '@babel/parser': 8.0.0-rc.2 + '@babel/types': 8.0.0-rc.2 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 '@types/jsesc': 2.5.1 @@ -28115,7 +28437,7 @@ snapshots: '@babel/helper-optimise-call-expression': 7.27.1 '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.28.6 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -28128,7 +28450,7 @@ snapshots: '@babel/helper-optimise-call-expression': 7.27.1 '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.28.6 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -28173,7 +28495,7 @@ snapshots: '@babel/helper-member-expression-to-functions@7.28.5': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.28.6 '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -28232,7 +28554,7 @@ snapshots: '@babel/core': 7.28.6 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color @@ -28241,7 +28563,7 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color @@ -28250,7 +28572,7 @@ snapshots: '@babel/core': 7.28.6 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color @@ -28259,13 +28581,13 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.28.6 '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -28276,14 +28598,14 @@ snapshots: '@babel/helper-validator-identifier@7.28.5': {} - '@babel/helper-validator-identifier@8.0.0-rc.1': {} + '@babel/helper-validator-identifier@8.0.0-rc.2': {} '@babel/helper-validator-option@7.27.1': {} '@babel/helper-wrap-function@7.28.6': dependencies: '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.28.6 '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -28301,9 +28623,9 @@ snapshots: dependencies: '@babel/types': 7.29.0 - '@babel/parser@8.0.0-rc.1': + '@babel/parser@8.0.0-rc.2': dependencies: - '@babel/types': 8.0.0-rc.1 + '@babel/types': 8.0.0-rc.2 '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.6)': dependencies: @@ -28410,11 +28732,22 @@ snapshots: '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.12.9) '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.12.9) + '@babel/plugin-proposal-partial-application@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-proposal-partial-application@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-proposal-pipeline-operator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-pipeline-operator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-proposal-pipeline-operator@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -28619,6 +28952,11 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-pipeline-operator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-pipeline-operator@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -29799,6 +30137,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/register@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + clone-deep: 4.0.1 + find-cache-dir: 2.1.0 + make-dir: 2.1.0 + pirates: 4.0.7 + source-map-support: 0.5.21 + '@babel/register@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -29854,18 +30201,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/traverse@7.29.0(supports-color@5.5.0)': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.0 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - '@babel/types@7.28.6': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -29876,10 +30211,10 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@babel/types@8.0.0-rc.1': + '@babel/types@8.0.0-rc.2': dependencies: '@babel/helper-string-parser': 8.0.0-rc.2 - '@babel/helper-validator-identifier': 8.0.0-rc.1 + '@babel/helper-validator-identifier': 8.0.0-rc.2 '@base2/pretty-print-object@1.0.1': {} @@ -30565,6 +30900,9 @@ snapshots: '@emotion/weak-memoize@0.4.0': {} + '@esbuild/aix-ppc64@0.19.12': + optional: true + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -30580,6 +30918,9 @@ snapshots: '@esbuild/android-arm64@0.18.20': optional: true + '@esbuild/android-arm64@0.19.12': + optional: true + '@esbuild/android-arm64@0.19.2': optional: true @@ -30598,6 +30939,9 @@ snapshots: '@esbuild/android-arm@0.18.20': optional: true + '@esbuild/android-arm@0.19.12': + optional: true + '@esbuild/android-arm@0.19.2': optional: true @@ -30616,6 +30960,9 @@ snapshots: '@esbuild/android-x64@0.18.20': optional: true + '@esbuild/android-x64@0.19.12': + optional: true + '@esbuild/android-x64@0.19.2': optional: true @@ -30634,6 +30981,9 @@ snapshots: '@esbuild/darwin-arm64@0.18.20': optional: true + '@esbuild/darwin-arm64@0.19.12': + optional: true + '@esbuild/darwin-arm64@0.19.2': optional: true @@ -30652,6 +31002,9 @@ snapshots: '@esbuild/darwin-x64@0.18.20': optional: true + '@esbuild/darwin-x64@0.19.12': + optional: true + '@esbuild/darwin-x64@0.19.2': optional: true @@ -30670,6 +31023,9 @@ snapshots: '@esbuild/freebsd-arm64@0.18.20': optional: true + '@esbuild/freebsd-arm64@0.19.12': + optional: true + '@esbuild/freebsd-arm64@0.19.2': optional: true @@ -30688,6 +31044,9 @@ snapshots: '@esbuild/freebsd-x64@0.18.20': optional: true + '@esbuild/freebsd-x64@0.19.12': + optional: true + '@esbuild/freebsd-x64@0.19.2': optional: true @@ -30706,6 +31065,9 @@ snapshots: '@esbuild/linux-arm64@0.18.20': optional: true + '@esbuild/linux-arm64@0.19.12': + optional: true + '@esbuild/linux-arm64@0.19.2': optional: true @@ -30724,6 +31086,9 @@ snapshots: '@esbuild/linux-arm@0.18.20': optional: true + '@esbuild/linux-arm@0.19.12': + optional: true + '@esbuild/linux-arm@0.19.2': optional: true @@ -30742,6 +31107,9 @@ snapshots: '@esbuild/linux-ia32@0.18.20': optional: true + '@esbuild/linux-ia32@0.19.12': + optional: true + '@esbuild/linux-ia32@0.19.2': optional: true @@ -30760,6 +31128,9 @@ snapshots: '@esbuild/linux-loong64@0.18.20': optional: true + '@esbuild/linux-loong64@0.19.12': + optional: true + '@esbuild/linux-loong64@0.19.2': optional: true @@ -30778,6 +31149,9 @@ snapshots: '@esbuild/linux-mips64el@0.18.20': optional: true + '@esbuild/linux-mips64el@0.19.12': + optional: true + '@esbuild/linux-mips64el@0.19.2': optional: true @@ -30796,6 +31170,9 @@ snapshots: '@esbuild/linux-ppc64@0.18.20': optional: true + '@esbuild/linux-ppc64@0.19.12': + optional: true + '@esbuild/linux-ppc64@0.19.2': optional: true @@ -30814,6 +31191,9 @@ snapshots: '@esbuild/linux-riscv64@0.18.20': optional: true + '@esbuild/linux-riscv64@0.19.12': + optional: true + '@esbuild/linux-riscv64@0.19.2': optional: true @@ -30832,6 +31212,9 @@ snapshots: '@esbuild/linux-s390x@0.18.20': optional: true + '@esbuild/linux-s390x@0.19.12': + optional: true + '@esbuild/linux-s390x@0.19.2': optional: true @@ -30850,6 +31233,9 @@ snapshots: '@esbuild/linux-x64@0.18.20': optional: true + '@esbuild/linux-x64@0.19.12': + optional: true + '@esbuild/linux-x64@0.19.2': optional: true @@ -30874,6 +31260,9 @@ snapshots: '@esbuild/netbsd-x64@0.18.20': optional: true + '@esbuild/netbsd-x64@0.19.12': + optional: true + '@esbuild/netbsd-x64@0.19.2': optional: true @@ -30898,6 +31287,9 @@ snapshots: '@esbuild/openbsd-x64@0.18.20': optional: true + '@esbuild/openbsd-x64@0.19.12': + optional: true + '@esbuild/openbsd-x64@0.19.2': optional: true @@ -30916,6 +31308,9 @@ snapshots: '@esbuild/sunos-x64@0.18.20': optional: true + '@esbuild/sunos-x64@0.19.12': + optional: true + '@esbuild/sunos-x64@0.19.2': optional: true @@ -30934,6 +31329,9 @@ snapshots: '@esbuild/win32-arm64@0.18.20': optional: true + '@esbuild/win32-arm64@0.19.12': + optional: true + '@esbuild/win32-arm64@0.19.2': optional: true @@ -30952,6 +31350,9 @@ snapshots: '@esbuild/win32-ia32@0.18.20': optional: true + '@esbuild/win32-ia32@0.19.12': + optional: true + '@esbuild/win32-ia32@0.19.2': optional: true @@ -30970,6 +31371,9 @@ snapshots: '@esbuild/win32-x64@0.18.20': optional: true + '@esbuild/win32-x64@0.19.12': + optional: true + '@esbuild/win32-x64@0.19.2': optional: true @@ -31418,76 +31822,6 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4))': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.5 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2))': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.5 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - '@jest/create-cache-key-function@29.7.0': dependencies: '@jest/types': 29.6.3 @@ -31583,7 +31917,7 @@ snapshots: '@jest/transform@29.7.0': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.31 babel-plugin-istanbul: 6.1.1 @@ -32489,16 +32823,16 @@ snapshots: '@modern-js/babel-preset@2.59.0(@rsbuild/core@1.0.1-rc.4)': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-proposal-decorators': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-proposal-partial-application': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-proposal-pipeline-operator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.29.0) - '@babel/preset-env': 7.28.6(@babel/core@7.29.0) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.28.6 + '@babel/plugin-proposal-decorators': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-proposal-partial-application': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-proposal-pipeline-operator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.28.6) + '@babel/preset-env': 7.28.6(@babel/core@7.28.6) + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.6) '@babel/runtime': 7.28.2 - '@babel/types': 7.29.0 + '@babel/types': 7.28.6 '@rsbuild/plugin-babel': 1.0.1-rc.4(@rsbuild/core@1.0.1-rc.4) '@swc/helpers': 0.5.3 '@types/babel__core': 7.20.5 @@ -32845,7 +33179,7 @@ snapshots: '@modern-js/plugin-data-loader@2.70.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 '@modern-js/runtime-utils': 2.70.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@modern-js/utils': 2.70.2 '@swc/helpers': 0.5.18 @@ -32857,7 +33191,7 @@ snapshots: '@modern-js/plugin-data-loader@2.70.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 '@modern-js/runtime-utils': 2.70.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@modern-js/utils': 2.70.5 '@swc/helpers': 0.5.18 @@ -33404,17 +33738,17 @@ snapshots: '@modern-js/server-utils@2.70.2(@babel/traverse@7.28.6)(@rsbuild/core@1.7.2)': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-proposal-decorators': 7.28.6(@babel/core@7.29.0) - '@babel/preset-env': 7.28.6(@babel/core@7.29.0) - '@babel/preset-react': 7.28.5(@babel/core@7.29.0) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.28.6 + '@babel/plugin-proposal-decorators': 7.28.6(@babel/core@7.28.6) + '@babel/preset-env': 7.28.6(@babel/core@7.28.6) + '@babel/preset-react': 7.28.5(@babel/core@7.28.6) + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.6) '@modern-js/babel-compiler': 2.70.2 '@modern-js/babel-plugin-module-resolver': 2.70.2 '@modern-js/babel-preset': 2.70.2(@rsbuild/core@1.7.2) '@modern-js/utils': 2.70.2 '@swc/helpers': 0.5.18 - babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.29.0)(@babel/traverse@7.28.6) + babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.28.6)(@babel/traverse@7.28.6) transitivePeerDependencies: - '@babel/traverse' - '@rsbuild/core' @@ -33422,17 +33756,17 @@ snapshots: '@modern-js/server-utils@2.70.5(@babel/traverse@7.28.6)(@rsbuild/core@1.7.3)': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-proposal-decorators': 7.28.6(@babel/core@7.29.0) - '@babel/preset-env': 7.28.6(@babel/core@7.29.0) - '@babel/preset-react': 7.28.5(@babel/core@7.29.0) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.28.6 + '@babel/plugin-proposal-decorators': 7.28.6(@babel/core@7.28.6) + '@babel/preset-env': 7.28.6(@babel/core@7.28.6) + '@babel/preset-react': 7.28.5(@babel/core@7.28.6) + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.6) '@modern-js/babel-compiler': 2.70.5 '@modern-js/babel-plugin-module-resolver': 2.70.5 '@modern-js/babel-preset': 2.70.5(@rsbuild/core@1.7.3) '@modern-js/utils': 2.70.5 '@swc/helpers': 0.5.18 - babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.29.0)(@babel/traverse@7.28.6) + babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.28.6)(@babel/traverse@7.28.6) transitivePeerDependencies: - '@babel/traverse' - '@rsbuild/core' @@ -33456,8 +33790,8 @@ snapshots: '@modern-js/server@2.70.2(@babel/traverse@7.28.6)(@rsbuild/core@1.7.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2))(tsconfig-paths@4.2.0)': dependencies: - '@babel/core': 7.29.0 - '@babel/register': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.28.6 + '@babel/register': 7.28.6(@babel/core@7.28.6) '@modern-js/runtime-utils': 2.70.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@modern-js/server-core': 2.70.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@modern-js/server-utils': 2.70.2(@babel/traverse@7.28.6)(@rsbuild/core@1.7.2) @@ -33485,8 +33819,8 @@ snapshots: '@modern-js/server@2.70.5(@babel/traverse@7.28.6)(@rsbuild/core@1.7.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2))(tsconfig-paths@4.2.0)': dependencies: - '@babel/core': 7.29.0 - '@babel/register': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.28.6 + '@babel/register': 7.28.6(@babel/core@7.28.6) '@modern-js/runtime-utils': 2.70.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@modern-js/server-core': 2.70.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@modern-js/server-utils': 2.70.5(@babel/traverse@7.28.6)(@rsbuild/core@1.7.3) @@ -33732,8 +34066,8 @@ snapshots: '@modern-js/uni-builder@2.70.2(@rspack/core@1.7.5(@swc/helpers@0.5.18))(esbuild@0.18.20)(styled-components@6.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(tslib@2.8.1)(type-fest@4.41.0)(typescript@5.8.2)(webpack-cli@5.1.4)(webpack-dev-server@5.2.3(webpack-cli@5.1.4)(webpack@5.104.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.18.20)(webpack-cli@5.1.4)))(webpack-hot-middleware@2.26.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/preset-react': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.28.6 + '@babel/preset-react': 7.28.5(@babel/core@7.28.6) '@babel/types': 7.29.0 '@modern-js/babel-preset': 2.70.2(@rsbuild/core@1.7.2) '@modern-js/flight-server-transform-plugin': 2.70.2 @@ -33760,7 +34094,7 @@ snapshots: '@swc/core': 1.15.8(@swc/helpers@0.5.18) '@swc/helpers': 0.5.18 autoprefixer: 10.4.23(postcss@8.5.6) - babel-loader: 9.2.1(@babel/core@7.29.0)(webpack@5.104.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.18.20)(webpack-cli@5.1.4)) + babel-loader: 9.2.1(@babel/core@7.28.6)(webpack@5.104.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.18.20)(webpack-cli@5.1.4)) babel-plugin-import: 1.13.8 babel-plugin-styled-components: 1.13.3(styled-components@6.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) babel-plugin-transform-react-remove-prop-types: 0.4.24 @@ -33812,8 +34146,8 @@ snapshots: '@modern-js/uni-builder@2.70.2(@rspack/core@1.7.5(@swc/helpers@0.5.18))(esbuild@0.25.5)(styled-components@6.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(tslib@2.8.1)(type-fest@4.41.0)(typescript@5.8.2)(webpack-cli@5.1.4)(webpack-dev-server@5.2.3(webpack-cli@5.1.4)(webpack@5.104.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.18.20)(webpack-cli@5.1.4)))(webpack-hot-middleware@2.26.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/preset-react': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.28.6 + '@babel/preset-react': 7.28.5(@babel/core@7.28.6) '@babel/types': 7.29.0 '@modern-js/babel-preset': 2.70.2(@rsbuild/core@1.7.2) '@modern-js/flight-server-transform-plugin': 2.70.2 @@ -33840,7 +34174,7 @@ snapshots: '@swc/core': 1.15.8(@swc/helpers@0.5.18) '@swc/helpers': 0.5.18 autoprefixer: 10.4.23(postcss@8.5.6) - babel-loader: 9.2.1(@babel/core@7.29.0)(webpack@5.104.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.18.20)(webpack-cli@5.1.4)) + babel-loader: 9.2.1(@babel/core@7.28.6)(webpack@5.104.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.18.20)(webpack-cli@5.1.4)) babel-plugin-import: 1.13.8 babel-plugin-styled-components: 1.13.3(styled-components@6.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) babel-plugin-transform-react-remove-prop-types: 0.4.24 @@ -33892,8 +34226,8 @@ snapshots: '@modern-js/uni-builder@2.70.5(@rspack/core@1.7.5(@swc/helpers@0.5.18))(esbuild@0.25.5)(styled-components@6.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(tslib@2.8.1)(type-fest@4.41.0)(typescript@5.8.2)(webpack-cli@5.1.4)(webpack-dev-server@5.2.3(webpack-cli@5.1.4)(webpack@5.104.1(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.25.5)(webpack-cli@5.1.4)))(webpack-hot-middleware@2.26.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/preset-react': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.28.6 + '@babel/preset-react': 7.28.5(@babel/core@7.28.6) '@babel/types': 7.29.0 '@modern-js/babel-preset': 2.70.5(@rsbuild/core@1.7.3) '@modern-js/flight-server-transform-plugin': 2.70.5 @@ -33920,7 +34254,7 @@ snapshots: '@swc/core': 1.15.8(@swc/helpers@0.5.18) '@swc/helpers': 0.5.18 autoprefixer: 10.4.23(postcss@8.5.6) - babel-loader: 9.2.1(@babel/core@7.29.0)(webpack@5.104.1(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.25.5)(webpack-cli@5.1.4)) + babel-loader: 9.2.1(@babel/core@7.28.6)(webpack@5.104.1(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.25.5)(webpack-cli@5.1.4)) babel-plugin-import: 1.13.8 babel-plugin-styled-components: 1.13.3(styled-components@6.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) babel-plugin-transform-react-remove-prop-types: 0.4.24 @@ -35815,6 +36149,8 @@ snapshots: '@oxc-project/types@0.112.0': {} + '@oxc-project/types@0.114.0': {} + '@oxc-resolver/binding-darwin-arm64@5.3.0': optional: true @@ -36132,8 +36468,8 @@ snapshots: '@preconstruct/hook@0.4.0': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.28.6 + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.6) pirates: 4.0.7 source-map-support: 0.5.21 transitivePeerDependencies: @@ -38279,7 +38615,7 @@ snapshots: - supports-color - utf-8-validate - '@react-native/eslint-config@0.80.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)))(prettier@2.8.8)(typescript@5.0.4)': + '@react-native/eslint-config@0.80.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)))(prettier@2.8.8)(typescript@5.0.4)': dependencies: '@babel/core': 7.29.0 '@babel/eslint-parser': 7.28.6(@babel/core@7.28.6)(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1)) @@ -38290,7 +38626,7 @@ snapshots: eslint-config-prettier: 8.10.2(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1)) eslint-plugin-eslint-comments: 3.2.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1)) eslint-plugin-ft-flow: 2.0.3(@babel/eslint-parser@7.28.6(@babel/core@7.29.0)(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1)))(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1)) - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(typescript@5.0.4))(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(typescript@5.0.4))(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)))(typescript@5.0.4) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(typescript@5.0.4))(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(typescript@5.0.4))(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)))(typescript@5.0.4) eslint-plugin-react: 7.37.2(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1)) eslint-plugin-react-native: 4.1.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1)) @@ -38540,50 +38876,93 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0-rc.3': optional: true + '@rolldown/binding-android-arm64@1.0.0-rc.5': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.3': optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.5': + optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.3': optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.5': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.3': optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.5': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.5': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.5': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.5': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.5': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.5': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.3': optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.5': + optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.3': dependencies: '@napi-rs/wasm-runtime': 1.1.1 optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.5': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3': optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.5': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.3': optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.5': + optional: true + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rolldown/pluginutils@1.0.0-rc.2': {} '@rolldown/pluginutils@1.0.0-rc.3': {} + '@rolldown/pluginutils@1.0.0-rc.5': {} + '@rollup/plugin-alias@3.1.9(rollup@2.79.2)': dependencies: rollup: 2.79.2 @@ -38847,6 +39226,17 @@ snapshots: core-js: 3.47.0 jiti: 2.6.1 + '@rsbuild/core@2.0.0-beta.0(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0)': + dependencies: + '@rspack/core': 2.0.0-alpha.1(@module-federation/runtime-tools@2.0.1)(@swc/helpers@0.5.18) + '@rspack/lite-tapable': 1.1.0 + '@swc/helpers': 0.5.18 + jiti: 2.6.1 + optionalDependencies: + core-js: 3.48.0 + transitivePeerDependencies: + - '@module-federation/runtime-tools' + '@rsbuild/core@2.0.0-beta.2(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0)': dependencies: '@rspack/core': 2.0.0-beta.0(@module-federation/runtime-tools@2.0.1)(@swc/helpers@0.5.18) @@ -39182,6 +39572,14 @@ snapshots: transitivePeerDependencies: - webpack-hot-middleware + '@rsbuild/plugin-react@1.4.5(@rsbuild/core@2.0.0-beta.0(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0))(webpack-hot-middleware@2.26.1)': + dependencies: + '@rsbuild/core': 2.0.0-beta.0(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0) + '@rspack/plugin-react-refresh': 1.6.0(react-refresh@0.18.0)(webpack-hot-middleware@2.26.1) + react-refresh: 0.18.0 + transitivePeerDependencies: + - webpack-hot-middleware + '@rsbuild/plugin-react@1.4.5(@rsbuild/core@2.0.0-beta.2(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0))(webpack-hot-middleware@2.26.1)': dependencies: '@rsbuild/core': 2.0.0-beta.2(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0) @@ -39625,6 +40023,9 @@ snapshots: '@rspack/binding-darwin-arm64@1.7.5': optional: true + '@rspack/binding-darwin-arm64@2.0.0-alpha.1': + optional: true + '@rspack/binding-darwin-arm64@2.0.0-beta.0': optional: true @@ -39655,6 +40056,9 @@ snapshots: '@rspack/binding-darwin-x64@1.7.5': optional: true + '@rspack/binding-darwin-x64@2.0.0-alpha.1': + optional: true + '@rspack/binding-darwin-x64@2.0.0-beta.0': optional: true @@ -39685,6 +40089,9 @@ snapshots: '@rspack/binding-linux-arm64-gnu@1.7.5': optional: true + '@rspack/binding-linux-arm64-gnu@2.0.0-alpha.1': + optional: true + '@rspack/binding-linux-arm64-gnu@2.0.0-beta.0': optional: true @@ -39715,6 +40122,9 @@ snapshots: '@rspack/binding-linux-arm64-musl@1.7.5': optional: true + '@rspack/binding-linux-arm64-musl@2.0.0-alpha.1': + optional: true + '@rspack/binding-linux-arm64-musl@2.0.0-beta.0': optional: true @@ -39745,6 +40155,9 @@ snapshots: '@rspack/binding-linux-x64-gnu@1.7.5': optional: true + '@rspack/binding-linux-x64-gnu@2.0.0-alpha.1': + optional: true + '@rspack/binding-linux-x64-gnu@2.0.0-beta.0': optional: true @@ -39775,6 +40188,9 @@ snapshots: '@rspack/binding-linux-x64-musl@1.7.5': optional: true + '@rspack/binding-linux-x64-musl@2.0.0-alpha.1': + optional: true + '@rspack/binding-linux-x64-musl@2.0.0-beta.0': optional: true @@ -39798,6 +40214,11 @@ snapshots: '@napi-rs/wasm-runtime': 1.0.7 optional: true + '@rspack/binding-wasm32-wasi@2.0.0-alpha.1': + dependencies: + '@napi-rs/wasm-runtime': 1.0.7 + optional: true + '@rspack/binding-wasm32-wasi@2.0.0-beta.0': dependencies: '@napi-rs/wasm-runtime': 1.0.7 @@ -39830,6 +40251,9 @@ snapshots: '@rspack/binding-win32-arm64-msvc@1.7.5': optional: true + '@rspack/binding-win32-arm64-msvc@2.0.0-alpha.1': + optional: true + '@rspack/binding-win32-arm64-msvc@2.0.0-beta.0': optional: true @@ -39860,6 +40284,9 @@ snapshots: '@rspack/binding-win32-ia32-msvc@1.7.5': optional: true + '@rspack/binding-win32-ia32-msvc@2.0.0-alpha.1': + optional: true + '@rspack/binding-win32-ia32-msvc@2.0.0-beta.0': optional: true @@ -39890,6 +40317,9 @@ snapshots: '@rspack/binding-win32-x64-msvc@1.7.5': optional: true + '@rspack/binding-win32-x64-msvc@2.0.0-alpha.1': + optional: true + '@rspack/binding-win32-x64-msvc@2.0.0-beta.0': optional: true @@ -40005,6 +40435,19 @@ snapshots: '@rspack/binding-win32-ia32-msvc': 1.7.5 '@rspack/binding-win32-x64-msvc': 1.7.5 + '@rspack/binding@2.0.0-alpha.1': + optionalDependencies: + '@rspack/binding-darwin-arm64': 2.0.0-alpha.1 + '@rspack/binding-darwin-x64': 2.0.0-alpha.1 + '@rspack/binding-linux-arm64-gnu': 2.0.0-alpha.1 + '@rspack/binding-linux-arm64-musl': 2.0.0-alpha.1 + '@rspack/binding-linux-x64-gnu': 2.0.0-alpha.1 + '@rspack/binding-linux-x64-musl': 2.0.0-alpha.1 + '@rspack/binding-wasm32-wasi': 2.0.0-alpha.1 + '@rspack/binding-win32-arm64-msvc': 2.0.0-alpha.1 + '@rspack/binding-win32-ia32-msvc': 2.0.0-alpha.1 + '@rspack/binding-win32-x64-msvc': 2.0.0-alpha.1 + '@rspack/binding@2.0.0-beta.0': optionalDependencies: '@rspack/binding-darwin-arm64': 2.0.0-beta.0 @@ -40124,6 +40567,14 @@ snapshots: optionalDependencies: '@swc/helpers': 0.5.18 + '@rspack/core@2.0.0-alpha.1(@module-federation/runtime-tools@2.0.1)(@swc/helpers@0.5.18)': + dependencies: + '@rspack/binding': 2.0.0-alpha.1 + '@rspack/lite-tapable': 1.1.0 + optionalDependencies: + '@module-federation/runtime-tools': 2.0.1 + '@swc/helpers': 0.5.18 + '@rspack/core@2.0.0-beta.0(@module-federation/runtime-tools@2.0.1)(@swc/helpers@0.5.18)': dependencies: '@rspack/binding': 2.0.0-beta.0 @@ -40176,13 +40627,13 @@ snapshots: optionalDependencies: webpack-hot-middleware: 2.26.1 - '@rspress/core@2.0.3(@module-federation/runtime-tools@2.0.1)(@types/react@18.3.11)(core-js@3.48.0)(webpack-hot-middleware@2.26.1)': + '@rspress/core@2.0.1(@module-federation/runtime-tools@2.0.1)(@types/react@18.3.11)(core-js@3.48.0)(webpack-hot-middleware@2.26.1)': dependencies: '@mdx-js/mdx': 3.1.1 '@mdx-js/react': 3.1.1(@types/react@18.3.11)(react@19.2.4) - '@rsbuild/core': 2.0.0-beta.3(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0) - '@rsbuild/plugin-react': 1.4.5(@rsbuild/core@2.0.0-beta.3(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0))(webpack-hot-middleware@2.26.1) - '@rspress/shared': 2.0.3(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0) + '@rsbuild/core': 2.0.0-beta.0(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0) + '@rsbuild/plugin-react': 1.4.5(@rsbuild/core@2.0.0-beta.0(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0))(webpack-hot-middleware@2.26.1) + '@rspress/shared': 2.0.1(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0) '@shikijs/rehype': 3.21.0 '@types/unist': 3.0.3 '@unhead/react': 2.1.4(react@19.2.4) @@ -40289,9 +40740,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@rspress/shared@2.0.3(@module-federation/runtime-tools@2.0.1)(core-js@3.36.1)': + '@rspress/shared@2.0.1(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0)': dependencies: - '@rsbuild/core': 2.0.0-beta.3(@module-federation/runtime-tools@2.0.1)(core-js@3.36.1) + '@rsbuild/core': 2.0.0-beta.0(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0) '@shikijs/rehype': 3.21.0 gray-matter: 4.0.3 lodash-es: 4.17.23 @@ -40300,9 +40751,9 @@ snapshots: - '@module-federation/runtime-tools' - core-js - '@rspress/shared@2.0.3(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0)': + '@rspress/shared@2.0.3(@module-federation/runtime-tools@2.0.1)(core-js@3.36.1)': dependencies: - '@rsbuild/core': 2.0.0-beta.3(@module-federation/runtime-tools@2.0.1)(core-js@3.48.0) + '@rsbuild/core': 2.0.0-beta.3(@module-federation/runtime-tools@2.0.1)(core-js@3.36.1) '@shikijs/rehype': 3.21.0 gray-matter: 4.0.3 lodash-es: 4.17.23 @@ -41055,7 +41506,7 @@ snapshots: dependencies: '@babel/generator': 7.29.1 '@babel/parser': 7.29.0 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.28.6 '@babel/types': 7.29.0 '@storybook/csf': 0.1.13 '@storybook/types': 7.6.21 @@ -41426,54 +41877,54 @@ snapshots: '@types/express': 4.17.25 file-system-cache: 2.3.0 - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.0)': + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.0)': + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.0)': + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.0)': + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.0)': + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.0)': + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.0)': + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.0)': + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 - '@svgr/babel-preset@8.1.0(@babel/core@7.29.0)': + '@svgr/babel-preset@8.1.0(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.29.0 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.0) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.0) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.0) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.0) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.0) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.0) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.0) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.0) + '@babel/core': 7.28.6 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.28.6) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.28.6) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.28.6) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.28.6) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.28.6) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.28.6) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.28.6) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.28.6) '@svgr/core@8.1.0(typescript@5.0.4)': dependencies: - '@babel/core': 7.29.0 - '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) + '@babel/core': 7.28.6 + '@svgr/babel-preset': 8.1.0(@babel/core@7.28.6) camelcase: 6.3.0 cosmiconfig: 8.3.6(typescript@5.0.4) snake-case: 3.0.4 @@ -41483,8 +41934,8 @@ snapshots: '@svgr/core@8.1.0(typescript@5.8.2)': dependencies: - '@babel/core': 7.29.0 - '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) + '@babel/core': 7.28.6 + '@svgr/babel-preset': 8.1.0(@babel/core@7.28.6) camelcase: 6.3.0 cosmiconfig: 8.3.6(typescript@5.8.2) snake-case: 3.0.4 @@ -41499,8 +41950,8 @@ snapshots: '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.8.2))': dependencies: - '@babel/core': 7.29.0 - '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) + '@babel/core': 7.28.6 + '@svgr/babel-preset': 8.1.0(@babel/core@7.28.6) '@svgr/core': 8.1.0(typescript@5.8.2) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 @@ -41816,8 +42267,8 @@ snapshots: '@testing-library/dom@10.4.1': dependencies: - '@babel/code-frame': 7.28.6 - '@babel/runtime': 7.28.2 + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.28.6 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -41917,8 +42368,8 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 @@ -43046,7 +43497,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.54.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 9.0.5 - semver: 7.7.3 + semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@5.4.5) typescript: 5.4.5 @@ -43061,7 +43512,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.54.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 9.0.5 - semver: 7.7.3 + semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@5.6.3) typescript: 5.6.3 @@ -43076,7 +43527,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.54.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 9.0.5 - semver: 7.7.3 + semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@5.8.2) typescript: 5.8.2 @@ -43091,7 +43542,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.54.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 9.0.5 - semver: 7.7.3 + semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 @@ -43847,7 +44298,7 @@ snapshots: '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) '@babel/template': 7.28.6 '@babel/traverse': 7.28.6 - '@babel/types': 7.29.0 + '@babel/types': 7.28.6 '@vue/babel-helper-vue-transform-on': 1.5.0 '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.28.6) '@vue/shared': 3.5.27 @@ -44984,7 +45435,7 @@ snapshots: ast-kit@3.0.0-beta.1: dependencies: - '@babel/parser': 8.0.0-rc.1 + '@babel/parser': 8.0.0-rc.2 estree-walker: 3.0.3 pathe: 2.0.3 @@ -45146,40 +45597,40 @@ snapshots: transitivePeerDependencies: - supports-color - babel-loader@9.2.1(@babel/core@7.28.6)(webpack@5.104.1): + babel-loader@9.2.1(@babel/core@7.28.6)(webpack@5.104.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.18.20)(webpack-cli@5.1.4)): dependencies: '@babel/core': 7.28.6 find-cache-dir: 4.0.0 schema-utils: 4.3.3 - webpack: 5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.25.0)(webpack-cli@5.1.4) + webpack: 5.104.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.18.20)(webpack-cli@5.1.4) - babel-loader@9.2.1(@babel/core@7.28.6)(webpack@5.99.9(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.0)(webpack-cli@5.1.4)): + babel-loader@9.2.1(@babel/core@7.28.6)(webpack@5.104.1(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.25.5)(webpack-cli@5.1.4)): dependencies: '@babel/core': 7.28.6 find-cache-dir: 4.0.0 schema-utils: 4.3.3 - webpack: 5.99.9(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.0)(webpack-cli@5.1.4) + webpack: 5.104.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.5)(webpack-cli@5.1.4) - babel-loader@9.2.1(@babel/core@7.28.6)(webpack@5.99.9(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.25.0)(webpack-cli@5.1.4)): + babel-loader@9.2.1(@babel/core@7.28.6)(webpack@5.104.1): dependencies: '@babel/core': 7.28.6 find-cache-dir: 4.0.0 schema-utils: 4.3.3 - webpack: 5.99.9(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.25.0)(webpack-cli@5.1.4) + webpack: 5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.25.0)(webpack-cli@5.1.4) - babel-loader@9.2.1(@babel/core@7.29.0)(webpack@5.104.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.18.20)(webpack-cli@5.1.4)): + babel-loader@9.2.1(@babel/core@7.28.6)(webpack@5.99.9(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.0)(webpack-cli@5.1.4)): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 find-cache-dir: 4.0.0 schema-utils: 4.3.3 - webpack: 5.104.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.99.9(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.0)(webpack-cli@5.1.4) - babel-loader@9.2.1(@babel/core@7.29.0)(webpack@5.104.1(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.25.5)(webpack-cli@5.1.4)): + babel-loader@9.2.1(@babel/core@7.28.6)(webpack@5.99.9(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.25.0)(webpack-cli@5.1.4)): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 find-cache-dir: 4.0.0 schema-utils: 4.3.3 - webpack: 5.104.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.5)(webpack-cli@5.1.4) + webpack: 5.99.9(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.25.0)(webpack-cli@5.1.4) babel-plugin-apply-mdx-type-prop@1.6.22(@babel/core@7.12.9): dependencies: @@ -45192,7 +45643,7 @@ snapshots: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6) - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color @@ -45325,19 +45776,19 @@ snapshots: babel-plugin-transform-react-remove-prop-types@0.4.24: {} - babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.28.6)(@babel/traverse@7.29.0): + babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.28.6)(@babel/traverse@7.28.6): dependencies: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 optionalDependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.28.6 - babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.29.0)(@babel/traverse@7.28.6): + babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.28.6)(@babel/traverse@7.29.0): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 optionalDependencies: - '@babel/traverse': 7.28.6 + '@babel/traverse': 7.29.0 babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.29.0)(@babel/traverse@7.29.0): dependencies: @@ -45692,9 +46143,9 @@ snapshots: dependencies: run-applescript: 7.1.0 - bundle-require@4.2.1(esbuild@0.19.2): + bundle-require@4.2.1(esbuild@0.19.12): dependencies: - esbuild: 0.19.2 + esbuild: 0.19.12 load-tsconfig: 0.2.5 busboy@1.6.0: @@ -46660,36 +47111,6 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - create-jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - create-require@1.1.1: {} cron-parser@4.9.0: @@ -48098,6 +48519,32 @@ snapshots: '@esbuild/win32-ia32': 0.18.20 '@esbuild/win32-x64': 0.18.20 + esbuild@0.19.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + esbuild@0.19.2: optionalDependencies: '@esbuild/android-arm': 0.19.2 @@ -48270,7 +48717,7 @@ snapshots: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3(supports-color@8.1.1) eslint: 9.26.0(hono@4.11.10)(jiti@2.6.1) - get-tsconfig: 4.13.4 + get-tsconfig: 4.13.0 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.15 @@ -48475,13 +48922,13 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(typescript@5.0.4))(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(typescript@5.0.4))(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)))(typescript@5.0.4): + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(typescript@5.0.4))(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(typescript@5.0.4))(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)))(typescript@5.0.4): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(typescript@5.0.4) eslint: 9.26.0(hono@4.11.10)(jiti@2.6.1) optionalDependencies: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(typescript@5.0.4))(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1))(typescript@5.0.4) - jest: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)) + jest: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)) transitivePeerDependencies: - supports-color - typescript @@ -48576,7 +49023,7 @@ snapshots: eslint-plugin-react-hooks@7.0.1(eslint@9.26.0(hono@4.11.10)(jiti@2.6.1)): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 '@babel/parser': 7.29.0 eslint: 9.26.0(hono@4.11.10)(jiti@2.6.1) hermes-parser: 0.25.1 @@ -49900,7 +50347,11 @@ snapshots: get-them-args@1.3.2: {} - get-tsconfig@4.13.4: + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + get-tsconfig@4.13.6: dependencies: resolve-pkg-maps: 1.0.0 @@ -51400,7 +51851,7 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 '@babel/parser': 7.29.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 @@ -51513,50 +51964,12 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)): - dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)) - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)) - exit: 0.1.2 - import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest-cli@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)): - dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)) - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)) - exit: 0.1.2 - import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - jest-config@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.0) + babel-jest: 29.7.0(@babel/core@7.28.6) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 @@ -51582,68 +51995,6 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)): - dependencies: - '@babel/core': 7.29.0 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.0) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.0 - graceful-fs: 4.2.11 - jest-circus: 29.7.0(babel-plugin-macros@3.1.0) - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 20.19.5 - ts-node: 10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)): - dependencies: - '@babel/core': 7.29.0 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.0) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.0 - graceful-fs: 4.2.11 - jest-circus: 29.7.0(babel-plugin-macros@3.1.0) - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 20.19.5 - ts-node: 10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - jest-diff@29.7.0: dependencies: chalk: 4.1.2 @@ -51902,30 +52253,6 @@ snapshots: - supports-color - ts-node - jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)): - dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)) - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.0.4)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)): - dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)) - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - jiti@1.21.7: {} jiti@2.4.2: {} @@ -52980,7 +53307,7 @@ snapshots: metro-babel-transformer@0.82.5: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 flow-enums-runtime: 0.0.6 hermes-parser: 0.29.1 nullthrows: 1.1.1 @@ -53077,10 +53404,10 @@ snapshots: metro-transform-plugins@0.82.5: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 '@babel/generator': 7.29.1 '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.28.6 flow-enums-runtime: 0.0.6 nullthrows: 1.1.1 transitivePeerDependencies: @@ -53088,7 +53415,7 @@ snapshots: metro-transform-worker@0.82.5: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 '@babel/generator': 7.29.1 '@babel/parser': 7.29.0 '@babel/types': 7.29.0 @@ -53526,7 +53853,7 @@ snapshots: minimatch@10.0.3: dependencies: - '@isaacs/brace-expansion': 5.0.0 + '@isaacs/brace-expansion': 5.0.1 minimatch@10.1.1: dependencies: @@ -57480,7 +57807,7 @@ snapshots: react-docgen@7.1.1: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.28.6 '@babel/traverse': 7.28.6 '@babel/types': 7.29.0 '@types/babel__core': 7.20.5 @@ -57857,12 +58184,6 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-router: 7.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-router-dom@7.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): - dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-router: 7.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react-router-dom@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: react: 19.2.4 @@ -57954,14 +58275,6 @@ snapshots: optionalDependencies: react-dom: 18.3.1(react@18.3.1) - react-router@7.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): - dependencies: - cookie: 1.1.1 - react: 19.1.1 - set-cookie-parser: 2.7.2 - optionalDependencies: - react-dom: 19.1.1(react@19.1.1) - react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: cookie: 1.1.1 @@ -58615,16 +58928,16 @@ snapshots: hash-base: 3.1.2 inherits: 2.0.4 - rolldown-plugin-dts@0.22.1(rolldown@1.0.0-rc.3)(typescript@5.8.2)(vue-tsc@2.2.12(typescript@5.8.2)): + rolldown-plugin-dts@0.22.3(rolldown@1.0.0-rc.3)(typescript@5.8.2)(vue-tsc@2.2.12(typescript@5.8.2)): dependencies: - '@babel/generator': 8.0.0-rc.1 - '@babel/helper-validator-identifier': 8.0.0-rc.1 - '@babel/parser': 8.0.0-rc.1 - '@babel/types': 8.0.0-rc.1 + '@babel/generator': 8.0.0-rc.2 + '@babel/helper-validator-identifier': 8.0.0-rc.2 + '@babel/parser': 8.0.0-rc.2 + '@babel/types': 8.0.0-rc.2 ast-kit: 3.0.0-beta.1 birpc: 4.0.0 dts-resolver: 2.1.3 - get-tsconfig: 4.13.4 + get-tsconfig: 4.13.6 obug: 2.1.1 rolldown: 1.0.0-rc.3 optionalDependencies: @@ -58633,16 +58946,16 @@ snapshots: transitivePeerDependencies: - oxc-resolver - rolldown-plugin-dts@0.22.1(rolldown@1.0.0-rc.3)(typescript@5.9.3)(vue-tsc@2.2.12(typescript@5.8.2)): + rolldown-plugin-dts@0.22.3(rolldown@1.0.0-rc.3)(typescript@5.9.3)(vue-tsc@2.2.12(typescript@5.8.2)): dependencies: - '@babel/generator': 8.0.0-rc.1 - '@babel/helper-validator-identifier': 8.0.0-rc.1 - '@babel/parser': 8.0.0-rc.1 - '@babel/types': 8.0.0-rc.1 + '@babel/generator': 8.0.0-rc.2 + '@babel/helper-validator-identifier': 8.0.0-rc.2 + '@babel/parser': 8.0.0-rc.2 + '@babel/types': 8.0.0-rc.2 ast-kit: 3.0.0-beta.1 birpc: 4.0.0 dts-resolver: 2.1.3 - get-tsconfig: 4.13.4 + get-tsconfig: 4.13.6 obug: 2.1.1 rolldown: 1.0.0-rc.3 optionalDependencies: @@ -58670,6 +58983,25 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.3 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.3 + rolldown@1.0.0-rc.5: + dependencies: + '@oxc-project/types': 0.114.0 + '@rolldown/pluginutils': 1.0.0-rc.5 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.5 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.5 + '@rolldown/binding-darwin-x64': 1.0.0-rc.5 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.5 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.5 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.5 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.5 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.5 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.5 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.5 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.5 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.5 + rollup-plugin-copy@3.5.0: dependencies: '@types/fs-extra': 8.1.5 @@ -59410,7 +59742,7 @@ snapshots: dependencies: '@img/colour': 1.0.0 detect-libc: 2.1.2 - semver: 7.7.3 + semver: 7.7.4 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.5 '@img/sharp-darwin-x64': 0.34.5 @@ -60135,7 +60467,7 @@ snapshots: styled-components@5.3.11(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react-is@18.3.1)(react@19.2.4): dependencies: '@babel/helper-module-imports': 7.28.6(supports-color@5.5.0) - '@babel/traverse': 7.29.0(supports-color@5.5.0) + '@babel/traverse': 7.28.6(supports-color@5.5.0) '@emotion/is-prop-valid': 1.4.0 '@emotion/stylis': 0.8.5 '@emotion/unitless': 0.7.5 @@ -61060,11 +61392,11 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.0.1(@babel/core@7.29.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(esbuild@0.25.0)(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)))(typescript@5.8.2): + ts-jest@29.0.1(@babel/core@7.29.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(esbuild@0.25.0)(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)))(typescript@5.8.2): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.5)(typescript@5.8.2)) + jest: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -61362,13 +61694,13 @@ snapshots: obug: 2.1.1 picomatch: 4.0.3 rolldown: 1.0.0-rc.3 - rolldown-plugin-dts: 0.22.1(rolldown@1.0.0-rc.3)(typescript@5.8.2)(vue-tsc@2.2.12(typescript@5.8.2)) - semver: 7.7.3 + rolldown-plugin-dts: 0.22.3(rolldown@1.0.0-rc.3)(typescript@5.8.2)(vue-tsc@2.2.12(typescript@5.8.2)) + semver: 7.7.4 tinyexec: 1.0.2 tinyglobby: 0.2.15 tree-kill: 1.2.2 unconfig-core: 7.5.0 - unrun: 0.2.27 + unrun: 0.2.28 optionalDependencies: publint: 0.3.17 typescript: 5.8.2 @@ -61390,13 +61722,13 @@ snapshots: obug: 2.1.1 picomatch: 4.0.3 rolldown: 1.0.0-rc.3 - rolldown-plugin-dts: 0.22.1(rolldown@1.0.0-rc.3)(typescript@5.9.3)(vue-tsc@2.2.12(typescript@5.8.2)) - semver: 7.7.3 + rolldown-plugin-dts: 0.22.3(rolldown@1.0.0-rc.3)(typescript@5.9.3)(vue-tsc@2.2.12(typescript@5.8.2)) + semver: 7.7.4 tinyexec: 1.0.2 tinyglobby: 0.2.15 tree-kill: 1.2.2 unconfig-core: 7.5.0 - unrun: 0.2.27 + unrun: 0.2.28 optionalDependencies: publint: 0.3.17 typescript: 5.9.3 @@ -61419,11 +61751,11 @@ snapshots: tsup@7.3.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(postcss@8.5.6)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@5.8.2))(typescript@5.8.2): dependencies: - bundle-require: 4.2.1(esbuild@0.19.2) + bundle-require: 4.2.1(esbuild@0.19.12) cac: 6.7.14 chokidar: 3.6.0 debug: 4.4.3(supports-color@8.1.1) - esbuild: 0.19.2 + esbuild: 0.19.12 execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 @@ -61857,9 +62189,9 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - unrun@0.2.27: + unrun@0.2.28: dependencies: - rolldown: 1.0.0-rc.3 + rolldown: 1.0.0-rc.5 unset-value@1.0.0: dependencies: diff --git a/scripts/bundle-size-report.mjs b/scripts/bundle-size-report.mjs index 6812082bd05..8a95bfc3418 100755 --- a/scripts/bundle-size-report.mjs +++ b/scripts/bundle-size-report.mjs @@ -55,7 +55,7 @@ function formatDelta(current, base) { return absPct > 5 ? `**${text}**` : text; } -/** Recursively sum all file sizes in a directory, excluding .map files */ +/** Recursively sum all file sizes in a directory, excluding .map files and src/ subdirs */ function dirSize(dir) { let total = 0; if (!existsSync(dir)) return total; @@ -64,6 +64,8 @@ function dirSize(dir) { for (const entry of entries) { const fullPath = join(dir, entry.name); if (entry.isDirectory()) { + // Skip src/ directories (source maps / original source) + if (entry.name === 'src') continue; total += dirSize(fullPath); } else if (entry.isFile()) { // Skip source map files diff --git a/tools/scripts/ci-is-affected.mjs b/tools/scripts/ci-is-affected.mjs index b1629dd76cc..23f2cbf0d7b 100644 --- a/tools/scripts/ci-is-affected.mjs +++ b/tools/scripts/ci-is-affected.mjs @@ -1,126 +1,28 @@ import { execSync } from 'child_process'; import yargs from 'yargs'; -const argv = yargs(process.argv.slice(2)) - .option('appName', { - type: 'string', - demandOption: true, - }) - .option('base', { - type: 'string', - }) - .option('head', { - type: 'string', - }) - .strict(false) - .parseSync(); +let { appName, base, head } = yargs(process.argv).argv; +base = base || 'origin/main'; +head = head || 'HEAD'; -let { appName, base, head } = argv; - -const hasGitRef = (ref) => { - if (!ref) { - return false; - } - try { - execSync(`git rev-parse --verify --quiet "${ref}^{commit}"`, { - stdio: 'ignore', - }); - return true; - } catch { - return false; - } -}; - -const resolveBase = (requestedBase) => { - if (hasGitRef(requestedBase)) { - return requestedBase; - } - if (hasGitRef(process.env.NX_BASE)) { - return process.env.NX_BASE; - } - if (hasGitRef('origin/main')) { - return 'origin/main'; - } - if (hasGitRef('main')) { - return 'main'; - } - if (hasGitRef('HEAD~1')) { - return 'HEAD~1'; - } - return null; -}; - -const resolveHead = (requestedHead) => { - if (hasGitRef(requestedHead)) { - return requestedHead; - } - if (hasGitRef(process.env.NX_HEAD)) { - return process.env.NX_HEAD; - } - if (hasGitRef('HEAD')) { - return 'HEAD'; - } - return null; -}; - -base = resolveBase(base); -head = resolveHead(head); - -const appNames = appName - .split(',') - .map((name) => name.trim()) - .filter(Boolean); - -if (appNames.length === 0) { - console.log('No valid app names were provided.'); +if (!appName) { + console.log('Could not find "appName" param.'); process.exit(1); } +const appNames = appName.split(','); -if (!base || !head) { - // Fail open for uncertain git state so CI does not skip required e2e checks. - console.warn( - `Unable to resolve a valid base/head commit (base=${base}, head=${head}). Running e2e by default.`, - ); - process.exit(0); -} - -if (base === head) { - // Same commit cannot produce an affected diff; run e2e to avoid false skips. - console.warn( - `Resolved base and head are identical (${base}). Running e2e by default.`, - ); - process.exit(0); -} - -let affectedProjectsOutput = []; -try { - affectedProjectsOutput = execSync( - `npx nx show projects --affected --base=${base} --head=${head}`, - ) - .toString() - .split('\n') - .map((p) => p.trim()) - .filter(Boolean); -} catch (error) { - console.warn( - `Failed to evaluate affected projects for base=${base} head=${head}. Running e2e by default.`, - ); - if (error instanceof Error) { - console.warn(error.message); - } - process.exit(0); -} - -const isAffected = affectedProjectsOutput.some((p) => appNames.includes(p)); +const isAffected = execSync(`npx nx show projects --affected`) + .toString() + .split('\n') + .map((p) => p.trim()) + .map((p) => appNames.includes(p)) + .some((included) => !!included) + .toString(); if (isAffected) { - console.log( - `appNames: ${appNames} , base=${base} head=${head}, conditions met, executing e2e CI.`, - ); + console.log(`appNames: ${appNames} , conditions met, executing e2e CI.`); process.exit(0); } else { - console.log( - `appNames: ${appNames} , base=${base} head=${head}, conditions not met, skipping e2e CI.`, - ); + console.log(`appNames: ${appNames} , conditions not met, skipping e2e CI.`); process.exit(1); }