vite对项目进行手动分包
记录一个在开发过程遇到的需求,对自己的项目build打包进行手动分包
项目概述:在一个vue项目中可能有多个不关联的模块内容,希望在build打包发布的时候对项目代码进行手动分包(按模块分)
要求:
1.希望项目打包之后按模块手动分包
项目大致结构
src -- 项目主要文件
└── view -- 页面文件
├── common -- 系统通用部分
├── page1 -- 业务代码1
├── page2 -- 业务代码2
├── page3 -- 业务代码3
├── module1 -- 模块1
├── page1 -- 业务代码1
├── page2 -- 业务代码2
├── page3 -- 业务代码3
├── module2 -- 模块2
├── module3 -- 模块3
└── api -- 接口请求文档
└── components -- 自定义组件文件夹
└── theme -- 样式文件
2.按模块打包之后,若某个模块下的代码修改之后重新打包后,生成的dist文件中的文件指纹进行变更,其他未修改的文件保持不变
3. 在进行build打包的时候可以指定模块进行增量打包,而不是全量打包
实现过程
实现代码的手动分包+文件hash文件指纹的缓存
import { defineConfig } from 'vite';
import { createHash } from 'crypto';
import { readFileSync, existsSync } from 'fs';
import { basename, extname, resolve } from 'path';
// 安全的文件内容读取函数
const getFileContent = (filePath) => {
try {
return existsSync(filePath) ? readFileSync(filePath, 'utf-8') : '';
} catch (e) {
console.warn(`Failed to read ${filePath}:`, e.message);
return '';
}
};
// 生成内容hash
const generateContentHash = (content, length = 8) => {
return createHash('sha256')
.update(content)
.digest('hex')
.substring(0, length);
};
plugins: [
{
name: 'file-content-hash',
enforce: 'post',
generateBundle(options, bundle) {
const distDir = options.dir || 'dist';
Object.entries(bundle).forEach(([fileName, chunk]) => {
if (chunk.type === 'chunk' || chunk.type === 'asset') {
const filePath = resolve(process.cwd(), distDir, fileName);
const newContent = chunk.type === 'chunk' ? chunk.code : chunk.source;
if (existsSync(filePath)) {
const existingContent = readFileSync(filePath, 'utf-8');
if (existingContent === newContent) {
delete bundle[fileName]; // 保留已存在的相同文件
}
}
}
});
}
}
]
build: {
outDir: 'dist', // 打包输出目录
chunkSizeWarningLimit: 1500, // 代码分包阈值
rollupOptions: {
// 确保相同模块总是生成相同的 chunk
hoistTransitiveImports: false,
preserveEntrySignatures: 'strict',
// 更稳定的模块 ID
generatedCode: {
preset: 'es2015'
},
output: {
// compact: true,
manualChunks(id) {
//所有第三方插件打包在一个文件内
if (id.includes('node_modules')) {
return 'vendor';
}
//1和2选一个使用
// 1.modules1下所有的业务代码都放在modules1文件夹下
if (id.includes('src/views/modules1')) {
const match = id.match(/src\/views\/iotgw_modules\/([^\/]+)/)
return match ? `modules1/${match[1]}` : undefined
}
// 2.按views下一级目录拆分打包
if (id.includes('src/views/')) {
// 按模块拆分
const moduleMatch = id.match(/src\/views\/([^\/]+)/);
if (moduleMatch) {
return `views-${moduleMatch[1]}`;
}
return 'views-other';
}
return undefined
},
// JS文件命名规则(基于内容hash)
chunkFileNames: (chunkInfo) => {
const content = chunkInfo.moduleIds
.map(id => getFileContent(id))
.join('');
const hash = generateContentHash(content);
return `assets/${chunkInfo.name}-${hash}.js`;
},
// 入口文件命名规则
entryFileNames: (chunkInfo) => {
const content = chunkInfo.moduleIds
.map(id => getFileContent(id))
.join('');
const hash = generateContentHash(content);
return `assets/${chunkInfo.name}-${hash}.js`;
},
// 资源文件命名规则(CSS/图片等)
assetFileNames: ({ name }) => {
const filePath = resolve(process.cwd(), name);
const content = getFileContent(filePath);
const hash = content ? generateContentHash(content) : '[hash]';
const ext = extname(name).slice(1);
const baseName = basename(name, `.${ext}`);
return `assets/${baseName}-${hash}.${ext}`;
},
},
},
},
实现过程
对指定模块进行增量打包
例:针对modules1模块打包
1.新建一个vite.iot.config.ts文件
import { mergeConfig } from 'vite';
import baseConfig from './vite.config';
export default mergeConfig(baseConfig, {
build: {
rollupOptions: {
output: {
manualChunks(id: string) {
if (id.includes('src/views/modules1')) return 'modules1';
if (id.includes('node_modules')) return 'vendor';
return undefined;
}
}
}
}
});
2.在package.json文件内配置
"scripts": {
"dev": "vite",
"build:modules1": "cross-env NODE_OPTIONS=--max-old-space-size=8096 vite build --config vite.iot.config.ts",
},
3.运行打包命令
npm run build:modules1