Files
crawler-plugin/frontend-vue/tests/build-chunk-planner.test.ts
T

211 lines
9.1 KiB
TypeScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import { createChunkPlanner } from '../src/shared/build-chunk-planner.ts'
test('test_task_091_chunk_normal_default_path', () => {
const planner = createChunkPlanner({
entries: ['publish', 'dedupe', 'convert'],
vendorRules: [
{ chunk: 'element-plus', patterns: ['element-plus', '@element-plus'] },
{ chunk: 'shared', patterns: ['/src/shared/'] },
{ chunk: 'vue-vendor', patterns: ['/node_modules/vue/', '/node_modules/@vue/', 'vue-router'] },
],
perEntryRules: [
{ entry: 'publish', chunk: 'publish-page', patterns: ['/src/pages/publish/'] },
{ entry: 'dedupe', chunk: 'dedupe-page', patterns: ['/src/pages/dedupe/'] },
{ entry: 'convert', chunk: 'convert-page', patterns: ['/src/pages/convert/'] },
],
})
const config = planner.manualChunksConfig()
assert.deepEqual(
Object.keys(config).sort(),
['convert-page', 'dedupe-page', 'element-plus', 'publish-page', 'shared', 'vue-vendor'].sort(),
'默认路径产出全部计划 chunk',
)
// Element Plus 模块进入独立 chunk
assert.equal(
planner.assign('D:/repo/node_modules/element-plus/es/components/button/index.mjs', 'publish'),
'element-plus',
)
// 公共业务模块进入公共业务 chunk
assert.equal(planner.assign('D:/repo/src/shared/api/java-modules.ts', 'publish'), 'shared')
// 页面私有模块进入页面 chunk
assert.equal(planner.assign('D:/repo/src/pages/publish/components/Table.vue', 'publish'), 'publish-page')
// vue 依赖进入 vendor chunk
assert.equal(planner.assign('D:/repo/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'publish'), 'vue-vendor')
// 未匹配模块不强制拆包,交给 Vite 默认处理
assert.equal(planner.assign('D:/repo/src/main.ts', 'publish'), undefined)
const stats = planner.stats()
assert.equal(stats.entryCount, 3)
assert.equal(stats.chunkCount, 6)
assert.equal(stats.sharedChunkCount, 3)
assert.equal(stats.unmatchedCount, 1)
})
test('test_task_091_chunk_normal_multiple_items', () => {
const entries = ['publish', 'dedupe', 'convert', 'split', 'delete-brand']
const planner = createChunkPlanner({
entries,
vendorRules: [{ chunk: 'element-plus', patterns: ['element-plus'] }],
perEntryRules: entries.map((entry) => ({
entry,
chunk: `${entry}-page`,
patterns: [`/src/pages/${entry}/`],
})),
})
// 批量分配:多个模块同一 chunk,结果不丢失且顺序稳定
for (let i = 0; i < 3; i++) {
assert.equal(planner.assign(`D:/repo/node_modules/element-plus/es/components/table/index.mjs`, entries[i]), 'element-plus')
}
for (const entry of entries) {
assert.equal(planner.assign(`D:/repo/src/pages/${entry}/index.vue`, entry), `${entry}-page`)
}
const plan = planner.planByEntry()
assert.equal(plan.length, 5)
assert.equal(plan[0].entry, 'publish')
assert.equal(plan[0].chunks[0], 'element-plus')
assert.equal(plan[0].chunks[1], 'publish-page')
assert.equal(plan[4].chunks[1], 'delete-brand-page')
// 每入口均含 element-plus + 自己的页面 chunk
for (const item of plan) {
assert.ok(item.chunks.includes('element-plus'))
assert.ok(item.chunks.includes(`${item.entry}-page`))
assert.equal(item.chunks.length, 2)
}
})
test('test_task_091_chunk_normal_repeated_operation_is_idempotent', () => {
const options = {
entries: ['publish', 'dedupe'],
vendorRules: [
{ chunk: 'element-plus', patterns: ['element-plus'] },
{ chunk: 'shared', patterns: ['/src/shared/'] },
],
perEntryRules: [
{ entry: 'publish', chunk: 'publish-page', patterns: ['/src/pages/publish/'] },
],
}
const planner = createChunkPlanner(options)
const once = planner.manualChunksConfig()
const twice = planner.manualChunksConfig()
assert.deepEqual(once, twice, '重复生成配置结果一致')
// 重复登记同 chunk 同名规则:合并去重,不产生重复 chunk
const merged = createChunkPlanner({
entries: ['publish', 'dedupe'],
vendorRules: [
{ chunk: 'element-plus', patterns: ['element-plus'] },
{ chunk: 'element-plus', patterns: ['@element-plus'] },
{ chunk: 'shared', patterns: ['/src/shared/'] },
],
perEntryRules: [
{ entry: 'publish', chunk: 'publish-page', patterns: ['/src/pages/publish/'] },
],
})
assert.deepEqual(merged.manualChunksConfig()['element-plus'].sort(), ['@element-plus', 'element-plus'])
assert.equal(merged.stats().chunkCount, 3)
// 重复 assign 同一模块:结果不变,命中统计幂等
assert.equal(planner.assign('x/node_modules/element-plus/a.js', 'publish'), 'element-plus')
assert.equal(planner.assign('x/node_modules/element-plus/a.js', 'publish'), 'element-plus')
assert.equal(planner.stats().unmatchedCount, 0)
})
test('test_task_091_chunk_boundary_empty_input', () => {
const planner = createChunkPlanner({ entries: [] })
assert.deepEqual(planner.manualChunksConfig(), {}, '无入口产空配置')
assert.deepEqual(planner.planByEntry(), [])
assert.equal(planner.stats().entryCount, 0)
assert.equal(planner.stats().chunkCount, 0)
assert.equal(planner.assign('x/module.js', 'publish'), undefined)
})
test('test_task_091_chunk_boundary_single_item', () => {
const planner = createChunkPlanner({
entries: ['withdraw'],
vendorRules: [{ chunk: 'element-plus', patterns: ['element-plus'] }],
perEntryRules: [{ entry: 'withdraw', chunk: 'withdraw-page', patterns: ['/src/pages/withdraw/'] }],
})
assert.equal(planner.assign('/repo/node_modules/element-plus/es/index.mjs', 'withdraw'), 'element-plus')
assert.equal(planner.assign('/repo/src/pages/withdraw/index.vue', 'withdraw'), 'withdraw-page')
assert.equal(planner.assign('/repo/src/pages/withdraw/index.vue', 'publish'), undefined, '入口不匹配时页面规则不生效')
const plan = planner.planByEntry()
assert.equal(plan.length, 1)
assert.equal(plan[0].chunks.length, 2)
})
test('test_task_091_chunk_boundary_limit_and_overflow', () => {
const planner = createChunkPlanner({
entries: ['a', 'b', 'c'],
vendorRules: [1, 2, 3, 4, 5].map((n) => ({ chunk: `vendor-${n}`, patterns: [`/vendor-${n}/`] })),
perEntryRules: [
{ entry: 'a', chunk: 'a-page', patterns: ['/src/pages/a/'] },
{ entry: 'b', chunk: 'b-page', patterns: ['/src/pages/b/'] },
{ entry: 'c', chunk: 'c-page', patterns: ['/src/pages/c/'] },
],
maxRules: 3,
maxEntries: 2,
})
const stats = planner.stats()
assert.equal(stats.chunkCount, 5, '规则超限后剩余规则被拒绝')
assert.equal(stats.rejectedCount, 2, 'vendor-4/vendor-5 被拒绝')
assert.equal(stats.entryCount, 2, '入口超过 maxEntries 只登记前 2 个')
assert.equal(planner.assign('x/vendor-1/a.js'), 'vendor-1')
assert.equal(planner.assign('x/vendor-4/a.js'), undefined, '被拒绝的规则不再生效')
const plan = planner.planByEntry()
assert.equal(plan.length, 2)
})
test('test_task_091_chunk_invalid_input_rejected', () => {
assert.throws(() => createChunkPlanner({ entries: 'publish' as never }), /entries 必须是数组/)
assert.throws(
() => createChunkPlanner({ entries: ['a'], vendorRules: [{ chunk: '', patterns: ['x'] }] }),
/chunk 名不能为空/,
)
assert.throws(
() => createChunkPlanner({ entries: ['a'], vendorRules: [{ chunk: 'v', patterns: [] }] }),
/patterns 必须是非空数组/,
)
assert.throws(
() => createChunkPlanner({ entries: ['a'], vendorRules: [{ chunk: 'v', patterns: 'x' as never }] }),
/patterns 必须是非空数组/,
)
assert.throws(() => createChunkPlanner({ entries: ['a'], maxRules: 0 }), /maxRules 必须为正数/)
assert.throws(() => createChunkPlanner({ entries: ['a'], maxEntries: -1 }), /maxEntries 必须为正数/)
assert.throws(
() => createChunkPlanner({ entries: ['a'], perEntryRules: [{ entry: '', chunk: 'x', patterns: ['y'] }] }),
/entry 名不能为空/,
)
})
test('test_task_091_chunk_dependency_failure_releases_resources', () => {
let broken = true
const poisonedPatterns = new Proxy(['element-plus'], {
get(target, prop, receiver) {
if (broken && prop === '0') throw new Error('pattern getter down')
return Reflect.get(target, prop, receiver)
},
ownKeys(target) {
if (broken) throw new Error('pattern ownKeys down')
return Reflect.ownKeys(target)
},
})
// 依赖(规则读取)失败:构造期抛错,规划器不产生部分状态
assert.throws(
() =>
createChunkPlanner({
entries: ['publish'],
vendorRules: [{ chunk: 'element-plus', patterns: poisonedPatterns as unknown as string[] }],
}),
/pattern/,
)
// 错误可恢复:依赖恢复后同一配置成功
broken = false
const planner = createChunkPlanner({
entries: ['publish'],
vendorRules: [{ chunk: 'element-plus', patterns: poisonedPatterns as unknown as string[] }],
})
assert.equal(planner.assign('/repo/node_modules/element-plus/a.js', 'publish'), 'element-plus')
assert.deepEqual(planner.manualChunksConfig()['element-plus'], ['element-plus'])
assert.equal(planner.stats().rejectedCount, 0, '失败不产生拒绝计数')
assert.equal(planner.stats().chunkCount, 1)
})