diff --git a/package.json b/package.json index eb86af3..e45a4f8 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "prettier": "3.3.3", "pretty-quick": "^4.0.0", "rimraf": "^6.0.1", + "rollup": "^4.47.1", "vite": "^5.4.3", "vitest": "^2.1.1", "wait-on": "^8.0.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5142e86..f1fa349 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ importers: version: 0.17.1 '@rollup/pluginutils': specifier: ^5.1.0 - version: 5.1.0(rollup@2.79.1) + version: 5.1.0(rollup@4.47.1) defu: specifier: ^6.1.4 version: 6.1.4 @@ -50,6 +50,9 @@ importers: rimraf: specifier: ^6.0.1 version: 6.0.1 + rollup: + specifier: ^4.47.1 + version: 4.47.1 vite: specifier: ^5.4.3 version: 5.4.3(@types/node@22.7.4)(sass-embedded@1.77.8)(terser@5.31.6) @@ -158,7 +161,7 @@ importers: version: 5.4.3(@types/node@22.7.4)(sass-embedded@1.77.8)(terser@5.31.6) vite-plugin-top-level-await: specifier: ^1.4.1 - version: 1.4.4(@swc/helpers@0.5.3)(rollup@4.28.1)(vite@5.4.3(@types/node@22.7.4)(sass-embedded@1.77.8)(terser@5.31.6)) + version: 1.4.4(@swc/helpers@0.5.3)(rollup@4.47.1)(vite@5.4.3(@types/node@22.7.4)(sass-embedded@1.77.8)(terser@5.31.6)) examples/vite-vite/vite-host: dependencies: @@ -207,7 +210,7 @@ importers: version: 6.0.6(@types/node@22.7.4)(jiti@2.4.2)(sass-embedded@1.77.8)(terser@5.31.6)(yaml@2.6.1) vite-plugin-top-level-await: specifier: ^1.4.4 - version: 1.4.4(@swc/helpers@0.5.3)(rollup@4.28.1)(vite@6.0.6(@types/node@22.7.4)(jiti@2.4.2)(sass-embedded@1.77.8)(terser@5.31.6)(yaml@2.6.1)) + version: 1.4.4(@swc/helpers@0.5.3)(rollup@4.47.1)(vite@6.0.6(@types/node@22.7.4)(jiti@2.4.2)(sass-embedded@1.77.8)(terser@5.31.6)(yaml@2.6.1)) examples/vite-vite/vite-remote: dependencies: @@ -259,7 +262,7 @@ importers: version: 6.0.6(@types/node@22.7.4)(jiti@2.4.2)(sass-embedded@1.77.8)(terser@5.31.6)(yaml@2.6.1) vite-plugin-top-level-await: specifier: ^1.4.4 - version: 1.4.4(@swc/helpers@0.5.3)(rollup@4.28.1)(vite@6.0.6(@types/node@22.7.4)(jiti@2.4.2)(sass-embedded@1.77.8)(terser@5.31.6)(yaml@2.6.1)) + version: 1.4.4(@swc/helpers@0.5.3)(rollup@4.47.1)(vite@6.0.6(@types/node@22.7.4)(jiti@2.4.2)(sass-embedded@1.77.8)(terser@5.31.6)(yaml@2.6.1)) examples/vite-webpack-rspack/dynamic-remote: dependencies: @@ -3384,66 +3387,66 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.21.0': + '@rollup/rollup-android-arm-eabi@4.28.1': resolution: { - integrity: sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==, + integrity: sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==, } cpu: [arm] os: [android] - '@rollup/rollup-android-arm-eabi@4.28.1': + '@rollup/rollup-android-arm-eabi@4.47.1': resolution: { - integrity: sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==, + integrity: sha512-lTahKRJip0knffA/GTNFJMrToD+CM+JJ+Qt5kjzBK/sFQ0EWqfKW3AYQSlZXN98tX0lx66083U9JYIMioMMK7g==, } cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.21.0': + '@rollup/rollup-android-arm64@4.28.1': resolution: { - integrity: sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==, + integrity: sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==, } cpu: [arm64] os: [android] - '@rollup/rollup-android-arm64@4.28.1': + '@rollup/rollup-android-arm64@4.47.1': resolution: { - integrity: sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==, + integrity: sha512-uqxkb3RJLzlBbh/bbNQ4r7YpSZnjgMgyoEOY7Fy6GCbelkDSAzeiogxMG9TfLsBbqmGsdDObo3mzGqa8hps4MA==, } cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.21.0': + '@rollup/rollup-darwin-arm64@4.28.1': resolution: { - integrity: sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==, + integrity: sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==, } cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-arm64@4.28.1': + '@rollup/rollup-darwin-arm64@4.47.1': resolution: { - integrity: sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==, + integrity: sha512-tV6reObmxBDS4DDyLzTDIpymthNlxrLBGAoQx6m2a7eifSNEZdkXQl1PE4ZjCkEDPVgNXSzND/k9AQ3mC4IOEQ==, } cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.21.0': + '@rollup/rollup-darwin-x64@4.28.1': resolution: { - integrity: sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==, + integrity: sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==, } cpu: [x64] os: [darwin] - '@rollup/rollup-darwin-x64@4.28.1': + '@rollup/rollup-darwin-x64@4.47.1': resolution: { - integrity: sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==, + integrity: sha512-XuJRPTnMk1lwsSnS3vYyVMu4x/+WIw1MMSiqj5C4j3QOWsMzbJEK90zG+SWV1h0B1ABGCQ0UZUjti+TQK35uHQ==, } cpu: [x64] os: [darwin] @@ -3456,6 +3459,14 @@ packages: cpu: [arm64] os: [freebsd] + '@rollup/rollup-freebsd-arm64@4.47.1': + resolution: + { + integrity: sha512-79BAm8Ag/tmJ5asCqgOXsb3WY28Rdd5Lxj8ONiQzWzy9LvWORd5qVuOnjlqiWWZJw+dWewEktZb5yiM1DLLaHw==, + } + cpu: [arm64] + os: [freebsd] + '@rollup/rollup-freebsd-x64@4.28.1': resolution: { @@ -3464,13 +3475,13 @@ packages: cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.21.0': + '@rollup/rollup-freebsd-x64@4.47.1': resolution: { - integrity: sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==, + integrity: sha512-OQ2/ZDGzdOOlyfqBiip0ZX/jVFekzYrGtUsqAfLDbWy0jh1PUU18+jYp8UMpqhly5ltEqotc2miLngf9FPSWIA==, } - cpu: [arm] - os: [linux] + cpu: [x64] + os: [freebsd] '@rollup/rollup-linux-arm-gnueabihf@4.28.1': resolution: @@ -3480,10 +3491,10 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.21.0': + '@rollup/rollup-linux-arm-gnueabihf@4.47.1': resolution: { - integrity: sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==, + integrity: sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==, } cpu: [arm] os: [linux] @@ -3496,12 +3507,12 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.21.0': + '@rollup/rollup-linux-arm-musleabihf@4.47.1': resolution: { - integrity: sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==, + integrity: sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==, } - cpu: [arm64] + cpu: [arm] os: [linux] '@rollup/rollup-linux-arm64-gnu@4.28.1': @@ -3512,10 +3523,10 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.21.0': + '@rollup/rollup-linux-arm64-gnu@4.47.1': resolution: { - integrity: sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==, + integrity: sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==, } cpu: [arm64] os: [linux] @@ -3528,6 +3539,14 @@ packages: cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-musl@4.47.1': + resolution: + { + integrity: sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==, + } + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-loongarch64-gnu@4.28.1': resolution: { @@ -3536,12 +3555,12 @@ packages: cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.21.0': + '@rollup/rollup-linux-loongarch64-gnu@4.47.1': resolution: { - integrity: sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==, + integrity: sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==, } - cpu: [ppc64] + cpu: [loong64] os: [linux] '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': @@ -3552,12 +3571,12 @@ packages: cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.21.0': + '@rollup/rollup-linux-ppc64-gnu@4.47.1': resolution: { - integrity: sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==, + integrity: sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==, } - cpu: [riscv64] + cpu: [ppc64] os: [linux] '@rollup/rollup-linux-riscv64-gnu@4.28.1': @@ -3568,12 +3587,20 @@ packages: cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.21.0': + '@rollup/rollup-linux-riscv64-gnu@4.47.1': resolution: { - integrity: sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==, + integrity: sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==, } - cpu: [s390x] + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.47.1': + resolution: + { + integrity: sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==, + } + cpu: [riscv64] os: [linux] '@rollup/rollup-linux-s390x-gnu@4.28.1': @@ -3584,12 +3611,12 @@ packages: cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.21.0': + '@rollup/rollup-linux-s390x-gnu@4.47.1': resolution: { - integrity: sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==, + integrity: sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==, } - cpu: [x64] + cpu: [s390x] os: [linux] '@rollup/rollup-linux-x64-gnu@4.28.1': @@ -3600,10 +3627,10 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.21.0': + '@rollup/rollup-linux-x64-gnu@4.47.1': resolution: { - integrity: sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==, + integrity: sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==, } cpu: [x64] os: [linux] @@ -3616,13 +3643,13 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.21.0': + '@rollup/rollup-linux-x64-musl@4.47.1': resolution: { - integrity: sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==, + integrity: sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==, } - cpu: [arm64] - os: [win32] + cpu: [x64] + os: [linux] '@rollup/rollup-win32-arm64-msvc@4.28.1': resolution: @@ -3632,12 +3659,12 @@ packages: cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.21.0': + '@rollup/rollup-win32-arm64-msvc@4.47.1': resolution: { - integrity: sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==, + integrity: sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==, } - cpu: [ia32] + cpu: [arm64] os: [win32] '@rollup/rollup-win32-ia32-msvc@4.28.1': @@ -3648,12 +3675,12 @@ packages: cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.21.0': + '@rollup/rollup-win32-ia32-msvc@4.47.1': resolution: { - integrity: sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==, + integrity: sha512-O+KcfeCORZADEY8oQJk4HK8wtEOCRE4MdOkb8qGZQNun3jzmj2nmhV/B/ZaaZOkPmJyvm/gW9n0gsB4eRa1eiQ==, } - cpu: [x64] + cpu: [ia32] os: [win32] '@rollup/rollup-win32-x64-msvc@4.28.1': @@ -3664,6 +3691,14 @@ packages: cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.47.1': + resolution: + { + integrity: sha512-CpKnYa8eHthJa3c+C38v/E+/KZyF1Jdh2Cz3DyKZqEWYgrM1IHFArXNWvBLPQCKUEsAqqKX27tTqVEFbDNUcOA==, + } + cpu: [x64] + os: [win32] + '@rsbuild/core@0.7.10': resolution: { @@ -4250,6 +4285,12 @@ packages: integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==, } + '@types/estree@1.0.8': + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, + } + '@types/express-serve-static-core@4.19.6': resolution: { @@ -11139,18 +11180,18 @@ packages: engines: { node: '>=10.0.0' } hasBin: true - rollup@4.21.0: + rollup@4.28.1: resolution: { - integrity: sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==, + integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==, } engines: { node: '>=18.0.0', npm: '>=8.0.0' } hasBin: true - rollup@4.28.1: + rollup@4.47.1: resolution: { - integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==, + integrity: sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==, } engines: { node: '>=18.0.0', npm: '>=8.0.0' } hasBin: true @@ -15669,6 +15710,10 @@ snapshots: optionalDependencies: rollup: 4.28.1 + '@rollup/plugin-virtual@3.0.2(rollup@4.47.1)': + optionalDependencies: + rollup: 4.47.1 + '@rollup/pluginutils@3.1.0(rollup@2.79.1)': dependencies: '@types/estree': 0.0.39 @@ -15681,13 +15726,13 @@ snapshots: estree-walker: 2.0.2 picomatch: 2.3.1 - '@rollup/pluginutils@5.1.0(rollup@2.79.1)': + '@rollup/pluginutils@5.1.0(rollup@4.47.1)': dependencies: '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 2.79.1 + rollup: 4.47.1 '@rollup/pluginutils@5.1.4(rollup@4.28.1)': dependencies: @@ -15697,111 +15742,123 @@ snapshots: optionalDependencies: rollup: 4.28.1 - '@rollup/rollup-android-arm-eabi@4.21.0': - optional: true - '@rollup/rollup-android-arm-eabi@4.28.1': optional: true - '@rollup/rollup-android-arm64@4.21.0': + '@rollup/rollup-android-arm-eabi@4.47.1': optional: true '@rollup/rollup-android-arm64@4.28.1': optional: true - '@rollup/rollup-darwin-arm64@4.21.0': + '@rollup/rollup-android-arm64@4.47.1': optional: true '@rollup/rollup-darwin-arm64@4.28.1': optional: true - '@rollup/rollup-darwin-x64@4.21.0': + '@rollup/rollup-darwin-arm64@4.47.1': optional: true '@rollup/rollup-darwin-x64@4.28.1': optional: true + '@rollup/rollup-darwin-x64@4.47.1': + optional: true + '@rollup/rollup-freebsd-arm64@4.28.1': optional: true + '@rollup/rollup-freebsd-arm64@4.47.1': + optional: true + '@rollup/rollup-freebsd-x64@4.28.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.21.0': + '@rollup/rollup-freebsd-x64@4.47.1': optional: true '@rollup/rollup-linux-arm-gnueabihf@4.28.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.21.0': + '@rollup/rollup-linux-arm-gnueabihf@4.47.1': optional: true '@rollup/rollup-linux-arm-musleabihf@4.28.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.21.0': + '@rollup/rollup-linux-arm-musleabihf@4.47.1': optional: true '@rollup/rollup-linux-arm64-gnu@4.28.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.21.0': + '@rollup/rollup-linux-arm64-gnu@4.47.1': optional: true '@rollup/rollup-linux-arm64-musl@4.28.1': optional: true + '@rollup/rollup-linux-arm64-musl@4.47.1': + optional: true + '@rollup/rollup-linux-loongarch64-gnu@4.28.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.21.0': + '@rollup/rollup-linux-loongarch64-gnu@4.47.1': optional: true '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.21.0': + '@rollup/rollup-linux-ppc64-gnu@4.47.1': optional: true '@rollup/rollup-linux-riscv64-gnu@4.28.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.21.0': + '@rollup/rollup-linux-riscv64-gnu@4.47.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.47.1': optional: true '@rollup/rollup-linux-s390x-gnu@4.28.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.21.0': + '@rollup/rollup-linux-s390x-gnu@4.47.1': optional: true '@rollup/rollup-linux-x64-gnu@4.28.1': optional: true - '@rollup/rollup-linux-x64-musl@4.21.0': + '@rollup/rollup-linux-x64-gnu@4.47.1': optional: true '@rollup/rollup-linux-x64-musl@4.28.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.21.0': + '@rollup/rollup-linux-x64-musl@4.47.1': optional: true '@rollup/rollup-win32-arm64-msvc@4.28.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.21.0': + '@rollup/rollup-win32-arm64-msvc@4.47.1': optional: true '@rollup/rollup-win32-ia32-msvc@4.28.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.21.0': + '@rollup/rollup-win32-ia32-msvc@4.47.1': optional: true '@rollup/rollup-win32-x64-msvc@4.28.1': optional: true + '@rollup/rollup-win32-x64-msvc@4.47.1': + optional: true + '@rsbuild/core@0.7.10': dependencies: '@rsbuild/shared': 0.7.10(@swc/helpers@0.5.3) @@ -16161,6 +16218,8 @@ snapshots: '@types/estree@1.0.6': {} + '@types/estree@1.0.8': {} + '@types/express-serve-static-core@4.19.6': dependencies: '@types/node': 22.7.4 @@ -20908,28 +20967,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - rollup@4.21.0: - dependencies: - '@types/estree': 1.0.5 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.21.0 - '@rollup/rollup-android-arm64': 4.21.0 - '@rollup/rollup-darwin-arm64': 4.21.0 - '@rollup/rollup-darwin-x64': 4.21.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.21.0 - '@rollup/rollup-linux-arm-musleabihf': 4.21.0 - '@rollup/rollup-linux-arm64-gnu': 4.21.0 - '@rollup/rollup-linux-arm64-musl': 4.21.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.21.0 - '@rollup/rollup-linux-riscv64-gnu': 4.21.0 - '@rollup/rollup-linux-s390x-gnu': 4.21.0 - '@rollup/rollup-linux-x64-gnu': 4.21.0 - '@rollup/rollup-linux-x64-musl': 4.21.0 - '@rollup/rollup-win32-arm64-msvc': 4.21.0 - '@rollup/rollup-win32-ia32-msvc': 4.21.0 - '@rollup/rollup-win32-x64-msvc': 4.21.0 - fsevents: 2.3.3 - rollup@4.28.1: dependencies: '@types/estree': 1.0.6 @@ -20955,6 +20992,32 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.28.1 fsevents: 2.3.3 + rollup@4.47.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.47.1 + '@rollup/rollup-android-arm64': 4.47.1 + '@rollup/rollup-darwin-arm64': 4.47.1 + '@rollup/rollup-darwin-x64': 4.47.1 + '@rollup/rollup-freebsd-arm64': 4.47.1 + '@rollup/rollup-freebsd-x64': 4.47.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.47.1 + '@rollup/rollup-linux-arm-musleabihf': 4.47.1 + '@rollup/rollup-linux-arm64-gnu': 4.47.1 + '@rollup/rollup-linux-arm64-musl': 4.47.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.47.1 + '@rollup/rollup-linux-ppc64-gnu': 4.47.1 + '@rollup/rollup-linux-riscv64-gnu': 4.47.1 + '@rollup/rollup-linux-riscv64-musl': 4.47.1 + '@rollup/rollup-linux-s390x-gnu': 4.47.1 + '@rollup/rollup-linux-x64-gnu': 4.47.1 + '@rollup/rollup-linux-x64-musl': 4.47.1 + '@rollup/rollup-win32-arm64-msvc': 4.47.1 + '@rollup/rollup-win32-ia32-msvc': 4.47.1 + '@rollup/rollup-win32-x64-msvc': 4.47.1 + fsevents: 2.3.3 + run-applescript@7.0.0: {} run-parallel@1.2.0: @@ -21977,19 +22040,29 @@ snapshots: - rollup - supports-color - vite-plugin-top-level-await@1.4.4(@swc/helpers@0.5.3)(rollup@4.28.1)(vite@5.4.3(@types/node@22.7.4)(sass-embedded@1.77.8)(terser@5.31.6)): + vite-plugin-top-level-await@1.4.4(@swc/helpers@0.5.3)(rollup@4.28.1)(vite@6.0.6(@types/node@22.7.4)(jiti@2.4.2)(sass-embedded@1.77.8)(terser@5.31.6)(yaml@2.6.1)): dependencies: '@rollup/plugin-virtual': 3.0.2(rollup@4.28.1) '@swc/core': 1.7.14(@swc/helpers@0.5.3) uuid: 10.0.0 + vite: 6.0.6(@types/node@22.7.4)(jiti@2.4.2)(sass-embedded@1.77.8)(terser@5.31.6)(yaml@2.6.1) + transitivePeerDependencies: + - '@swc/helpers' + - rollup + + vite-plugin-top-level-await@1.4.4(@swc/helpers@0.5.3)(rollup@4.47.1)(vite@5.4.3(@types/node@22.7.4)(sass-embedded@1.77.8)(terser@5.31.6)): + dependencies: + '@rollup/plugin-virtual': 3.0.2(rollup@4.47.1) + '@swc/core': 1.7.14(@swc/helpers@0.5.3) + uuid: 10.0.0 vite: 5.4.3(@types/node@22.7.4)(sass-embedded@1.77.8)(terser@5.31.6) transitivePeerDependencies: - '@swc/helpers' - rollup - vite-plugin-top-level-await@1.4.4(@swc/helpers@0.5.3)(rollup@4.28.1)(vite@6.0.6(@types/node@22.7.4)(jiti@2.4.2)(sass-embedded@1.77.8)(terser@5.31.6)(yaml@2.6.1)): + vite-plugin-top-level-await@1.4.4(@swc/helpers@0.5.3)(rollup@4.47.1)(vite@6.0.6(@types/node@22.7.4)(jiti@2.4.2)(sass-embedded@1.77.8)(terser@5.31.6)(yaml@2.6.1)): dependencies: - '@rollup/plugin-virtual': 3.0.2(rollup@4.28.1) + '@rollup/plugin-virtual': 3.0.2(rollup@4.47.1) '@swc/core': 1.7.14(@swc/helpers@0.5.3) uuid: 10.0.0 vite: 6.0.6(@types/node@22.7.4)(jiti@2.4.2)(sass-embedded@1.77.8)(terser@5.31.6)(yaml@2.6.1) @@ -22016,7 +22089,7 @@ snapshots: dependencies: esbuild: 0.21.5 postcss: 8.4.44 - rollup: 4.21.0 + rollup: 4.47.1 optionalDependencies: '@types/node': 22.7.4 fsevents: 2.3.3 diff --git a/src/plugins/pluginMFManifest.ts b/src/plugins/pluginMFManifest.ts index d1303f7..88be1e1 100644 --- a/src/plugins/pluginMFManifest.ts +++ b/src/plugins/pluginMFManifest.ts @@ -1,42 +1,78 @@ -import { join, relative } from 'pathe'; -import { Manifest, Plugin } from 'vite'; +import * as path from 'pathe'; +import { Plugin } from 'vite'; +import type { PluginContext } from 'rollup'; import { getNormalizeModuleFederationOptions, getNormalizeShareItem, } from '../utils/normalizeModuleFederationOptions'; -import { getPreBuildLibImportId, getUsedRemotesMap, getUsedShares } from '../virtualModules'; +import { getUsedRemotesMap, getUsedShares } from '../virtualModules'; + +import { + buildFileToShareKeyMap, + collectCssAssets, + createEmptyAssetMap, + deduplicateAssets, + JS_EXTENSIONS, + PreloadMap, + processModuleAssets, + trackAsset, +} from '../utils/cssModuleHelpers'; + +// Helper to build share key map with proper context typing +interface BuildFileToShareKeyMapContext { + resolve: PluginContext['resolve']; +} const Manifest = (): Plugin[] => { const mfOptions = getNormalizeModuleFederationOptions(); const { name, filename, getPublicPath, manifest: manifestOptions } = mfOptions; + let mfManifestName: string = ''; if (manifestOptions === true) { mfManifestName = 'mf-manifest.json'; } if (typeof manifestOptions !== 'boolean') { - mfManifestName = join(manifestOptions?.filePath || '', manifestOptions?.fileName || ''); + mfManifestName = path.join(manifestOptions?.filePath || '', manifestOptions?.fileName || ''); } - let extensions: string[]; + let root: string; - type PreloadMap = Record< - string, - { - sync: string[]; - async: string[]; - } - >; // 保存模块和文件的映射关系 let remoteEntryFile: string; let publicPath: string; let _command: string; let _originalConfigBase: string | undefined; let viteConfig: any; + + /** + * Adds global CSS assets to all module exports + * @param filesMap - The preload map to update + * @param cssAssets - Set of CSS asset filenames to add + */ + const addCssAssetsToAllExports = (filesMap: PreloadMap, cssAssets: Set) => { + Object.keys(filesMap).forEach((key) => { + cssAssets.forEach((cssAsset) => { + trackAsset(filesMap, key, cssAsset, false, 'css'); + }); + }); + }; + return [ { name: 'module-federation-manifest', apply: 'serve', + /** + * Stores resolved Vite config for later use + */ + /** + * Finalizes configuration after all plugins are resolved + * @param config - Fully resolved Vite config + */ configResolved(config) { viteConfig = config; }, + /** + * Configures dev server middleware to handle manifest requests + * @param server - Vite dev server instance + */ configureServer(server) { server.middlewares.use((req, res, next) => { if (!mfManifestName) { @@ -83,24 +119,21 @@ const Manifest = (): Plugin[] => { { name: 'module-federation-manifest', enforce: 'post', + /** + * Initial plugin configuration + * @param config - Vite config object + * @param command - Current Vite command (serve/build) + */ config(config, { command }) { if (!config.build) config.build = {}; - if (!config.build.manifest) + if (!config.build.manifest) { config.build.manifest = config.build.manifest || !!manifestOptions; + } _command = command; _originalConfigBase = config.base; }, configResolved(config) { root = config.root; - extensions = config.resolve.extensions || [ - '.mjs', - '.js', - '.mts', - '.ts', - '.jsx', - '.tsx', - '.json', - ]; let base = config.base; if (_command === 'serve') { base = (config.server.origin || '') + config.base; @@ -108,121 +141,85 @@ const Manifest = (): Plugin[] => { publicPath = _originalConfigBase === '' ? 'auto' : base ? base.replace(/\/?$/, '/') : 'auto'; }, + /** + * Generates the module federation manifest file + * @param options - Rollup output options + * @param bundle - Generated bundle assets + */ async generateBundle(options, bundle) { if (!mfManifestName) return; - const exposesModules = Object.keys(mfOptions.exposes).map( - (item) => mfOptions.exposes[item].import - ); // 获取你提供的 moduleIds - const filesContainingModules: PreloadMap = {}; - // 帮助函数:检查模块路径是否匹配 - const isModuleMatched = (relativeModulePath: string, preloadModule: string) => { - // 先尝试直接匹配 - if (relativeModulePath === preloadModule) return true; - // 如果 preloadModule 没有后缀,尝试添加可能的后缀进行匹配 - for (const ext of extensions) { - if (relativeModulePath === `${preloadModule}${ext}`) { - return true; - } - } - return false; - }; + let filesMap: PreloadMap = {}; - // 遍历打包生成的每个文件 - for (const [fileName, fileData] of Object.entries(bundle)) { + // First pass: Find remoteEntry file + for (const [_, fileData] of Object.entries(bundle)) { if ( mfOptions.filename.replace(/[\[\]]/g, '_').replace(/\.[^/.]+$/, '') === fileData.name || fileData.name === 'remoteEntry' ) { remoteEntryFile = fileData.fileName; - } - if (fileData.type === 'chunk') { - // 遍历该文件的所有模块 - for (const modulePath of Object.keys(fileData.modules)) { - // 将绝对路径转换为相对于 Vite root 的相对路径 - const relativeModulePath = relative(root, modulePath); - - // 检查模块是否在 preloadModules 列表中 - for (const preloadModule of exposesModules) { - const formatPreloadModule = preloadModule.replace('./', ''); - if (isModuleMatched(relativeModulePath, formatPreloadModule)) { - if (!filesContainingModules[preloadModule]) { - filesContainingModules[preloadModule] = { - sync: [], - async: [], - }; - } - console.log(Object.keys(fileData.modules)); - filesContainingModules[preloadModule].sync.push(fileName); - filesContainingModules[preloadModule].async.push( - ...(fileData.dynamicImports || []) - ); - findSynchronousImports(fileName, filesContainingModules[preloadModule].sync); - break; // 如果找到匹配,跳出循环 - } - } - } + break; // We can break early since we only need to find remoteEntry once } } - // 递归查找模块的同步导入文件 - function findSynchronousImports(fileName: string, array: string[]) { - const fileData = bundle[fileName]; - if (fileData && fileData.type === 'chunk') { - array.push(fileName); // 将当前文件加入预加载列表 - // 遍历该文件的同步导入文件 - fileData.imports.forEach((importedFile) => { - if (array.indexOf(importedFile) === -1) { - findSynchronousImports(importedFile, array); // 递归查找同步导入的文件 - } - }); - } - } - const fileToShareKey: Record = {}; - await Promise.all( - Array.from(getUsedShares()).map(async (shareKey) => { - const file = (await (this as any).resolve(getPreBuildLibImportId(shareKey))).id.split( - '?' - )[0]; - fileToShareKey[file] = shareKey; - }) + // Second pass: Collect all CSS assets + const allCssAssets = collectCssAssets(bundle); + + const exposesModules = Object.keys(mfOptions.exposes).map( + (item) => mfOptions.exposes[item].import ); - // 遍历打包生成的每个文件 - for (const [fileName, fileData] of Object.entries(bundle)) { - if (fileData.type === 'chunk') { - // 遍历该文件的所有模块 - for (const modulePath of Object.keys(fileData.modules)) { - const sharedKey = fileToShareKey[modulePath]; - if (sharedKey) { - if (!filesContainingModules[sharedKey]) { - filesContainingModules[sharedKey] = { - sync: [], - async: [], - }; - } - filesContainingModules[sharedKey].sync.push(fileName); - filesContainingModules[sharedKey].async.push(...(fileData.dynamicImports || [])); - findSynchronousImports(fileName, filesContainingModules[sharedKey].sync); - break; // 如果找到匹配,跳出循环 - } + // Process exposed modules + processModuleAssets(bundle, filesMap, (modulePath) => { + const absoluteModulePath = path.resolve(root, modulePath); + return exposesModules.find((exposeModule) => { + const exposePath = path.resolve(root, exposeModule); + + // First try exact path match + if (absoluteModulePath === exposePath) { + return true; } - } - } - Object.keys(filesContainingModules).forEach((key) => { - filesContainingModules[key].sync = Array.from(new Set(filesContainingModules[key].sync)); - filesContainingModules[key].async = Array.from( - new Set(filesContainingModules[key].async) - ); + + // Then try path match without known extensions + const getPathWithoutKnownExt = (filePath: string) => { + const ext = path.extname(filePath); + return JS_EXTENSIONS.includes(ext as any) + ? path.join(path.dirname(filePath), path.basename(filePath, ext)) + : filePath; + }; + const modulePathNoExt = getPathWithoutKnownExt(absoluteModulePath); + const exposePathNoExt = getPathWithoutKnownExt(exposePath); + return modulePathNoExt === exposePathNoExt; + }); }); + + // Process shared modules + const fileToShareKey = await buildFileToShareKeyMap( + getUsedShares(), + this.resolve.bind(this) + ); + processModuleAssets(bundle, filesMap, (modulePath) => fileToShareKey.get(modulePath)); + + // Add all CSS assets to every export + addCssAssetsToAllExports(filesMap, allCssAssets); + + // Final deduplication of all assets + filesMap = deduplicateAssets(filesMap); + this.emitFile({ type: 'asset', fileName: mfManifestName, - source: JSON.stringify(generateMFManifest(filesContainingModules)), + source: JSON.stringify(generateMFManifest(filesMap)), }); }, }, ]; + + /** + * Generates the final manifest JSON structure + * @param preloadMap - Map of module assets to include + * @returns Complete manifest object + */ function generateMFManifest(preloadMap: PreloadMap) { const options = getNormalizeModuleFederationOptions(); const { name } = options; @@ -231,44 +228,23 @@ const Manifest = (): Plugin[] => { path: '', type: 'module', }; - const remotes: { - federationContainerName: string; - moduleName: string; - alias: string; - entry: string; - }[] = []; - const usedRemotesMap = getUsedRemotesMap(); - Object.keys(usedRemotesMap).forEach((remoteKey) => { - const usedModules = Array.from(usedRemotesMap[remoteKey]); - usedModules.forEach((moduleKey) => { - remotes.push({ + + // Process remotes + const remotes = Array.from(Object.entries(getUsedRemotesMap())).flatMap( + ([remoteKey, modules]) => + Array.from(modules).map((moduleKey) => ({ federationContainerName: options.remotes[remoteKey].entry, moduleName: moduleKey.replace(remoteKey, '').replace('/', ''), alias: remoteKey, entry: '*', - }); - }); - }); - type ManifestItem = { - id: string; - name: string; - version: string; - requiredVersion: string; - assets: { - js: { - async: string[]; - sync: string[]; - }; - css: { - async: string[]; - sync: string[]; - }; - }; - }; - // @ts-ignore - const shared: ManifestItem[] = Array.from(getUsedShares()) + })) + ); + + // Process shared dependencies + const shared = Array.from(getUsedShares()) .map((shareKey) => { const shareItem = getNormalizeShareItem(shareKey); + const assets = preloadMap[shareKey] || createEmptyAssetMap(); return { id: `${name}:${shareKey}`, @@ -277,45 +253,48 @@ const Manifest = (): Plugin[] => { requiredVersion: shareItem.shareConfig.requiredVersion, assets: { js: { - async: preloadMap?.[shareKey]?.async || [], - sync: preloadMap?.[shareKey]?.sync || [], + async: assets.js.async, + sync: assets.js.sync, }, css: { - async: [], - sync: [], + async: assets.css.async, + sync: assets.css.sync, }, }, }; }) - .filter((item) => item); - const exposes = Object.keys(options.exposes) - .map((key) => { - // assets(.css, .jpg, .svg等)其他资源, 不重要, 暂未处理 + .filter(Boolean); + + // Process exposed modules + const exposes = Object.entries(options.exposes) + .map(([key, value]) => { const formatKey = key.replace('./', ''); - const sourceFile = options.exposes[key].import; + const sourceFile = value.import; + const assets = preloadMap[sourceFile] || createEmptyAssetMap(); + return { - id: name + ':' + formatKey, + id: `${name}:${formatKey}`, name: formatKey, assets: { js: { - async: preloadMap?.[sourceFile]?.async || [], - sync: preloadMap?.[sourceFile]?.sync || [], + async: assets.js.async, + sync: assets.js.sync, }, css: { - sync: [], - async: [], + async: assets.css.async, + sync: assets.css.sync, }, }, path: key, }; }) - .filter((item) => item); // Filter out any null values + .filter(Boolean); - const result = { + return { id: name, - name: name, + name, metaData: { - name: name, + name, type: 'app', buildInfo: { buildVersion: '1.0.0', @@ -326,8 +305,6 @@ const Manifest = (): Plugin[] => { types: { path: '', name: '', - // "zip": "@mf-types.zip", - // "api": "@mf-types.d.ts" }, globalName: name, pluginVersion: '0.2.5', @@ -337,7 +314,6 @@ const Manifest = (): Plugin[] => { remotes, exposes, }; - return result; } }; diff --git a/src/utils/__tests__/cssModuleHelpers.test.ts b/src/utils/__tests__/cssModuleHelpers.test.ts new file mode 100644 index 0000000..3877456 --- /dev/null +++ b/src/utils/__tests__/cssModuleHelpers.test.ts @@ -0,0 +1,177 @@ +import { + createEmptyAssetMap, + trackAsset, + isCSSFile, + collectCssAssets, + processModuleAssets, + addCssAssetsToAllExports, + deduplicateAssets, + buildFileToShareKeyMap, +} from '../cssModuleHelpers'; +import type { OutputBundleItem, PreloadMap } from '../cssModuleHelpers'; + +describe('cssModuleHelpers', () => { + describe('createEmptyAssetMap', () => { + it('creates empty asset maps for js and css', () => { + const result = createEmptyAssetMap(); + expect(result).toEqual({ + js: { sync: [], async: [] }, + css: { sync: [], async: [] }, + }); + }); + }); + + describe('trackAsset', () => { + let map: PreloadMap; + + beforeEach(() => { + map = {}; + }); + + it('tracks sync js asset', () => { + trackAsset(map, 'module1', 'file.js', false, 'js'); + expect(map).toEqual({ + module1: { + js: { sync: ['file.js'], async: [] }, + css: { sync: [], async: [] }, + }, + }); + }); + + it('tracks async css asset', () => { + trackAsset(map, 'module2', 'styles.css', true, 'css'); + expect(map).toEqual({ + module2: { + js: { sync: [], async: [] }, + css: { sync: [], async: ['styles.css'] }, + }, + }); + }); + + it('deduplicates assets', () => { + trackAsset(map, 'module1', 'file.js', false, 'js'); + trackAsset(map, 'module1', 'file.js', false, 'js'); + expect(map.module1.js.sync).toEqual(['file.js']); + }); + }); + + describe('isCSSFile', () => { + it.each([ + ['styles.css', true], + ['styles.scss', true], + ['styles.less', true], + ['script.js', false], + ['image.png', false], + ['file.txt', false], + ])('detects %s as CSS: %s', (filename, expected) => { + expect(isCSSFile(filename)).toBe(expected); + }); + }); + + describe('collectCssAssets', () => { + it('collects css assets from bundle', () => { + const bundle = { + 'styles.css': { type: 'asset', fileName: 'styles.css' }, + 'script.js': { type: 'chunk', fileName: 'script.js' }, + 'other.css': { type: 'asset', fileName: 'other.css' }, + } as Record; + + const result = collectCssAssets(bundle); + expect(result).toEqual(new Set(['styles.css', 'other.css'])); + }); + + it('ignores non-css assets', () => { + const bundle = { + 'script.js': { type: 'chunk', fileName: 'script.js' }, + 'image.png': { type: 'asset', fileName: 'image.png' }, + } as Record; + + const result = collectCssAssets(bundle); + expect(result.size).toBe(0); + }); + }); + + describe('processModuleAssets', () => { + it('processes module assets', () => { + const bundle = { + 'chunk.js': { + type: 'chunk', + fileName: 'chunk.js', + modules: { + module1: {}, + module2: {}, + }, + dynamicImports: ['async.js'], + }, + 'async.js': { + type: 'chunk', + fileName: 'async.js', + }, + } as Record; + + const filesMap = {}; + const moduleMatcher = (path: string) => path; + + processModuleAssets(bundle, filesMap, moduleMatcher); + + expect(filesMap).toEqual({ + module1: { + js: { sync: ['chunk.js'], async: ['async.js'] }, + css: { sync: [], async: [] }, + }, + module2: { + js: { sync: ['chunk.js'], async: ['async.js'] }, + css: { sync: [], async: [] }, + }, + }); + }); + }); + + describe('addCssAssetsToAllExports', () => { + it('adds css assets to all exports', () => { + const filesMap = { + module1: createEmptyAssetMap(), + module2: createEmptyAssetMap(), + }; + const cssAssets = new Set(['styles.css']); + + addCssAssetsToAllExports(filesMap, cssAssets); + + expect(filesMap.module1.css.sync).toEqual(['styles.css']); + expect(filesMap.module2.css.sync).toEqual(['styles.css']); + }); + }); + + describe('deduplicateAssets', () => { + it('deduplicates assets', () => { + const filesMap = { + module1: { + js: { sync: ['file.js', 'file.js'], async: [] }, + css: { sync: ['styles.css', 'styles.css'], async: [] }, + }, + }; + + const result = deduplicateAssets(filesMap); + expect(result.module1.js.sync).toEqual(['file.js']); + expect(result.module1.css.sync).toEqual(['styles.css']); + }); + }); + + describe('buildFileToShareKeyMap', () => { + it('builds file to share key map', async () => { + const shareKeys = new Set(['react']); + const resolveFn = vi.fn().mockResolvedValue({ id: 'path/to/react.js' }); + + // Mock getNormalizeModuleFederationOptions + vi.mock('../normalizeModuleFederationOptions', () => ({ + getNormalizeModuleFederationOptions: vi.fn(() => ({ + name: 'test-app', + virtualModuleDir: '__mf_virtual_test', + })), + })); + + const result = await buildFileToShareKeyMap(shareKeys, resolveFn); + expect(result.get('path/to/react.js')).toBe('react'); + }); + }); +}); diff --git a/src/utils/cssModuleHelpers.ts b/src/utils/cssModuleHelpers.ts new file mode 100644 index 0000000..f93ea01 --- /dev/null +++ b/src/utils/cssModuleHelpers.ts @@ -0,0 +1,183 @@ +import { getPreBuildLibImportId } from '../virtualModules'; + +export type OutputBundleItem = { + type: 'chunk' | 'asset'; + name?: string; + fileName: string; + modules?: Record | undefined; + dynamicImports?: string[] | undefined; +}; + +export const ASSET_TYPES = ['js', 'css'] as const; +export const LOAD_TIMINGS = ['sync', 'async'] as const; +export const JS_EXTENSIONS = ['.ts', '.tsx', '.jsx', '.mjs', '.cjs'] as const; + +export type AssetType = (typeof ASSET_TYPES)[number]; +export type AssetMap = { + sync: string[]; + async: string[]; +}; + +export type PreloadMap = Record< + string, + { + [K in (typeof ASSET_TYPES)[number]]: AssetMap; + } +>; + +/** + * Creates an empty asset map structure for tracking JS and CSS assets + * @returns Initialized asset map with sync/async arrays for JS and CSS + */ +export const createEmptyAssetMap = (): { js: AssetMap; css: AssetMap } => ({ + js: { sync: [], async: [] }, + css: { sync: [], async: [] }, +}); + +/** + * Tracks an asset in the preload map with deduplication + * @param map - The preload map to update + * @param key - The module key to track under + * @param fileName - The asset filename to track + * @param isAsync - Whether the asset is loaded async + * @param type - The asset type ('js' or 'css') + */ +export const trackAsset = ( + map: PreloadMap, + key: string, + fileName: string, + isAsync: boolean, + type: AssetType +) => { + if (!map[key]) { + map[key] = createEmptyAssetMap(); + } + const target = isAsync ? map[key][type].async : map[key][type].sync; + if (!target.includes(fileName)) { + target.push(fileName); + } +}; + +/** + * Checks if a file is a CSS file by extension + * @param fileName - The filename to check + * @returns True if file has a CSS extension (.css, .scss, .less) + */ +export const isCSSFile = (fileName: string): boolean => { + return fileName.endsWith('.css') || fileName.endsWith('.scss') || fileName.endsWith('.less'); +}; + +/** + * Collects all CSS assets from the bundle + * @param bundle - The Rollup output bundle + * @returns Set of CSS asset filenames + */ +export const collectCssAssets = (bundle: Record): Set => { + const cssAssets = new Set(); + for (const [fileName, fileData] of Object.entries(bundle)) { + if (fileData.type === 'asset' && isCSSFile(fileName)) { + cssAssets.add(fileName); + } + } + return cssAssets; +}; + +/** + * Processes module assets and tracks them in the files map + * @param bundle - The Rollup output bundle + * @param filesMap - The preload map to populate + * @param moduleMatcher - Function that matches module paths to keys + */ +export const processModuleAssets = ( + bundle: Record, + filesMap: PreloadMap, + moduleMatcher: (modulePath: string) => string | undefined +) => { + for (const [fileName, fileData] of Object.entries(bundle)) { + if (fileData.type !== 'chunk') continue; + + if (!fileData.modules) continue; + + for (const modulePath of Object.keys(fileData.modules)) { + const matchKey = moduleMatcher(modulePath); + if (!matchKey) continue; + + // Track main JS chunk + trackAsset(filesMap, matchKey, fileName, false, 'js'); + + // Handle dynamic imports + if (fileData.dynamicImports) { + for (const dynamicImport of fileData.dynamicImports) { + const importData = bundle[dynamicImport]; + if (!importData) continue; + + const isCss = isCSSFile(dynamicImport); + trackAsset(filesMap, matchKey, dynamicImport, true, isCss ? 'css' : 'js'); + } + } + } + } +}; + +/** + * Adds global CSS assets to all module exports + * @param filesMap - The preload map to update + * @param cssAssets - Set of CSS asset filenames to add + */ +export const addCssAssetsToAllExports = (filesMap: PreloadMap, cssAssets: Set) => { + Object.keys(filesMap).forEach((key) => { + cssAssets.forEach((cssAsset) => { + trackAsset(filesMap, key, cssAsset, false, 'css'); + }); + }); +}; + +/** + * Deduplicates assets in the files map + * @param filesMap - The preload map to deduplicate + * @returns New deduplicated preload map + */ +export const deduplicateAssets = (filesMap: PreloadMap): PreloadMap => { + const result: PreloadMap = {}; + for (const [key, assetMaps] of Object.entries(filesMap)) { + result[key] = createEmptyAssetMap(); + for (const type of ASSET_TYPES) { + for (const timing of LOAD_TIMINGS) { + result[key][type][timing] = Array.from(new Set(assetMaps[type][timing])); + } + } + } + return result; +}; + +/** + * Builds a mapping between module files and their share keys + * @param shareKeys - Set of share keys to map + * @param resolveFn - Function to resolve module paths + * @returns Map of file paths to their corresponding share keys + */ +export const buildFileToShareKeyMap = async ( + shareKeys: Set, + resolveFn: (id: string) => Promise<{ id: string } | null> +): Promise> => { + const fileToShareKey = new Map(); + + const resolutions = await Promise.all( + Array.from(shareKeys).map((shareKey) => + resolveFn(getPreBuildLibImportId(shareKey)) + .then((resolution) => ({ + shareKey, + file: resolution?.id?.split('?')[0], + })) + .catch(() => null) + ) + ); + + for (const resolution of resolutions) { + if (resolution?.file) { + fileToShareKey.set(resolution.file, resolution.shareKey); + } + } + + return fileToShareKey; +};