前言
接下来进入项目搭建的重点部分:Vite配置。
本篇内容可能会有些多,耐心看完会有很多收获。
1、在根目录下新建一个build文件夹
虽然vue从很早就使用vue.config.js或者vite.config.ts来进行打包配置,但在webpick时期,似乎是用build文件夹来进行打包配置的,所以这里也使用build文件夹。
先看一下需要进行的打包配置,后面我会逐个文件的解释。
2、vite配置
先把vite.config.ts文件放出来,后面再一项一项解析
// vite.config.ts
import { ConfigEnv, loadEnv, UserConfig } from 'vite';
import { resolve } from 'path';
import pkg from './package.json';
import dayjs from 'dayjs';
import { exclude, include } from './build/optimize';
import { createProxy } from './build/vite/proxy';
import { wrapperEnv } from './build/utils';
import { createVitePlugins } from './build/vite/plugins';
import { OUTPUT_DIR } from './build/constant';
import { generateModifyVars } from './build/generate/generateModifyVars';
function pathResolve(dir: string) {
return resolve(process.cwd(), '.', dir);
}
const { dependencies, devDependencies, name, version } = pkg;
const __APP_INFO__ = {
pkg: { dependencies, devDependencies, name, version },
// lastBuildTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
lastBuildTime: dayjs().unix(),
};
export default ({ command, mode }: ConfigEnv): UserConfig => {
const root = process.cwd();
const env = loadEnv(mode, root);
const viteEnv = wrapperEnv(env);
const { VITE_PORT, VITE_PUBLIC_PATH, VITE_PROXY, VITE_DROP_CONSOLE } = viteEnv;
const isBuild = command === 'build';
return {
base: VITE_PUBLIC_PATH,
root,
resolve: {
alias: [
// // 避免vue-i18n警告
// {
// find: 'vue-i18n',
// replacement: 'vue-i18n/dist/vue-i18n.cjs.js',
// },
// /@/xxxx => src/xxxx
{
find: /\/@\//,
replacement: pathResolve('src') + '/',
},
// /#/xxxx => types/xxxx
{
find: /\/#\//,
replacement: pathResolve('types') + '/',
},
],
},
server: {
// 监听所有本地IP
host: true,
port: VITE_PORT,
// 从.env加载代理配置
proxy: createProxy(VITE_PROXY),
},
esbuild: {
// 从.env加载no_console
drop: VITE_DROP_CONSOLE ? ['console', 'debugger'] : [],
},
build: {
sourcemap: true,
// 兼容原生ESM的浏览器
target: 'es2015',
// 兼容移动端css样式
cssTarget: 'chrome80',
// 生成打包文件名称 dist
outDir: OUTPUT_DIR,
// 不报告压缩大小
reportCompressedSize: false,
// 消除打包大小超过500kb警告
chunkSizeWarningLimit: 4000,
rollupOptions: {
output: {
// 用于从入口点创建的块的打包输出格式[name]表示文件名,[hash]表示该文件内容hash值
entryFileNames: 'js/[name].[hash].js',
// 用于命名代码拆分时创建的共享块的输出命名
chunkFileNames: 'js/[name].[hash].js',
// 用于输出静态资源的命名,[ext]表示文件扩展名
assetFileNames: (assetInfo: any) => {
const info = assetInfo.name.split('.');
let extType = info[info.length - 1];
if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(assetInfo.name)) {
extType = 'media';
} else if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(assetInfo.name)) {
extType = 'img';
} else if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(assetInfo.name)) {
extType = 'fonts';
}
return `${extType}/[name].[hash].[ext]`;
},
},
// 这一部分等后续nginx熟练了再启用
// output: {
// // 最小化拆分包
// manualChunks: (id) => {
// if (id.includes('node_modules')) {
// return id.toString().split('node_modules/')[1].split('/')[0].toString();
// }
// },
// // 用于从入口点创建的块的打包输出格式[name]表示文件名,[hash]表示该文件内容hash值
// entryFileNames: `${OUTPUT_DIR}/js/[name].[hash].js`,
// // 用于命名代码拆分时创建的共享块的输出命名
// chunkFileNames: `${OUTPUT_DIR}/js/[name].[hash].js`,
// // 用于输出静态资源的命名,[ext]表示文件扩展名
// assetFileNames: `${OUTPUT_DIR}/[ext]/[name].[hash].[ext]`,
// },
},
},
define: {
// 定义全局变量替换方式
__APP_INFO__: JSON.stringify(__APP_INFO__),
},
css: {
preprocessorOptions: {
scss: {
additionalData: generateModifyVars(),
},
},
},
// 插件
plugins: createVitePlugins(viteEnv, isBuild),
// 依赖预构建配置项
optimizeDeps: {
include,
exclude,
},
};
};
全部粘贴过去以后会有很多爆红,没事先都注释掉,后面逐项放开。
// vite.config.ts
// import { ConfigEnv, loadEnv, UserConfig } from 'vite';
import { UserConfig } from 'vite';
import { resolve } from 'path';
import pkg from './package.json';
// import dayjs from 'dayjs';
// import { exclude, include } from './build/optimize';
// import { createProxy } from './build/vite/proxy';
// import { wrapperEnv } from './build/utils';
// import { createVitePlugins } from './build/vite/plugins';
// import { OUTPUT_DIR } from './build/constant';
// import { generateModifyVars } from './build/generate/generateModifyVars';
function pathResolve(dir: string) {
return resolve(process.cwd(), '.', dir);
}
const { dependencies, devDependencies, name, version } = pkg;
const __APP_INFO__ = {
pkg: { dependencies, devDependencies, name, version },
// lastBuildTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
// lastBuildTime: dayjs().unix(),
};
export default (): UserConfig => {
// export default ({ command, mode }: ConfigEnv): UserConfig => {
const root = process.cwd();
// const env = loadEnv(mode, root);
// const viteEnv = wrapperEnv(env);
// const { VITE_PORT, VITE_PUBLIC_PATH, VITE_PROXY, VITE_DROP_CONSOLE } = viteEnv;
// const isBuild = command === 'build';
return {
// base: VITE_PUBLIC_PATH,
root,
resolve: {
alias: [
// 避免vue-i18n警告
{
find: 'vue-i18n',
replacement: 'vue-i18n/dist/vue-i18n.cjs.js',
},
// /@/xxxx => src/xxxx
{
find: /\/@\//,
replacement: pathResolve('src') + '/',
},
// /#/xxxx => types/xxxx
{
find: /\/#\//,
replacement: pathResolve('types') + '/',
},
],
},
server: {
// 监听所有本地IP
host: true,
// port: VITE_PORT,
// 从.env加载代理配置
// proxy: createProxy(VITE_PROXY),
},
esbuild: {
// 从.env加载no_console
// drop: VITE_DROP_CONSOLE ? ['console', 'debugger'] : [],
},
build: {
sourcemap: true,
// 兼容原生ESM的浏览器
target: 'es2015',
// 兼容移动端css样式
cssTarget: 'chrome80',
// 生成打包文件名称 dist
// outDir: OUTPUT_DIR,
// 不报告压缩大小
reportCompressedSize: false,
// 消除打包大小超过500kb警告
chunkSizeWarningLimit: 4000,
rollupOptions: {
output: {
// 用于从入口点创建的块的打包输出格式[name]表示文件名,[hash]表示该文件内容hash值
entryFileNames: 'js/[name].[hash].js',
// 用于命名代码拆分时创建的共享块的输出命名
chunkFileNames: 'js/[name].[hash].js',
// 用于输出静态资源的命名,[ext]表示文件扩展名
assetFileNames: (assetInfo: any) => {
const info = assetInfo.name.split('.');
let extType = info[info.length - 1];
if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(assetInfo.name)) {
extType = 'media';
} else if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(assetInfo.name)) {
extType = 'img';
} else if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(assetInfo.name)) {
extType = 'fonts';
}
return `${extType}/[name].[hash].[ext]`;
},
},
// 这一部分等后续nginx熟练了再启用
// output: {
// // 最小化拆分包
// manualChunks: (id) => {
// if (id.includes('node_modules')) {
// return id.toString().split('node_modules/')[1].split('/')[0].toString();
// }
// },
// // 用于从入口点创建的块的打包输出格式[name]表示文件名,[hash]表示该文件内容hash值
// entryFileNames: `${OUTPUT_DIR}/js/[name].[hash].js`,
// // 用于命名代码拆分时创建的共享块的输出命名
// chunkFileNames: `${OUTPUT_DIR}/js/[name].[hash].js`,
// // 用于输出静态资源的命名,[ext]表示文件扩展名
// assetFileNames: `${OUTPUT_DIR}/[ext]/[name].[hash].[ext]`,
// },
},
},
define: {
// 定义全局变量替换方式
__APP_INFO__: JSON.stringify(__APP_INFO__),
},
css: {
preprocessorOptions: {
scss: {
// additionalData: generateModifyVars(),
},
},
},
// 插件
// plugins: createVitePlugins(viteEnv, isBuild),
// 依赖预构建配置项
optimizeDeps: {
// include,
// exclude,
},
};
};
3、scss全局变量-generate文件
在build文件下新建一个generate文件夹,并在里面新建一个generateModifyVars.ts文件用来存放我们的全局变量文件。
// generateModifyVars.ts
/**
* scss全局变量
*/
export function generateModifyVars() {
// 用于全局导入,以避免需要单独导入每个样式文件
return `@use '/@/style/config.scss' as *;`;
}
/@/这个路径前缀后面我们会配置,先这么用着吧,指向的src目录。
按照/@/style/config.scss新建scss全局变量文件config.scss。
// config.scss
// generateModifyVars函数使用@use引用,此处不能再次使用@use引用
// 原因未知
@import './color';
@import './var/index';
// color.scss
html {
// let sider
--sider-dark-bg-color: #273352;
}
$content-bg: #f4f7f9;
$text-color-help-dark: #909399;
$primary-color: #13c2c2;
// let -menu
$sider-dark-bg-color: var(--sider-dark-bg-color);
// var/index.scss
$namespace: hp;
此时可以把vite.config.ts里generateModifyVars()相关的代码放开了。
这里放一些示例。接着我们可以这么用。
4、dayjs
下载dayjs插件,这个没什么好说的。
// package.json
"dayjs": "^1.11.1",
此时可以放开dayjs相关的部分。
5、constant-常量
在build文件里新建constant.ts文件。
// constant.ts
/**
* 在生产环境中输入的配置文件的名称
* 全局常量
*/
export const GLOB_CONFIG_FILE_NAME = '_app.config.js';
export const OUTPUT_DIR = 'dist';
此时output部分可以放开了,注意:// 这一部分等后续nginx熟练了再启用。
建议使用该注释上面的部分。
6、utils-打包需要的一些工具函数
这个文件要仔细看一下,会影响到.env文件的命名、环境变量的前缀和类型、打包预览的生成这些部分。
和constant.ts同级,新建utils文件。
// utils.ts
import fs from 'fs';
import path from 'path';
import dotenv from 'dotenv';
/**
* 对环境变量的类型进行处理
* @param envConf Obj
* @returns
*/
export function wrapperEnv(envConf: Recordable): ViteEnv {
const ret: any = {};
for (const envName of Object.keys(envConf)) {
let realName = envConf[envName].replace(/\\n/g, '\n');
realName = realName === 'true' ? true : realName === 'false' ? false : realName;
if (envName === 'VITE_PORT') {
realName = Number(realName);
}
if (envName === 'VITE_PROXY' && realName) {
try {
realName = JSON.parse(realName.replace(/'/g, '"'));
} catch (error) {
realName = '';
}
}
ret[envName] = realName;
}
return ret;
}
/**
* 是否生成打包预览
*/
export function isReportMode(): boolean {
return process.env.REPORT === 'true';
}
/**
* 获取当前环境下生效的配置文件名
*/
function getConfFiles() {
const script = process.env.npm_lifecycle_script;
const reg = new RegExp('--mode ([a-z_\\d]+)');
const result = reg.exec(script as string) as any;
if (result) {
const mode = result[1] as string;
return ['.env', `.env.${mode}`];
}
return ['.env', '.env.production'];
}
/**
* 获取以指定前缀开头的环境变量
* @param match prefix
* @param confFiles ext
*/
export function getEnvConfig(match = 'VITE_GLOB_', confFiles = getConfFiles()) {
let envConfig = {};
confFiles.forEach((item) => {
try {
const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)));
envConfig = { ...envConfig, ...env };
} catch (e) {
console.error(`Error in parsing ${item}`, e);
}
});
const reg = new RegExp(`^(${match})`);
Object.keys(envConfig).forEach((key) => {
if (!reg.test(key)) {
Reflect.deleteProperty(envConfig, key);
}
});
return envConfig;
}
/**
* 获取用户根目录
* @param dir file path
*/
export function getRootPath(...dir: string[]) {
return path.resolve(process.cwd(), ...dir);
}
这里会有些报错,顺便配置一下全局类型和dotenv插件安装。
// package.json
"dotenv": "^16.0.3",
在根目录下新建types文件夹,里面新建global.d.ts文件,内容如下。
// global.d.ts
declare type Recordable<T = any> = Record<string, T>;
declare interface ViteEnv {
VITE_PORT: number;
VITE_USE_MOCK: boolean;
VITE_USE_PWA: boolean;
VITE_PUBLIC_PATH: string;
VITE_PROXY: [string, string][];
VITE_GLOB_APP_TITLE: string;
VITE_GLOB_APP_SHORT_NAME: string;
VITE_USE_CDN: boolean;
VITE_DROP_CONSOLE: boolean;
VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none';
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean;
VITE_LEGACY: boolean;
VITE_GENERATE_UI: string;
}
const __APP_INFO__: {
pkg: {
name: string;
version: string;
dependencies: Recordable<string>;
devDependencies: Recordable<string>;
};
lastBuildTime: string;
};
declare type Nullable<T> = T | null;
declare type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
};
declare type TimeoutHandle = ReturnType<typeof setTimeout>;
declare type IntervalHandle = ReturnType<typeof setInterval>;
declare module '*.vue' {
import { ComponentOptions } from 'vue';
const componentOptions: ComponentOptions;
export default componentOptions;
}
此时utils文件的报错就ok了。
接着放开./build/utils相关的部分。
// proxy: createProxy(VITE_PROXY),这部分先注释。
这部分会涉及到一些环境变量,先贴一份完整的,可以根据需要改动,建议详细看看配置里涉及到环境变量的部分。
// .env.development
# 是否打开mock
VITE_USE_MOCK = true
# 公共地址
VITE_PUBLIC_PATH = /
# 选择gzip | brotli | none的打包压缩方式
# Optional: gzip | brotli | none
# 可以同时使用两种打包方式,使用,分割即可
VITE_BUILD_COMPRESS = 'none'
# 可以配置多个跨域代理
# 注意没有换行符
VITE_PROXY = [["/devApi/devPrefix","http://localhost:3000"]]
# 是否删除警告
VITE_DROP_CONSOLE = false
# 是否兼容旧的浏览器
VITE_LEGACY = false
# 接口地址
VITE_GLOB_API_URL = /devApi
# 接口前缀
VITE_GLOB_API_URL_PREFIX = /devPrefix
# 文件下载接口地址
VITE_GLOB_UPLOAD_URL = /devUpload
// .env.production
# 是否打开mock
VITE_USE_MOCK = true
# 公共地址
VITE_PUBLIC_PATH = /
# 是否删除警告
VITE_DROP_CONSOLE = true
# 选择gzip | brotli | none的打包压缩方式
# Optional: gzip | brotli | none
# 可以同时使用两种打包方式,使用,分割即可
VITE_BUILD_COMPRESS = 'none'
# 是否兼容旧的浏览器
VITE_LEGACY = false
# 使用压缩时是否删除原始文件,默认为false
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
# 接口地址
VITE_GLOB_API_URL = /proApi
# 接口前缀
VITE_GLOB_API_URL_PREFIX = /proPrefix
# 文件下载接口地址
VITE_GLOB_UPLOAD_URL = /proUpload
7、optimizeDeps-依赖预构建配置项
在build文件夹下,和constant.ts同级,新建optimize.ts文件
// optimize.ts
/**
* 用于 `vite.config.ts` 的 `optimizeDeps.include` 依赖预构建配置项
* 引入到 src/main.ts 里的依赖就不需要再添加到 include了
*/
const include = [];
/**
* 预构建中强制排除的依赖项
*/
const exclude = [];
// const exclude = ['@iconify-icons/ep', '@iconify-icons/ri'];
export { include, exclude };
这部分等后面写到icon再说。
此时可以放开./build/optimize相关的项。
8、plugins-插件
在build文件夹下新建vite文件夹,里面新建plugins文件夹、script文件夹和proxy文件。
8.1、先说proxy文件。
// proxy.ts文件
/**
* 使用 .env.development proxy 配置
*/
import type { ProxyOptions } from 'vite';
type ProxyItem = [string, string];
type ProxyList = ProxyItem[];
type ProxyTargetList = Record<string, ProxyOptions>;
const httpsRE = /^https:\/\//;
/**
* 代理生成
* @param list
*/
export function createProxy(list: ProxyList = []) {
const ret: ProxyTargetList = {};
for (const [prefix, target] of list) {
const isHttps = httpsRE.test(target);
ret[prefix] = {
target: target,
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''),
// https 模式需要 secure=false
...(isHttps ? { secure: false } : {}),
};
}
// 编译时会根据VITE_PROXY生成proxy对象
return ret;
}
在.env.development和.env.production两个文件里查看proxy地址。
地址根据需要可以变动,可以根据实际情况更改createProxy函数。
此时可以放开createProxy(VITE_PROXY)部分。
8.2、script-生成配置文件函数
在这个文件夹下新建两个文件,buildConf.ts和postBuild.ts。
它们是用来生成配置文件的,除此之外还有其他的作用,感兴趣可以深入看一下,反正代码量不多。
// buildConf.ts
/**
* 用于打包时生成其他配置文件。该文件可以配置一些全局变量,这样就可以在不重新打包的情况下直接从外部更改
*/
import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../../constant';
import fs, { writeFileSync } from 'fs-extra';
import colors from 'picocolors';
import { getEnvConfig, getRootPath } from '../../utils';
import { getConfigFileName } from '../../getConfigFileName';
import pkg from '../../../package.json';
interface CreateConfigParams {
// windows对象下的存储配置的属性名
configName: string;
// 存储配置的对象
config: any;
// JS文件名
configFileName?: string;
}
function createConfig(params: CreateConfigParams) {
const { configName, config, configFileName } = params;
try {
// 变量字符串
const windowConf = `window.${configName}`;
// 将配置的对象以JSON字符串格式拼接到变量字符串后面,最后用正则将空格去掉,确保变量不会被修改
let configStr = `${windowConf}=${JSON.stringify(config)};`;
configStr += `
Object.freeze(${windowConf});
Object.defineProperty(window, "${configName}", {
configurable: false,
writable: false,
});
`.replace(/\s/g, '');
// 创建dist文件夹
fs.mkdirp(getRootPath(OUTPUT_DIR));
// 将字符串写入到dist文件下的指定JS文件名的文件中
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr);
console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`);
console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n');
} catch (error) {
console.log(colors.red('configuration file configuration file failed to package:\n' + error));
}
}
export function runBuildConfig() {
// 获取我们可以配置的环境变量对象,默认取VITE_GLOB_开头的环境变量
const config = getEnvConfig();
// 获取配置文件的JS名
const configFileName = getConfigFileName(config);
// 创建文件
createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME });
}
这里可以看到如果启动脚本参数带有disabled-config就不会生成对应的配置参数
// postBuild.ts
import { runBuildConfig } from './buildConf';
import colors from 'picocolors';
import pkg from '../../../package.json';
export const runBuild = async () => {
try {
// 取运行脚本命令里的参数
const argvList = process.argv.splice(2);
if (!argvList.includes('disabled-config')) {
// 同步创建配置文件-
runBuildConfig();
}
console.log(`✨ ${colors.cyan(`[${pkg.name}]`)}` + ' - build successfully!');
} catch (error) {
console.log(colors.red('vite build error:\n' + error));
process.exit(1);
}
};
runBuild();
安装几个插件,在build文件夹下新建getConfigFileName.ts文件。
// package.json
"picocolors": "^1.0.0",
"@types/fs-extra": "^11.0.4",
"fs-extra": "^11.2.0",```
这里控制生成的配置文件名
// getConfigFileName.ts
/**
* 获取配置文件变量名
* @param env
*/
export const getConfigFileName = (env: Record<string, any>) => {
return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`
.toUpperCase()
.replace(/\s/g, '');
};
8.3、plugins-插件
vite提供了很多好用的插件,集中放在这里
vite-plugin-compression,用于打包和输出gzip
// compress.ts
/**
* 用于打包和输出gzip;
* https://github.com/anncwb/vite-plugin-compression
*/
import type { PluginOption } from 'vite';
import compressPlugin from 'vite-plugin-compression';
export function configCompressPlugin(
compress: 'gzip' | 'brotli' | 'none',
deleteOriginFile = false,
): PluginOption | PluginOption[] {
const compressList = compress.split(',');
const plugins: PluginOption[] = [];
if (compressList.includes('gzip')) {
plugins.push(
compressPlugin({
ext: '.gz',
deleteOriginFile,
}),
);
}
if (compressList.includes('brotli')) {
plugins.push(
compressPlugin({
ext: '.br',
algorithm: 'brotliCompress',
deleteOriginFile,
}),
);
}
return plugins;
}
vite-plugin-html,针对 index.html,提供压缩和基于 ejs 模板功能
// html.ts
/**
* 针对 index.html,提供压缩和基于 ejs 模板功能
* https://github.com/anncwb/vite-plugin-html
*/
import type { PluginOption } from 'vite';
import { createHtmlPlugin } from 'vite-plugin-html';
import pkg from '../../../package.json';
import { GLOB_CONFIG_FILE_NAME } from '../../constant';
export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env;
const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`;
const getAppConfigSrc = () => {
return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
};
const htmlPlugin: PluginOption[] = createHtmlPlugin({
// 是否压缩 html
minify: isBuild,
inject: {
// 将数据注入ejs模板
data: {
title: VITE_GLOB_APP_TITLE,
},
// 嵌入生成的app.config.js文件
tags: isBuild
? [
{
tag: 'script',
attrs: {
src: getAppConfigSrc(),
},
},
]
: [],
},
});
return htmlPlugin;
}
vite-plugin-mock,用于开发和生产的mock插件
// mock.ts
/**
* 用于开发和生产的mock插件.
* https://github.com/anncwb/vite-plugin-mock
*/
import { viteMockServe } from 'vite-plugin-mock';
export function configMockPlugin(isBuild: boolean) {
return viteMockServe({
ignore: /^\_/,
mockPath: 'mock',
localEnabled: !isBuild,
prodEnabled: isBuild,
injectCode: `
import { setupProdMockServer } from '../mock/_createProductionServer';
setupProdMockServer();
`,
});
}
rollup-plugin-visualizer,打包构建分析
// visualizer.ts
/**
* 打包构建分析
*/
import visualizer from 'rollup-plugin-visualizer';
import { isReportMode } from '../../utils';
export function configVisualizerConfig() {
if (isReportMode()) {
return visualizer({
filename: './node_modules/.cache/visualizer/stats.html',
open: true,
gzipSize: true,
brotliSize: true,
}) as Plugin;
}
return [];
}
先装插件,再新建文件
// package.json
"mockjs": "^1.1.0",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.0",
"vite-plugin-mock": "2.9.6",
"rollup-plugin-visualizer": "^5.9.0",
vite-plugin-mock插件需要我们新建一个mock文件夹,按照上面路径新建一个
也就是根目录下,里面新建_createProductionServer.ts和_util.ts文件
// _createProductionServer.ts
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
// 问题描述
// 1. `import.meta.globEager` 已被弃用, 需要升级vite版本,有兼容问题
// 2. `vite-plugin-mock` 插件问题 https://github.com/vbenjs/vite-plugin-mock/issues/56
// const modules: Record<string, any> = import.meta.glob("./**/*.ts", {
// import: "default",
// eager: true,
// });
// const mockModules = Object.keys(modules).reduce((pre, key) => {
// if (!key.includes("/_")) {
// pre.push(...modules[key]);
// }
// return pre;
// }, [] as any[]);
const modules = import.meta.glob('./**/*.ts', { eager: true });
const mockModules: any[] = [];
Object.keys(modules).forEach((key) => {
if (key.includes('/_')) {
return;
}
mockModules.push(...(modules as Recordable)[key].default);
});
// const modules = import.meta.globEager('./**/*.ts');
// const mockModules: any[] = [];
// Object.keys(modules).forEach((key) => {
// if (key.includes('/_')) {
// return;
// }
// mockModules.push(...modules[key].default);
// });
/**
* 用于生产环境。需要手动导入所有模块
*/
export function setupProdMockServer() {
createProdMockServer(mockModules);
}
_util.ts是用来设置mock接口返回数据结构模板的
// _util.ts
// 用于返回统一格式的接口数据格式
import { ResultEnum } from '/@/enums/httpEnum';
export function resultSuccess<T = Recordable>(result: T, { message = 'ok' } = {}) {
return {
code: ResultEnum.SUCCESS,
result,
message,
type: 'success',
};
}
export function resultPageSuccess<T = any>(
page: number,
pageSize: number,
list: T[],
{ message = 'ok' } = {},
) {
const pageData = pagination(page, pageSize, list);
return {
...resultSuccess({
items: pageData,
total: list.length,
}),
message,
};
}
export function resultError(
message = 'Request failed',
{ code = ResultEnum.ERROR, result = null } = {},
) {
return {
code,
result,
message,
type: 'error',
};
}
export function pagination<T = any>(pageNo: number, pageSize: number, array: T[]): T[] {
const offset = (pageNo - 1) * Number(pageSize);
return offset + Number(pageSize) >= array.length
? array.slice(offset, array.length)
: array.slice(offset, offset + Number(pageSize));
}
export interface requestParams {
method: string;
body: any;
headers?: { authorization?: string };
query: any;
}
/**
* @description 本函数用于从request数据中获取token,请根据项目的实际情况修改
*
*/
export function getRequestToken({ headers }: requestParams): string | undefined {
return headers?.authorization;
}
// 接口地址baseUrl
export const baseUrl = '/devApi/devPrefix';
/@/enums/httpEnum’是我个人用的数据返回结构,放在这里做示例
src目录下新建enums文件夹
// src/enums/httpEnum.ts
/**
* @description: 请求结果
*/
export enum ResultEnum {
SUCCESS = 200,
ERROR = 500,
TIMEOUT = 504,
TYPE = 'success',
}
/**
* @description: 请求方法
*/
export enum RequestEnum {
GET = 'GET',
POST = 'POST',
PUT = 'PUT',
DELETE = 'DELETE',
}
/**
* @description: contentType
*/
export enum ContentTypeEnum {
// json
JSON = 'application/json;charset=UTF-8',
// form-data qs
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
// form-data upload
FORM_DATA = 'multipart/form-data;charset=UTF-8',
}
直接和_createProductionServer.ts平级新建一个demo文件夹,这里放一些mock数据的示例
// mock/demo/list.ts
import { resultSuccess, resultError, getRequestToken, requestParams, baseUrl } from '../_util';
import { MockMethod } from 'vite-plugin-mock';
const listInfo = {
content: [
{
id: 1,
name: '示例数据1',
createBy: '示例数据创建人1',
},
{
id: 2,
name: '示例数据2',
createBy: '示例数据创建人2',
},
{
id: 3,
name: '示例数据3',
createBy: '示例数据创建人3',
},
{
id: 4,
name: '示例数据4',
createBy: '示例数据创建人4',
},
{
id: 5,
name: '示例数据5',
createBy: '示例数据创建人5',
},
{
id: 6,
name: '示例数据6',
createBy: '示例数据创建人6',
},
],
page: 1,
pageSize: 10,
total: 100,
};
export default [
{
url: `${baseUrl}/getListInfo`,
timeout: 1000,
method: 'get',
response: (request: requestParams) => {
const token = getRequestToken(request);
if (!token) {
return resultError('Invalid token!');
}
return resultSuccess(listInfo);
},
},
] as MockMethod[];
文件夹名可以任意命名,原因可以看_createProductionServer.ts文件
接着来看plugins里面的index.ts文件,这里面也有一些vite插件
// build/vite/plugins/index.ts
import { PluginOption } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import legacy from '@vitejs/plugin-legacy';
import { webUpdateNotice } from '@plugin-web-update-notification/vite';
import { configCompressPlugin } from './compress';
import { configHtmlPlugin } from './html';
import { configVisualizerConfig } from './visualizer';
import { configMockPlugin } from './mock';
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
const {
VITE_LEGACY,
VITE_BUILD_COMPRESS,
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE,
VITE_USE_MOCK,
} = viteEnv;
const vitePlugins: (PluginOption | PluginOption[])[] = [
vue(),
vueJsx(),
// 打包部署后提醒插件
webUpdateNotice({
logVersion: true,
// 取消默认通知栏,监听更新事件自定义
// hiddenDefaultNotification: true
}),
// 在其他文件中监听自定义更新事件
// document.body.addEventListener('plugin_web_update_notice', (e) => {
// const { version, options } = e.detail;
// // write some code, show your custom notification and etc.
// alert('System update!');
// }),
];
// 兼容低版本浏览器
VITE_LEGACY && isBuild && vitePlugins.push(legacy());
// 生成打包预览
vitePlugins.push(configVisualizerConfig());
// 生成mock数据
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild));
// 用于最小化和使用index.html中ejs模板语法的插件
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
if (isBuild) {
// 打包gzip | brotli压缩
vitePlugins.push(
configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE),
);
}
return vitePlugins;
}
@vitejs/plugin-legacy,兼容低版本浏览器
@plugin-web-update-notification/vite,打包部署后提醒插件
安装一下
// package.json
"@vitejs/plugin-legacy": "^4.0.3",
"@plugin-web-update-notification/vite": "^1.7.1",
至此build打包配置完成。
在vite.config.ts里记得放开createVitePlugins函数相关的部分
9、安装一些辅助插件
// package.json
"rimraf": "^5.0.0",
"esno": "^0.16.3",
"cross-env": "^7.0.3",
10、配置一下package.json的脚本命令
可以把vue-tsc去掉,因为我们已经装了Lint代码检查工具了。
// package.json
"author": {
"name": "hit-point",
"email": "13213726869@163.com"
},
"scripts": {
"commit": "git add . && czg",
"bootstrap": "pnpm install",
"release": "release-it",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 -n ./changelog-option.cjs",
"dev": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite --mode development",
"dev:pro": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite --mode production",
"serve": "pnpm dev",
"build": "rimraf dist && cross-env NODE_ENV=development NODE_OPTIONS=--max-old-space-size=8192 vite build --mode development && esno ./build/vite/script/postBuild.ts",
"build:pro": "rimraf dist && cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 vite build --mode production && esno ./build/vite/script/postBuild.ts",
"preview": "pnpm run build && vite preview",
"report": "cross-env REPORT=true pnpm run build",
"clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
"clean:lib": "rimraf node_modules",
"test:unit": "vitest",
"lint:eslint": "eslint --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --fix \"**/*.{vue,css,scss,postcss,less}\" node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.cjs",
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
"prepare": "husky install"
},
接下来pnpm clean:lib,然后删掉-lock文件,重新install一下。
结语
vite配置这部分本想跟规范篇一样分成很多篇的,但是看了一下比较复杂还是全放在一起比较好,后面出些文章丰富一下吧。未完待续…