@plugin-light/project-config-vite
Version:
开箱即用的项目配置,适用于 Vue3.x 项目
408 lines (401 loc) • 13.3 kB
JavaScript
import path from 'node:path';
import { loadEnv } from 'vite';
import { CDN_MAP, getSubProjectRoot, getSubProjectConfig, DEFAULT_CONTEXT_OBJECT, scssLogger } from '@plugin-light/shared';
import { addCodeAtEndVitePlugin } from '@plugin-light/vite-plugin-add-code-at-end';
import { aliasForLibrary } from '@plugin-light/vite-plugin-alias-for-library';
import { crossGameStyleVitePlugin } from '@plugin-light/vite-plugin-cross-game-style';
import { crossPlatformVitePlugin } from '@plugin-light/vite-plugin-cross-platform';
import { exportDefaultInVue } from '@plugin-light/vite-plugin-export-default-in-vue';
import { genVersionWebVitePlugin } from '@plugin-light/vite-plugin-gen-version';
import { ifdefVitePlugin } from '@plugin-light/vite-plugin-ifdef';
import basicSsl from '@vitejs/plugin-basic-ssl';
import legacy from '@vitejs/plugin-legacy';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import { visualizer } from 'rollup-plugin-visualizer';
import AutoImport from 'unplugin-auto-import/vite';
import { VantResolver, ElementPlusResolver, TDesignResolver } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite';
import importToCDN from 'vite-plugin-cdn-import';
import commonjs from 'vite-plugin-commonjs';
import viteCompression from 'vite-plugin-compression';
import { createHtmlPlugin } from 'vite-plugin-html';
import mkcert from 'vite-plugin-mkcert';
import mockDevServerPlugin from 'vite-plugin-mock-dev-server';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import vueSetupExtend from 'vite-plugin-vue-setup-extend';
import { cdn } from 'vite-plugin-cdn2';
/**
* 移除第一个反斜杠
*
* @export
* @param {string} [str=''] 输入字符串
* @returns {string} 字符串
* @example
* ```ts
* removeFirstSlash('/abc/ddd/')
*
* 'abc/ddd/'
* ```
*/
/**
* 移除最后一个反斜杠
*
* @export
* @param {string} [str=''] 输入字符串
* @returns {string} 字符串
*
* @example
* ```ts
* removeLastSlash('/abc/')
*
* '/abc'
* ```
*/
function removeLastSlash(str) {
if (str === void 0) {
str = '';
}
return str.replace(/\/$/, '');
}
var BUILD_NAME_MAP = {
build: '_vConsoleBuildInfo',
commit: '_vConsoleCommitInfo'
};
function enableCDN(isEnabled) {
if (isEnabled === 'true') {
return cdn({
// url 可以更换为私有或其他源
// url: "https://cdn.jsdelivr.net/npm/",
url: 'https://unpkg.com/',
modules: ['vue', 'vue-demi', 'pinia', 'axios', 'vant', 'vue-router'],
});
}
}
const getCdnList = ({ useElementPlusCDN, }) => {
const modules = [
{
name: 'vue',
var: 'Vue',
path: CDN_MAP.VUE_V3,
},
{
name: 'vue-demi',
var: 'VueDemi',
path: CDN_MAP.VUE_DEMI,
},
{
name: 'vue-router',
var: 'VueRouter',
path: CDN_MAP.VUE_ROUTER_V4,
},
{
name: 'aegis-web-sdk',
var: 'Aegis',
path: CDN_MAP.AEGIS_WEB,
},
{
name: 'pinia',
var: 'Pinia',
path: CDN_MAP.PINIA,
},
];
if (useElementPlusCDN) {
modules.push({
name: 'element-plus',
var: 'ElementPlus',
css: CDN_MAP.ELEMENT_PLUS_CSS,
path: CDN_MAP.ELEMENT_PLUS,
});
}
return {
modules,
};
};
// 以下内容经常被 include
// md5
// js-cookie
// qs
// axios
const DEFAULT_OPTIMIZE_DEPS_INCLUDES = [];
const DEFAULT_OPTIMIZE_DEPS_EXCLUDES = [];
const DEFAULT_ADD_CODE_AT_END_OPTIONS = {};
// 之前版本内容
// {
// list: [
// {
// id: 'vue.js',
// code: 'export default function(){}',
// exact: false,
// },
// {
// id: 'vue.runtime.esm-bundler.js',
// code: 'export default function(){}',
// exact: false,
// number: 1,
// },
// ],
// };
const DEFAULT_ALIAS_FOR_LIBRARY_OPTIONS = {
list: [
'press-ui',
'press-plus',
'pmd-aegis',
'pmd-app-info',
'pmd-config',
'pmd-location',
'pmd-login',
'pmd-network',
'pmd-report',
'pmd-tools',
'pmd-types',
'pmd-widget',
'pmd-vue',
'pmd-jsapi',
],
target: 'src/library',
};
const DEFAULT_ALIAS = {
PRESS_UI: 'src/library/press-ui',
PRESS_PLUS: 'src/library/press-plus',
};
const getDefaultPmdAliasMap = (root) => [
'pmd-aegis',
'pmd-app-info',
'pmd-config',
'pmd-location',
'pmd-login',
'pmd-network',
'pmd-report',
'pmd-tools',
'pmd-types',
'pmd-widget',
'pmd-vue',
'pmd-jsapi',
].reduce((acc, item) => {
const list = item.split('/');
const libName = list[list.length - 1];
acc[item] = path.resolve(root, `src/library/${libName}`);
return acc;
}, {});
const DEFAULT_MKCERT_OPTIONS = {
source: 'coding',
};
const ENV_PREFIX = ['VITE_', 'VUE_APP'];
// 当前工作目录路径
const root = process.cwd();
function getAlias({ subProjectRoot, pressUiAlias, pressPlusAlias, pmdAliasMap, usePMDNetworkV2, }) {
const result = {
'@': subProjectRoot,
src: path.resolve(root, 'src'),
...(pmdAliasMap || {}),
};
if (pressUiAlias) {
result['press-ui'] = path.resolve(root, pressUiAlias);
}
if (pressPlusAlias) {
result['press-plus'] = path.resolve(root, pressPlusAlias);
}
if (usePMDNetworkV2) {
result['pmd-network'] = path.resolve(root, 'node_modules/pmd-network-v2');
}
return result;
}
function getViteBaseConfig({ mode, serverHttps, serverPort, serverHost = true, optimizeDepsIncludes, optimizeDepsExcludes, addCodeAtEndOptions, pressUiAlias = DEFAULT_ALIAS.PRESS_UI, pressPlusAlias = DEFAULT_ALIAS.PRESS_PLUS, aliasForLibraryOptions = DEFAULT_ALIAS_FOR_LIBRARY_OPTIONS, pmdAliasMap = getDefaultPmdAliasMap(root), customElements = [], useCdn = false, useElementPlusCDN = false, mkcertOptions = DEFAULT_MKCERT_OPTIONS, prePlugins = [], postPlugins = [], autoImportVant = true, autoImportElementPlus = true, autoImportTDesign = {
library: 'vue-next',
}, compressionOptions = true, legacyOptions = true, usePMDNetworkV2 = false, }) {
// 环境变量
const env = loadEnv(mode, root, ENV_PREFIX);
const isProduction = mode === 'production';
const appDir = env.VUE_APP_DIR || '';
const vueAppBase = env.VUE_APP_PUBLICPATH;
exportDefaultInVue({
root,
});
const subProjectRoot = getSubProjectRoot({
root,
appDir,
});
const subProjectConfig = getSubProjectConfig(subProjectRoot);
const autoImportComponents = [
autoImportVant
? VantResolver(typeof autoImportVant === 'boolean' ? undefined : autoImportVant)
: null,
autoImportElementPlus
? ElementPlusResolver(typeof autoImportElementPlus === 'boolean' ? undefined : autoImportElementPlus)
: null,
autoImportTDesign
? TDesignResolver(typeof autoImportTDesign === 'boolean' ? undefined : autoImportTDesign)
: null,
].filter(Boolean);
const plugins = [
...prePlugins,
aliasForLibrary({
root,
...(aliasForLibraryOptions || {}),
}),
ifdefVitePlugin({
context: {
...DEFAULT_CONTEXT_OBJECT,
H5: true,
WEB: true,
VUE3: true,
__NOT_UNI__: true,
},
type: ['css', 'js', 'html'],
}),
crossPlatformVitePlugin({
platform: 'web',
}),
crossGameStyleVitePlugin({
styleName: subProjectConfig?.styleName || '',
}),
addCodeAtEndOptions ? addCodeAtEndVitePlugin(typeof addCodeAtEndOptions === 'boolean'
? DEFAULT_ADD_CODE_AT_END_OPTIONS
: addCodeAtEndOptions) : null,
serverHttps ? basicSsl() : '',
commonjs(),
vue({
template: {
compilerOptions: {
// @ts-ignore
isCustomElement: (tag) => customElements.includes(tag),
},
},
}),
vueJsx(),
mockDevServerPlugin(),
AutoImport({
resolvers: autoImportComponents,
}),
Components({
dts: 'typings/components.d.ts',
resolvers: autoImportComponents,
}),
// svg icon
createSvgIconsPlugin({
// 指定图标文件夹
iconDirs: [path.resolve(subProjectRoot, './icons/svg')],
// 指定 symbolId 格式
symbolId: 'icon-[dir]-[name]',
}),
// 允许 setup 语法糖上添加组件名属性
vueSetupExtend(),
// 生产环境 gzip 压缩资源
compressionOptions
? viteCompression(typeof compressionOptions === 'boolean'
? undefined
: compressionOptions)
: null,
// 注入模板数据
createHtmlPlugin({
inject: {
data: {
ENABLE_ERUDA: env.VITE_ENABLE_ERUDA || 'false',
},
},
}),
// 生产环境默认不启用 CDN 加速
enableCDN(env.VITE_CDN_DEPS),
genVersionWebVitePlugin({
buildName: BUILD_NAME_MAP.build,
commitName: BUILD_NAME_MAP.commit,
delay: 10,
}),
legacyOptions ? legacy(typeof legacyOptions === 'boolean'
? {
targets: ['> 1%, last 1 version, ie >= 11'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime'], // 面向IE11时需要此插件
}
: legacyOptions)
: null,
isProduction && useCdn ? importToCDN(getCdnList({
useElementPlusCDN,
})) : null,
isProduction ? visualizer({
open: !!env.VITE_VISUALIZER,
filename: path.resolve(subProjectRoot, 'dist', 'stats.html'),
gzipSize: true,
brotliSize: true, // 收集 brotli 大小并将其显示
}) : null,
mkcertOptions ? mkcert(typeof mkcertOptions === 'boolean'
? DEFAULT_MKCERT_OPTIONS
: mkcertOptions) : null,
...postPlugins,
].filter(item => !!item);
const experimentalConfig = vueAppBase ? {
experimental: {
renderBuiltUrl(filename, { hostId, hostType, type }) {
console.log('[experimental] ', hostType, hostId, type, filename);
return `${removeLastSlash(vueAppBase)}/${filename}`;
},
},
} : {};
// 生成环境默认开启 sourcemap
const buildSourceMap = isProduction;
return {
root: subProjectRoot,
envDir: process.cwd(),
base: vueAppBase || './',
...experimentalConfig,
optimizeDeps: {
// include: ["press-ui/**/*.vue"]
include: optimizeDepsIncludes || DEFAULT_OPTIMIZE_DEPS_INCLUDES,
exclude: optimizeDepsExcludes || DEFAULT_OPTIMIZE_DEPS_EXCLUDES,
},
plugins,
resolve: {
alias: getAlias({
subProjectRoot,
pressUiAlias,
pressPlusAlias,
pmdAliasMap,
usePMDNetworkV2,
}),
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
},
server: {
port: serverPort ?? (env.VUE_APP_PORT ? +(env.VUE_APP_PORT) : 443),
https: serverHttps ?? {},
host: serverHost,
// 仅在 proxy 中配置的代理前缀, mock-dev-server 才会拦截并 mock
// doc: https://github.com/pengzhanbo/vite-plugin-mock-dev-server
proxy: {
// '^/dev-api': {
// target: '',
// },
},
},
build: {
target: 'es2015',
rollupOptions: {
input: {
1: path.resolve(subProjectRoot, './index.html'),
},
output: {
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
},
},
sourcemap: buildSourceMap,
},
esbuild: isProduction ? {
pure: ['console.log'],
drop: ['debugger'],
} : {},
css: {
preprocessorOptions: {
scss: {
// api: 'modern',
// https://sass-lang.com/documentation/breaking-changes/legacy-js-api/
// https://sass-lang.com/documentation/breaking-changes/import/
// https://sass-lang.com/documentation/breaking-changes/mixed-decls/
silenceDeprecations: ['import', 'legacy-js-api', 'mixed-decls'],
logger: scssLogger,
},
},
},
};
}
export { getViteBaseConfig };