feat: 🚀 打包配置优化

This commit is contained in:
bunny 2024-05-28 10:22:20 +08:00
parent 47e0b8d504
commit 4a12220476
11 changed files with 17085 additions and 16521 deletions

50
build/buildEnv.ts Normal file
View File

@ -0,0 +1,50 @@
import type { BuildOptions } from 'vite';
export const buildEnvironment = () => {
const environment: BuildOptions = {
assetsInlineLimit: 20000,
// 构建输出的目录,默认值为"dist"
outDir: 'dist',
// 用于指定使用的代码压缩工具。在这里minify 被设置为 'terser',表示使用 Terser 进行代码压缩。默认值terser
// esbuild 打包更快,但是不能去除 console.logterser打包慢但能去除 console.log
minify: 'terser',
// 用于配置 Terser 的选项
terserOptions: {
// 用于配置压缩选项
compress: {
drop_console: true, // 是否删除代码中的 console 语句, 默认值false
drop_debugger: true, // 是否删除代码中的 debugger 语句, 默认值false
},
},
// 禁用 gzip 压缩大小报告,可略微减少打包时间
reportCompressedSize: false,
// 用于指定是否生成源映射文件。源映射文件可以帮助调试和定位源代码中的错误。当设置为false时构建过程不会生成源映射文件
sourcemap: false,
// 用于配置 CommonJS 模块的选项
commonjsOptions: {
// 用于指定是否忽略 CommonJS 模块中的 try-catch 语句。当设置为false时构建过程会保留 CommonJS 模块中的 try-catch 语句
ignoreTryCatch: false,
},
// 规定触发警告的 chunk 大小, 当某个代码分块的大小超过该限制时Vite 会发出警告
chunkSizeWarningLimit: 2000,
// 用于配置 Rollup 打包工具的选项
rollupOptions: {
// 用于配置输出选项
output: {
// 静态资源分类和包装
chunkFileNames: 'js/[name]-[hash].js', // 用于指定代码分块的输出文件名格式
entryFileNames: 'js/[name]-[hash].js', // 用于指定入口文件的输出文件名格式
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]', // 用于指定静态资源的输出文件名格式
// ? 配置文件生成方式
manualChunks: (id, meta) => {
// 如果是包含在包中则打包成 vendor
if (id.includes('node_modules')) {
return 'vendor';
}
},
},
},
};
return environment;
};

55
build/cdn.ts Normal file
View File

@ -0,0 +1,55 @@
import { Plugin as importToCDN } from "vite-plugin-cdn-import";
/**
* @description `cdn`使cdn模式 .env.production VITE_CDN true
* cdnhttps://www.bootcdn.cn当然你也可以选择 https://unpkg.com 或者 https://www.jsdelivr.com
* 使jscss文件cdn
*/
export const cdn = importToCDN({
//prodUrl解释 name: 对应下面modules的nameversion: 自动读取本地package.json中dependencies依赖中对应包的版本号path: 对应下面modules的path当然也可写完整路径会替换prodUrl
// prodUrl: 'https://cdn.bootcdn.net/ajax/libs/{name}/{version}/{path}',
prodUrl: 'https://unpkg.com/{name}@{version}/{path}',
modules: [
{
name: 'vue',
var: 'Vue',
path: 'dist/vue.global.js',
},
{
name: 'vue-router',
var: 'VueRouter',
path: 'dist/vue-router.global.js',
},
{
name: 'vue-i18n',
var: 'VueI18n',
path: 'dist/vue-i18n.global.js',
},
// 项目中没有直接安装vue-demi但是pinia用到了所以需要在引入pinia前引入vue-demihttps://github.com/vuejs/pinia/blob/v2/packages/pinia/package.json#L77
{
name: 'vue-demi',
var: 'VueDemi',
path: 'lib/index.iife.js',
},
{
name: 'pinia',
var: 'Pinia',
path: 'dist/pinia.iife.js',
},
{
name: 'axios',
var: 'axios',
path: 'dist/axios.min.js',
},
{
name: 'dayjs',
var: 'dayjs',
path: 'dayjs.min.js',
},
{
name: 'echarts',
var: 'echarts',
path: 'dist/echarts.js',
},
],
});

57
build/compress.ts Normal file
View File

@ -0,0 +1,57 @@
import type { Plugin } from 'vite';
import { isArray } from '@pureadmin/utils';
import compressPlugin from 'vite-plugin-compression';
export const configCompressPlugin = (compress: ViteCompression): Plugin | Plugin[] => {
if (compress === 'none') return null;
const gz = {
// 生成的压缩包后缀
ext: '.gz',
// 体积大于threshold才会被压缩
threshold: 0,
// 默认压缩.js|mjs|json|css|html后缀文件设置成true压缩全部文件
filter: () => true,
// 压缩后是否删除原始文件
deleteOriginFile: false,
};
const br = {
ext: '.br',
algorithm: 'brotliCompress',
threshold: 0,
filter: () => true,
deleteOriginFile: false,
};
const codeList = [
{ k: 'gzip', v: gz },
{ k: 'brotli', v: br },
{ k: 'both', v: [gz, br] },
];
const plugins: Plugin[] = [];
codeList.forEach(item => {
if (compress.includes(item.k)) {
if (compress.includes('clear')) {
if (isArray(item.v)) {
item.v.forEach(vItem => {
plugins.push(compressPlugin(Object.assign(vItem, { deleteOriginFile: true })));
});
} else {
plugins.push(compressPlugin(Object.assign(item.v, { deleteOriginFile: true })));
}
} else {
if (isArray(item.v)) {
item.v.forEach(vItem => {
plugins.push(compressPlugin(vItem));
});
} else {
plugins.push(compressPlugin(item.v));
}
}
}
});
return plugins;
};

53
build/info.ts Normal file
View File

@ -0,0 +1,53 @@
import type { Plugin } from 'vite';
import { getPackageSize } from './utils';
import dayjs, { type Dayjs } from 'dayjs';
import duration from 'dayjs/plugin/duration';
import gradientString from 'gradient-string';
import boxen, { type Options as BoxenOptions } from 'boxen';
dayjs.extend(duration);
const welcomeMessage = gradientString('cyan', 'magenta').multiline(
`您好! 欢迎使用 bunny-admin 后台管理
访
http://localhost:8202/
访
http://localhost:8202/`,
);
const boxenOptions: BoxenOptions = {
padding: 0.5,
borderColor: 'cyan',
borderStyle: 'round',
};
export function viteBuildInfo(): Plugin {
let config: { command: string };
let startTime: Dayjs;
let endTime: Dayjs;
let outDir: string;
return {
name: 'vite:buildInfo',
configResolved(resolvedConfig) {
config = resolvedConfig;
outDir = resolvedConfig.build?.outDir ?? 'dist';
},
buildStart() {
console.log(boxen(welcomeMessage, boxenOptions));
if (config.command === 'build') {
startTime = dayjs(new Date());
}
},
closeBundle() {
if (config.command === 'build') {
endTime = dayjs(new Date());
getPackageSize({
folder: outDir,
callback: (size: string) => {
console.log(boxen(gradientString('cyan', 'magenta').multiline(`🎉 恭喜打包完成(总用时${dayjs.duration(endTime.diff(startTime)).format('mm分ss秒')},打包后的大小为${size}`), boxenOptions));
},
});
}
},
};
}

59
build/optimize.ts Normal file
View File

@ -0,0 +1,59 @@
/**
* `vite.config.ts` `optimizeDeps.include`
* `vite` include esm node_modules/.vite
* include里vite 使 node_modules/.vite
* 使 src/main.ts include vite node_modules/.vite
*/
const include = [
'qs',
'mitt',
'xlsx',
'dayjs',
'axios',
'pinia',
'typeit',
'swiper',
'qrcode',
'intro.js',
'vue-i18n',
'vxe-table',
'vue-types',
'js-cookie',
'vue-tippy',
'cropperjs',
'jsbarcode',
'pinyin-pro',
'sortablejs',
'swiper/vue',
'mint-filter',
'@vueuse/core',
'vue3-danmaku',
'v-contextmenu',
'vue-pdf-embed',
'wavesurfer.js',
'swiper/modules',
'china-area-data',
'vue-json-pretty',
'@logicflow/core',
'@pureadmin/utils',
'@wangeditor/editor',
'responsive-storage',
'plus-pro-components',
'@howdyjs/mouse-menu',
'@logicflow/extension',
'vue-virtual-scroller',
'@amap/amap-jsapi-loader',
'el-table-infinite-scroll',
'vue-waterfall-plugin-next',
'@infectoone/vue-ganttastic',
'@wangeditor/editor-for-vue',
'vuedraggable/src/vuedraggable',
];
/**
*
* `@iconify-icons/` `exclude` 使
*/
const exclude = ['@iconify-icons/ep', '@iconify-icons/ri', '@pureadmin/theme/dist/browser-utils'];
export { include, exclude };

43
build/plugins.ts Normal file
View File

@ -0,0 +1,43 @@
import { cdn } from './cdn';
import vue from '@vitejs/plugin-vue';
import type { PluginOption } from 'vite';
import vueJsx from '@vitejs/plugin-vue-jsx';
import legacy from '@vitejs/plugin-legacy';
import { compression } from 'vite-plugin-compression2';
import { viteMockServe } from 'vite-plugin-mock';
import { viteBuildInfo } from './info';
export function getPluginsList(): PluginOption[] {
const lifecycle = process.env.npm_lifecycle_event;
return [
vue(),
legacy({
targets: ['android 4', 'ios 8', 'chrome 30', 'ie 6'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
renderLegacyChunks: true,
polyfills: [
'es.symbol',
'es.array.filter',
'es.promise',
'es.promise.finally',
'es/map',
'es/set',
'es.array.for-each',
'es.object.define-properties',
'es.object.define-property',
'es.object.get-own-property-descriptor',
'es.object.get-own-property-descriptors',
'es.object.keys',
'es.object.to-string',
'web.dom-collections.for-each',
'esnext.global-this',
'esnext.string.match-all',
],
}),
vueJsx(),
compression(),
viteBuildInfo(),
cdn,
viteMockServe({ mockPath: 'src/mock' }),
];
}

24
build/server.ts Normal file
View File

@ -0,0 +1,24 @@
import { type ServerOptions } from 'vite';
export const serverOptions = () => {
const options: ServerOptions = {
port: 6261,
host: '0.0.0.0',
open: true,
cors: true,
proxy: {
'/api': {
target: process.env.BUNNY_APP_URL,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api/, '/api'),
},
'/mock': {
target: process.env.BUNNY_APP_URL,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/mock/, '/mock'),
},
},
};
return options;
};

33
build/utils.ts Normal file
View File

@ -0,0 +1,33 @@
import { readdir, stat } from 'node:fs';
import { formatBytes, sum } from '@pureadmin/utils';
const fileListTotal: number[] = [];
/** 获取指定文件夹中所有文件的总大小 */
const getPackageSize = options => {
const { folder = 'dist', callback, format = true } = options;
readdir(folder, (err, files: string[]) => {
if (err) throw err;
let count = 0;
const checkEnd = () => {
++count == files.length && callback(format ? formatBytes(sum(fileListTotal)) : sum(fileListTotal));
};
files.forEach((item: string) => {
stat(`${folder}/${item}`, async (err, stats) => {
if (err) throw err;
if (stats.isFile()) {
fileListTotal.push(stats.size);
checkEnd();
} else if (stats.isDirectory()) {
getPackageSize({
folder: `${folder}/${item}/`,
callback: checkEnd,
});
}
});
});
files.length === 0 && callback(0);
});
};
export { getPackageSize };

33104
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,15 +20,19 @@
"commit": "git pull && git add -A && git-cz && git push"
},
"dependencies": {
"@pureadmin/utils": "^2.4.7",
"@vitejs/plugin-legacy": "^5.4.0",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vitejs/plugin-vue2-jsx": "^1.1.1",
"axios": "^1.6.7",
"boxen": "^7.1.1",
"compression-webpack-plugin": "^11.1.0",
"core-js": "^3.36.0",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
"dotenv": "^16.4.5",
"echarts": "^5.5.0",
"gradient-string": "^2.0.2",
"lodash": "^4.17.21",
"mitt": "^3.0.1",
"moment": "^2.30.1",

View File

@ -1,101 +1,29 @@
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
import { defineConfig, UserConfig } from "vite";
import legacy from "@vitejs/plugin-legacy";
import vueJsx from "@vitejs/plugin-vue2-jsx";
import { compression } from "vite-plugin-compression2";
import cdn from "vite-plugin-cdn-import";
import { viteMockServe } from "vite-plugin-mock";
import { resolve } from 'path';
import { defineConfig, UserConfig } from 'vite';
import { buildEnvironment } from './build/buildEnv';
import { exclude, include } from './build/optimize';
import { getPluginsList } from './build/plugins';
import { serverOptions } from './build/server';
export default defineConfig(
(): UserConfig => ({
// base: './', // ? 在每个文件前加上这个前缀
// publicDir: './static',// ? 设置静态资源目录
envPrefix: "BUNNY",
resolve: {
alias: {
"@": resolve(__dirname, "./src"),
"vue-i18n": "vue-i18n/dist/vue-i18n.cjs.js"
}
},
server: {
host: "0.0.0.0",
port: 6261,
open: true,
cors: true,
proxy: {
"/api": {
target: process.env.BUNNY_APP_URL,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api/, "/api")
},
"/mock": {
target: process.env.BUNNY_APP_URL,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/mock/, "/mock")
}
}
},
plugins: [
vue(),
legacy({ targets: ["defaults", "not IE 11"] }),
vueJsx(),
compression(),
cdn({
modules: []
}),
viteMockServe({ mockPath: "src/mock" })
],
esbuild: {
pure: ["console.log", "debugger"],
jsxFactory: "h",
jsxFragment: "Fragment",
jsxInject: "import { h } from 'vue';"
},
// 配置构建过程的选项,例如是否生成压缩文件和源映射
build: {
assetsInlineLimit: 20000,
// 构建输出的目录,默认值为"dist"
outDir: "dist",
// 用于指定使用的代码压缩工具。在这里minify 被设置为 'terser',表示使用 Terser 进行代码压缩。默认值terser
// esbuild 打包更快,但是不能去除 console.logterser打包慢但能去除 console.log
minify: "terser",
// 用于配置 Terser 的选项
terserOptions: {
// 用于配置压缩选项
compress: {
drop_console: true, // 是否删除代码中的 console 语句, 默认值false
drop_debugger: true // 是否删除代码中的 debugger 语句, 默认值false
}
},
// 禁用 gzip 压缩大小报告,可略微减少打包时间
reportCompressedSize: false,
// 用于指定是否生成源映射文件。源映射文件可以帮助调试和定位源代码中的错误。当设置为false时构建过程不会生成源映射文件
sourcemap: false,
// 用于配置 CommonJS 模块的选项
commonjsOptions: {
// 用于指定是否忽略 CommonJS 模块中的 try-catch 语句。当设置为false时构建过程会保留 CommonJS 模块中的 try-catch 语句
ignoreTryCatch: false
},
// 规定触发警告的 chunk 大小, 当某个代码分块的大小超过该限制时Vite 会发出警告
chunkSizeWarningLimit: 2000,
// 用于配置 Rollup 打包工具的选项
rollupOptions: {
// 用于配置输出选项
output: {
// 静态资源分类和包装
chunkFileNames: "js/[name]-[hash].js", // 用于指定代码分块的输出文件名格式
entryFileNames: "js/[name]-[hash].js", // 用于指定入口文件的输出文件名格式
assetFileNames: "assets/[ext]/[name]-[hash].[ext]", // 用于指定静态资源的输出文件名格式
// ? 配置文件生成方式
manualChunks: (id, meta) => {
// 如果是包含在包中则打包成 vendor
if (id.includes("node_modules")) {
return "vendor";
}
}
}
}
}
})
(): UserConfig => ({
envPrefix: 'BUNNY',
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js',
},
},
optimizeDeps: { include, exclude },
server: serverOptions(),
plugins: getPluginsList(),
esbuild: {
pure: ['console.log', 'debugger'],
jsxFactory: 'h',
jsxFragment: 'Fragment',
jsxInject: "import { h } from 'vue';",
},
// 配置构建过程的选项,例如是否生成压缩文件和源映射
build: buildEnvironment(),
}),
);