diff --git a/packages/uni-mp-vite/__tests__/independentVendorChunk.spec.ts b/packages/uni-mp-vite/__tests__/independentVendorChunk.spec.ts new file mode 100644 index 0000000000..b57d085a94 --- /dev/null +++ b/packages/uni-mp-vite/__tests__/independentVendorChunk.spec.ts @@ -0,0 +1,76 @@ +import { IndependentVendorChunk } from '../src/plugin/independentVendorChunk' +import { virtualPagePath } from '../src/plugins/entry' + +const inputDir = '/proj/src' +const independentRoots = ['pkg6051indep/'] + +describe('IndependentVendorChunk.resolve', () => { + test('only independent subpackage importers go to that vendor', () => { + expect( + IndependentVendorChunk.resolve( + [`${inputDir}/pkg6051indep/class-probe.vue?vue&type=script`], + inputDir, + independentRoots + ) + ).toBe('pkg6051indep/common/vendor') + }) + + test('regular subpackage importers stay on main vendor', () => { + expect( + IndependentVendorChunk.resolve( + [`${inputDir}/pkg6051/class-probe.vue`], + inputDir, + independentRoots + ) + ).toBeUndefined() + }) + + test('shared with main package stays on main vendor', () => { + expect( + IndependentVendorChunk.resolve( + [ + `${inputDir}/pkg6051indep/class-probe.vue`, + `${inputDir}/pages/index/index.vue`, + ], + inputDir, + independentRoots + ) + ).toBeUndefined() + }) + + test('shared by two independent subpackages stays on main vendor', () => { + expect( + IndependentVendorChunk.resolve( + [ + `${inputDir}/pkg6051indep/a.vue`, + `${inputDir}/pkgOther/b.vue`, + ], + inputDir, + ['pkg6051indep/', 'pkgOther/'] + ) + ).toBeUndefined() + }) + + test('virtual page importer resolves into the independent root', () => { + expect( + IndependentVendorChunk.resolve( + [virtualPagePath('pkg6051indep/class-probe.vue')], + inputDir, + independentRoots + ) + ).toBe('pkg6051indep/common/vendor') + }) + + test('empty importers or roots do not move the chunk', () => { + expect( + IndependentVendorChunk.resolve( + [`${inputDir}/pkg6051indep/class-probe.vue`], + inputDir, + [] + ) + ).toBeUndefined() + expect( + IndependentVendorChunk.resolve([], inputDir, independentRoots) + ).toBeUndefined() + }) +}) diff --git a/packages/uni-mp-vite/src/plugin/build.ts b/packages/uni-mp-vite/src/plugin/build.ts index e37537f075..39bdd7c943 100644 --- a/packages/uni-mp-vite/src/plugin/build.ts +++ b/packages/uni-mp-vite/src/plugin/build.ts @@ -31,6 +31,7 @@ import { parseVirtualComponentPath, parseVirtualPagePath, } from '../plugins/entry' +import { IndependentVendorChunk } from './independentVendorChunk' const debugChunk = debug('uni:chunk') @@ -240,7 +241,8 @@ function createMoveToVendorChunkFn(): GetManualChunk | undefined { } return } - const { hasOptimizationSubPackages, subPackages } = getSubPackages() + const { hasOptimizationSubPackages, subPackages, independentSubPackages } = + getSubPackages() // 处理子包引用的 node_modules 中的文件 if ( hasOptimizationSubPackages && @@ -264,6 +266,17 @@ function createMoveToVendorChunkFn(): GetManualChunk | undefined { return `${matchSubPackages.values().next().value}common/vendor` } } + // 独立分包不能引用主包 JS。仅被某一个独立分包引用的 npm 打进该分包 vendor。 + // 不依赖 optimization.subPackages:这是微信运行时正确性,不是体积优化。 + const independentVendor = IndependentVendorChunk.resolve( + getModuleInfo(id)?.importers || [], + inputDir, + independentSubPackages + ) + if (independentVendor) { + debugChunk(independentVendor, normalizedId) + return independentVendor + } // 非项目内的 js 资源,均打包到 vendor debugChunk('common/vendor', normalizedId) return 'common/vendor' diff --git a/packages/uni-mp-vite/src/plugin/independentVendorChunk.ts b/packages/uni-mp-vite/src/plugin/independentVendorChunk.ts new file mode 100644 index 0000000000..35b4fd9c30 --- /dev/null +++ b/packages/uni-mp-vite/src/plugin/independentVendorChunk.ts @@ -0,0 +1,61 @@ +/** + * 独立分包 npm vendor 落点。 + * 微信独立分包不能 require 主包 JS;仅当全部 importer 落在同一个独立分包根下时, + * 才把该 npm 打进 `{root}common/vendor`,否则保持主包 common/vendor。 + */ +import path from 'path' +import { normalizePath } from '@dcloudio/uni-cli-shared' +import { + isUniComponentUrl, + isUniPageUrl, + parseVirtualComponentPath, + parseVirtualPagePath, +} from '../plugins/entry' + +export namespace IndependentVendorChunk { + export function resolveImporterFile( + importer: string, + inputDir: string + ): string { + const raw = normalizePath(importer) + if (isUniPageUrl(raw)) { + return normalizePath(path.resolve(inputDir, parseVirtualPagePath(raw))) + } + if (isUniComponentUrl(raw)) { + return normalizePath( + path.resolve(inputDir, parseVirtualComponentPath(raw)) + ) + } + return raw.split('?')[0] + } + + /** + * 微信独立分包不能引用主包 JS。 + * 仅当全部 importer 落在同一个独立分包根下时,把 npm 打进 `{root}common/vendor`。 + */ + export function resolve( + importers: readonly string[], + inputDir: string, + independentRoots: readonly string[] + ): string | undefined { + if (!importers.length || !independentRoots.length) { + return + } + const normalizedInput = normalizePath(inputDir) + const matched = new Set() + for (const importer of importers) { + const file = resolveImporterFile(importer, normalizedInput) + const root = independentRoots.find((subPackagePath) => + file.startsWith(`${normalizedInput}/${subPackagePath}`) + ) + if (!root) { + return + } + matched.add(root) + } + if (matched.size !== 1) { + return + } + return `${[...matched][0]}common/vendor` + } +} diff --git a/packages/uni-mp-vite/src/plugins/entry.ts b/packages/uni-mp-vite/src/plugins/entry.ts index 295a20b607..3cd24bf93b 100644 --- a/packages/uni-mp-vite/src/plugins/entry.ts +++ b/packages/uni-mp-vite/src/plugins/entry.ts @@ -59,12 +59,17 @@ export function parseComponentStyleIsolation(content: string) { let hasOptimizationSubPackages = false // 是否开启分包优化配置 let subPackages: string[] = [] +let independentSubPackages: string[] = [] +function normalizeSubPackageRoot(root: string) { + return `${root.replace(/\/$/, '')}/` +} function initSubPackages() { const inputDir = normalizePath(process.env.UNI_INPUT_DIR) const pagesJsonFile = path.resolve(inputDir, 'pages.json') if (!fs.existsSync(pagesJsonFile)) { hasOptimizationSubPackages = false subPackages = [] + independentSubPackages = [] return } const platform = process.env.UNI_PLATFORM @@ -76,14 +81,19 @@ function initSubPackages() { platform, { subpackages: true } ) - subPackages = Object.values(appJson.subPackages || appJson.subpackages || {}) - .filter(Boolean) - .map(({ root }) => `${root.replace(/\/$/, '')}/`) + const packages = Object.values( + appJson.subPackages || appJson.subpackages || {} + ).filter(Boolean) + subPackages = packages.map(({ root }) => normalizeSubPackageRoot(root)) + independentSubPackages = packages + .filter((pkg) => pkg.independent) + .map(({ root }) => normalizeSubPackageRoot(root)) } export function getSubPackages() { return { hasOptimizationSubPackages, subPackages, + independentSubPackages, } }