@voya-kit/vite-config
Version:
Voya vite config
346 lines (333 loc) • 11.3 kB
JavaScript
import { join, resolve } from 'node:path';
import dayjs from 'dayjs';
import { readPackageJSON } from 'pkg-types';
import { defineConfig, loadEnv, mergeConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import colors from 'picocolors';
import dotenv from 'dotenv';
import fsExtra from 'fs-extra';
import { createHash } from 'node:crypto';
import compressPlugin from 'vite-plugin-compression';
import { createHtmlPlugin } from 'vite-plugin-html';
import visualizer from 'rollup-plugin-visualizer';
import { existsSync, mkdir, writeFile } from 'node:fs';
import { setupName } from '@voya-kit/vite-plugins';
import dts from 'vite-plugin-dts';
function getConfFiles() {
const script = process.env.npm_lifecycle_script;
const reg = new RegExp('--mode ([a-z_\\d]+)');
const result = reg.exec(script);
if (result) {
const mode = result[1];
return ['.env', `.env.${mode}`];
}
return ['.env', '.env.production'];
}
async function getEnvConfig(match = 'VITE_', confFiles = getConfFiles()) {
let envConfig = {};
for (const confFile of confFiles) {
try {
const { readFile } = fsExtra;
const envPath = await readFile(join(process.cwd(), confFile), { encoding: 'utf8' });
const env = dotenv.parse(envPath);
envConfig = { ...envConfig, ...env };
}
catch (e) {
console.error(`Error in parsing ${confFile}`, e);
}
}
const reg = new RegExp(`^(${match})`);
Object.keys(envConfig).forEach((key) => {
if (!reg.test(key)) {
Reflect.deleteProperty(envConfig, key);
}
});
return envConfig;
}
function createContentHash(content, hashLSize = 12) {
const hash = createHash('sha256').update(content);
return hash.digest('hex').slice(0, hashLSize);
}
const GLOBAL_CONFIG_FILE_NAME = '_app.config.js';
const PLUGIN_NAME = 'app-config';
async function createAppConfigPlugin({ root, isBuild, }) {
let publicPath;
let source;
if (!isBuild) {
return {
name: PLUGIN_NAME,
};
}
const { version = '' } = await readPackageJSON(root);
return {
name: PLUGIN_NAME,
async configResolved(_config) {
let appTitle = _config?.env?.VITE_GLOB_APP_TITLE ?? "";
appTitle = appTitle.replace(/\s/g, '_').replace(/-/g, '_');
publicPath = _config.base;
source = await getConfigSource(appTitle);
},
async transformIndexHtml(html) {
publicPath = publicPath.endsWith('/') ? publicPath : `${publicPath}/`;
const appConfigSrc = `${publicPath || '/'}${GLOBAL_CONFIG_FILE_NAME}?v=${version}-${createContentHash(source)}`;
return {
html,
tags: [
{
tag: 'script',
attrs: {
src: appConfigSrc,
},
},
],
};
},
async generateBundle() {
try {
this.emitFile({
type: 'asset',
fileName: GLOBAL_CONFIG_FILE_NAME,
source,
});
console.log(colors.cyan(`configuration file is build successfully!`));
}
catch (error) {
console.log(colors.red('configuration file configuration file failed to package:\n' + error));
}
},
};
}
const getVariableName = (title) => {
function strToHex(str) {
const result = [];
for (let i = 0; i < str.length; ++i) {
const hex = str.charCodeAt(i).toString(16);
result.push(('000' + hex).slice(-4));
}
return result.join('').toUpperCase();
}
return `__PRODUCTION__${strToHex(title) || '__APP'}__CONF__`.toUpperCase().replace(/\s/g, '');
};
async function getConfigSource(appTitle) {
const config = await getEnvConfig();
const variableName = getVariableName(appTitle);
const windowVariable = `window.${variableName}`;
let source = `${windowVariable}=${JSON.stringify(config)};`;
source += `
Object.freeze(${windowVariable});
Object.defineProperty(window, "${variableName}", {
configurable: false,
writable: false,
});
`.replace(/\s/g, '');
return source;
}
function configCompressPlugin({ compress, deleteOriginFile = false, }) {
const compressList = compress.split(',');
const plugins = [];
if (compressList.includes('gzip')) {
plugins.push(compressPlugin({
ext: '.gz',
deleteOriginFile,
}));
}
if (compressList.includes('brotli')) {
plugins.push(compressPlugin({
ext: '.br',
algorithm: 'brotliCompress',
deleteOriginFile,
}));
}
return plugins;
}
function configHtmlPlugin({ isBuild }) {
const htmlPlugin = createHtmlPlugin({
minify: isBuild,
});
return htmlPlugin;
}
function configVisualizerConfig() {
return visualizer({
filename: 'stats.html',
open: true,
gzipSize: true,
brotliSize: true,
});
}
const writeVersion = (versionFilePath, content) => {
writeFile(versionFilePath, content, (err) => {
if (err)
throw err;
});
};
const configVersionPlugin = (options) => {
let config = null;
return {
name: 'version-update',
configResolved(resolvedConfig) {
config = resolvedConfig;
},
buildStart() {
if (!config) {
throw new Error('Config not resolved before build start.');
}
const file = resolve(config.publicDir, 'version.json');
const content = JSON.stringify({ lastBuildTime: options.lastBuildTime });
if (existsSync(config.publicDir)) {
writeVersion(file, content);
}
else {
mkdir(config.publicDir, { recursive: true }, (err) => {
if (err)
throw err;
writeVersion(file, content);
});
}
},
};
};
async function createPlugins({ isBuild, root, compress, enableAnalyze }, lastBuildTime) {
const vitePlugins = [vue(), vueJsx()];
const appConfigPlugin = await createAppConfigPlugin({ root, isBuild });
vitePlugins.push(appConfigPlugin);
vitePlugins.push(configHtmlPlugin({ isBuild }));
vitePlugins.push(configVersionPlugin({ lastBuildTime }));
if (isBuild) {
vitePlugins.push(configCompressPlugin({
compress,
}));
}
if (enableAnalyze) {
vitePlugins.push(configVisualizerConfig());
}
return vitePlugins;
}
const commonConfig = (mode) => ({
server: {
port: 60006,
},
esbuild: {
drop: mode === 'production' ? ['console', 'debugger'] : [],
},
build: {
reportCompressedSize: false,
chunkSizeWarningLimit: 1500,
rollupOptions: {
maxParallelFileOps: 3,
},
},
plugins: [setupName()],
});
function defineApplicationConfig(defineOptions = {}) {
const { overrides = {} } = defineOptions;
return defineConfig(async ({ command, mode }) => {
const root = process.cwd();
const isBuild = command === 'build';
const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_ENABLE_ANALYZE } = loadEnv(mode, root);
const lastBuildTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
const defineData = await createDefineData(root, lastBuildTime);
const plugins = await createPlugins({
isBuild,
root,
enableAnalyze: VITE_ENABLE_ANALYZE === 'true',
enableMock: VITE_USE_MOCK === 'true',
compress: VITE_BUILD_COMPRESS || '',
}, lastBuildTime);
const pathResolve = (pathname) => resolve(root, '.', pathname);
const applicationConfig = {
resolve: {
alias: [
{
find: 'vue-i18n',
replacement: 'vue-i18n/dist/vue-i18n.cjs.js',
},
{
find: /\/@\//,
replacement: pathResolve('src') + '/',
},
{
find: /\/#\//,
replacement: pathResolve('types') + '/',
},
{
find: /@\//,
replacement: pathResolve('src') + '/',
},
{
find: /#\//,
replacement: pathResolve('types') + '/',
},
],
},
define: defineData,
build: {
target: 'es2015',
cssTarget: 'chrome80',
rollupOptions: {
output: {
entryFileNames: 'assets/[name].js',
manualChunks: {
vue: ['vue', 'pinia', 'vue-router'],
antd: ['ant-design-vue', '@ant-design/icons-vue'],
},
},
},
},
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
},
},
},
plugins,
};
const mergedConfig = mergeConfig(commonConfig(mode), applicationConfig);
return mergeConfig(mergedConfig, overrides);
});
}
async function createDefineData(root, lastBuildTime) {
try {
const pkgJson = await readPackageJSON(root);
const { dependencies, devDependencies, name, version } = pkgJson;
const __APP_INFO__ = {
pkg: { dependencies, devDependencies, name, version },
lastBuildTime: lastBuildTime,
};
return {
__APP_INFO__: JSON.stringify(__APP_INFO__),
};
}
catch (error) {
return {};
}
}
function definePackageConfig(defineOptions = {}) {
const { overrides = {} } = defineOptions;
const root = process.cwd();
return defineConfig(async ({ mode }) => {
const { dependencies = {}, peerDependencies = {} } = await readPackageJSON(root);
const packageConfig = {
build: {
lib: {
entry: 'src/index.ts',
formats: ['es'],
fileName: () => 'index.mjs',
},
rollupOptions: {
external: [...Object.keys(dependencies), ...Object.keys(peerDependencies)],
},
},
plugins: [
dts({
logLevel: 'error',
}),
],
};
const mergedConfig = mergeConfig(commonConfig(mode), packageConfig);
return mergeConfig(mergedConfig, overrides);
});
}
export { defineApplicationConfig, definePackageConfig };
//# sourceMappingURL=index.mjs.map