vue3 项目viteConfig配置需求点

0 阅读3分钟
1.环境变量&配置加载
import { defineConfig, loadEnv, ConfigEnv } from 'vite'

const env = loadEnv(mode.mode, process.cwd())
for (const k in env) {
  process.env[k] = env[k]
}

process(node.js运行时环境的内置全局对象, 包含关于进程的信息和操作信息的方法,**主要用来访问环境变量,获取进程状态,控制进程生命周期**)
process.env   环境变量对象,包含当前进程的环境变量(如 NODE_ENV)
process.argv  启动Node进程时的命令行参数数组

process.env环境变量对象,包含当前进程的环境变量(如 NODE_ENV
process.argv启动 Node 进程时的命令行参数数组
process.cwd()返回当前工作目录的路径
process.exit(code)结束进程,code 是退出码
process.nextTick(fn)在当前事件循环尾部执行回调
process.memoryUsage()获取进程的内存使用情况
process.platform当前操作系统平台,如 win32, linux, darwin
process.versionNode.js 版本号
process.uptime()进程运行的秒数
process.pid当前进程的 ID
process.stdinprocess.stdoutprocess.stderr
2.别名配置
const alias = {
        '@': path.resolve(__dirname, './src'),
        vue$: 'vue/dist/vue.runtime.esm-bundler.js',
}
3.剔除console & debugger 

const esbuild = {
  drop: NODE_ENV === 'development' ? [] : ['console', 'debugger'],
}
4.Rullup打包配置-代码分割
manualChunks(id) {
  if (id.includes('node_modules')) {
    if (id.split('node_modules/')[1].split('/')[0].includes('pnpm')) {
      return id.split('node_modules/')[1].split('/')[1]
    }
    return id.split('node_modules/')[1].split('/')[0]
  }
}
-   通过路径拆分,把每个 `node_modules` 包单独拆分成一个 chunk 文件。

-   对 pnpm 包做特殊拆分,拆成具体包名的 chunk。

-   目的是实现第三方库按包拆分,优化缓存利用和加载性能

5.开发服务器配置:
server: {
  port: VITE_CLI_PORT,
  overlay: {
    warnings: false,
    errors: true,
  },
  proxy: {
    [VITE_BASE_API]: { target: VITE_BASE_PATH, changeOrigin: true, rewrite: ... },
    '/mapapi': { target: VITE_MAP_PATH, changeOrigin: true, rewrite: ... },
    '/sysSettingMessages': { target: VITE_WS_BASE_PATH, changeOrigin: true, secure: true, ws: true },
  },
},
-   指定本地启动端口。

-   前端请求接口时,自动代理转发到对应的后端服务器,方便开发跨域。

-   支持 websocket 代理

6.构建相关配置:
build: {
  minify: 'esbuild',
  manifest: false,
  sourcemap: VITE_ENV !== 'production' ? 'inline' : false,
  outDir: 'dist',
  reportCompressedSize: true,
  rollupOptions,
  chunkSizeWarningLimit: 800,
},
-   使用 esbuild 进行快速压缩(相比 terser 更快)。

-   生产环境关闭 sourcemap,开发环境开启 inline sourcemap 方便调试。

-   自定义打包输出目录和文件命名规则。

-   允许打包大小警告阈值设为 800kb。

-   启用 gzip 体积报告,辅助评估构建产物大小

7.自动导入&组件按需加载
AutoImport({
  imports: ['vue', 'vue-router'],
  // resolvers: [ElementPlusResolver()],
}),
Components({
  resolvers: [ElementPlusResolver()],
}),
-   `unplugin-auto-import` 实现 API(如 `ref`, `reactive`, `computed`, `onMounted`)的自动引入,避免反复写导入语句。

-   `unplugin-vue-components` 实现自定义组件和 ElementPlus 组件的自动按需导入,避免全量引入,减小体积。

8.兼容老旧浏览器
legacyPlugin({
  targets: ['Chrome >= 70', 'Safari >= 10.1', 'Firefox >= 54', 'Edge >= 15'],
}),
通过 Babel 插件转译和 polyfill,支持较老版本浏览器,保障兼容性。

9.外部依赖使用CDN

externalGlobals({
  vue: 'Vue',
  echarts: 'echarts',
  'vue-demi': 'VueDemi',
  tinymce: 'tinymce',
  '@tinymce/tinymce-vue': 'Editor',
}),
生产构建时不打包指定的依赖库,改为使用 CDN 全局变量,减少包体积,利用 CDN 高速分发。

10.HTML模板注入&压缩
createHtmlPlugin({
  minify: true,
  entry: '/src/main.js',
  template: '/index.html',
  inject: { data: publicCDN },
}),
-   支持通过 ejs 语法向 `index.html` 模板注入变量,动态插入 CDN 地址或其他动态信息。

-   启用 HTML 压缩,减少体积。

11.骨架屏插件
skeletonPlugin(),
自定义骨架屏插件,改善页面加载体验,减少白屏

12.资源压缩:
viteCompression({
  verbose: true,
  disable: false,
  threshold: 10240,
  algorithm: 'gzip',
  ext: '.gz',
}),
产出 gzip 压缩文件,提升资源传输效率,减少首屏加载时间。

13.打包分析
visualizer({
  template: 'treemap',
  filename: './node_modules/.cache/visualizer/stats.html',
  open: true,
  gzipSize: true,
  brotliSize: true,
}),
打包后生成可视化分析报告,帮助开发者洞察依赖结构、包大小,辅助优化。

14.开发工具&全量引入
if (NODE_ENV === 'development') {
  config.plugins.push(VueDevTools());
  config.plugins.push(fullImportPlugin());
}
-   开发模式下集成 Vue Devtools 支持。

-   通过 `fullImportPlugin` 实现开发时全量引入依赖,避免模块按需加载导致的构建重复,提升开发体验。

完整配置代码(web端)

import { defineConfig, loadEnv, ConfigEnv } from 'vite'
import legacyPlugin from '@vitejs/plugin-legacy'
import AutoImport from 'unplugin-auto-import/vite' // API 的 自动引入
import Components from 'unplugin-vue-components/vite' // 按需加载自定义组件
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import Banner from 'vite-plugin-banner'
import * as path from 'path'
import * as dotenv from 'dotenv'
import * as fs from 'fs'
import vuePlugin from '@vitejs/plugin-vue'
import vueSetupExtend from 'vite-plugin-vue-setup-extend'
import GvaPosition from './vitePlugin/gvaPosition'
import fullImportPlugin from './vitePlugin/fullImport/fullImport.js'
import skeletonPlugin from './vitePlugin/skeletonPlugin/index.js'
import vueJsx from '@vitejs/plugin-vue-jsx'
// html 压缩
import { createHtmlPlugin } from 'vite-plugin-html'
import viteCompression from 'vite-plugin-compression'
// import removeConsole from 'vite-plugin-remove-console'
import visualizer from 'rollup-plugin-visualizer'
import externalGlobals from 'rollup-plugin-external-globals'
import VueDevTools from 'vite-plugin-vue-devtools'

import { publicCDN } from './public/publicCDN'
// @see https://cn.vitejs.dev/config/
const viteConfig = defineConfig((mode) => {
	console.log(`output->process`, process.argv)
	const env = loadEnv(mode.mode, process.cwd())
	console.log('👏vite环境变量', env)
	const isFeatureEnabled = process.argv.includes('--d')
	console.log(`output->isFeatureEnabled`, isFeatureEnabled)
	for (const k in env) {
		process.env[k] = env[k]
	}
	process.env['VITE_NOPERMISSION'] = isFeatureEnabled

	const {
		NODE_ENV,
		VITE_CLI_PORT,
		VITE_SERVER_PORT,
		VITE_BASE_API,
		VITE_BASE_PATH,
		VITE_WS_BASE_PATH,
		VITE_MAP_PATH,
		VITE_NOPERMISSION,
		VITE_ENV,
	} = process.env
	const timestamp = Date.parse(new Date())
	// 在运行时首次使用某个依赖时进行预构建
	const optimizeDeps = {
		//减少运行时css的重新构建开销
		include: ['element-plus/es/components/**/*'],
	}

	const alias = {
		'@': path.resolve(__dirname, './src'),
		vue$: 'vue/dist/vue.runtime.esm-bundler.js',
	}

	const esbuild = {
		drop: NODE_ENV === 'development' ? [] : [('console', 'debugger')],
	}
	const rollupOptions = {
		output: {
			chunkFileNames: 'static/js/[name]-[hash].js', // 引入文件名的名称
			entryFileNames: 'static/js/[name]-[hash].js', // 包的入口文件名称
			assetFileNames: 'static/[ext]/[name]-[hash].[ext]', // 资源文件像 字体,图片等
			manualChunks(id) {
				if (id.includes('node_modules')) {
					// 让每个插件都打包成独立的文件
					if (id.toString().split('node_modules/')[1].split('/')[0].includes('pnpm')) {
						return id.toString().split('node_modules/')[1].split('/')[1].toString()
					}

					return id.toString().split('node_modules/')[1].split('/')[0].toString()
				}
			},
			compact: true, //用于压缩 Rollup 产生的额外代码
		},

		plugins: [],
	}
	const config = {
		mode: NODE_ENV,
		base: NODE_ENV === 'development' ? './' : '/', // index.html文件所在位置
		resolve: {
			alias,
		},
		publicPath: '/',
		server: {
			port: VITE_CLI_PORT,
			overlay: {
				warnings: false,
				errors: true,
			},
			proxy: {
				// 把key的路径代理到target位置
				// detail: https://cli.vuejs.org/config/#devserver-proxy
				[VITE_BASE_API]: {
					// 需要代理的路径   例如 '/api'
					target: `${VITE_BASE_PATH}`, // 代理到 目标路径
					changeOrigin: true,
					rewrite: (path) => {
						if (VITE_BASE_PATH.includes('http:')) {
							return path.replace(new RegExp('^' + '/api'), '')
						}
						return path
					},
				},
				'/mapapi': {
					// 需要代理的路径   例如 '/api'
					target: `${VITE_MAP_PATH}`, // 代理到 目标路径
					changeOrigin: true,
					rewrite: (path) => path.replace(new RegExp('^' + '/mapapi'), ''),
				},
				'/sysSettingMessages': {
					// 需要代理的路径   例如 '/sysSettingMessages'
					target: `${VITE_WS_BASE_PATH}`, // 代理到 目标路径
					changeOrigin: true,
					secure: true,
					ws: true,
				},
			},
		},
		build: {
			minify: 'esbuild', // 是否进行压缩,boolean | 'terser' | 'esbuild',默认使用esbuild
			manifest: false, // 是否产出manifest.json
			sourcemap: VITE_ENV != 'production' ? 'inline' : false, // 是否产出sourcemap.json
			outDir: 'dist', // 产出目录
			reportCompressedSize: true, //启用/禁用 gzip 压缩大小报告
			rollupOptions,
			chunkSizeWarningLimit: 800,
		},
		esbuild,
		optimizeDeps,
		// 不打包依赖
		external: ['vue', 'vue-demi', 'echarts', 'tinymce', '@tinymce/tinymce-vue'],
		plugins: [
			// GvaPosition(), // 代码位置提示
			vuePlugin(),
			vueJsx(),
			AutoImport({
				imports: [
					'vue',
					'vue-router',
					// 其他需要自动导入的库
				],
				// resolvers: [ElementPlusResolver()],
			}),
			Components({
				resolvers: [ElementPlusResolver()],
			}),

			vueSetupExtend(),
			legacyPlugin({
				targets: ['Chrome >= 70', 'Safari >= 10.1', 'Firefox >= 54', 'Edge >= 15'],
			}),
			// 由于AutoImport 会和cdn存在冲突 所以要等  注意一定要放在AutoImport的后面,防止前面会失效
			// 不打包依赖映射的对象
			// 由于AutoImport 会和cdn存在冲突 所以要等  注意一定要放在AutoImport的后面,防止前面会失效
			{
				...externalGlobals({
					// "在项目中引入的变量名称":"CDN包导出的名称,一般在CDN包中都是可见的"
					vue: 'Vue',
					echarts: 'echarts',
					'vue-demi': 'VueDemi',
					tinymce: 'tinymce',
					'@tinymce/tinymce-vue': 'Editor',
				}),
				enforce: 'post',
				apply: 'build',
			},
			createHtmlPlugin({
				minify: true,
				entry: '/src/main.js',
				template: '/index.html',

				/**
				 * @description  需要注入 index.html ejs 模版的数据
				 * 需要抽离公共库到cdn 引入
				 */
				inject: {
					data: {
						notoSansSc: publicCDN.notoSansSc,
						vuescript: publicCDN.vuescript,
						demiScript: publicCDN.demiScript,
						echartsScript: publicCDN.echartsScript,
						tinymceScript: publicCDN.tinymceScript,

						tinymceVueScript: publicCDN.tinymceVueScript,
					},
				},
			}),
			skeletonPlugin(),
			viteCompression({
				//生成压缩包gz
				verbose: true,
				disable: false,
				threshold: 10240,
				algorithm: 'gzip',
				ext: '.gz',
			}),

			visualizer({
				template: 'treemap', // sunburst | treemap | network | raw-data | list
				filename: './node_modules/.cache/visualizer/stats.html',
				open: true,
				gzipSize: true,
				brotliSize: true,
			}),
		],
		css: {
			preprocessorOptions: {
				scss: {
					additionalData: `@use "@/style/element/index.scss" as *;`,
				},
			},
			devSourcemap: true, // 在开发过程中是否启用 sourcemap。
		},
		clearScreen: NODE_ENV === 'development' ? true : false, //设为 false 可以避免 Vite 清屏而错过在终端中打印某些关键信息
	}
	if (NODE_ENV === 'development') {
		// 开发工具
		config.plugins.push(VueDevTools())
		// 开发时进行主动全局引入,避免不同页面模块引用的组件不一样,对应的css资源需加载,导致vite重新构建
		config.plugins.push(fullImportPlugin())
	} else {
	}
	return config
})

export default viteConfig