diff --git a/.gitignore b/.gitignore index dfd441fd..344f1b58 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,3 @@ target dist logs cache -build diff --git a/Report-V3-TS/build/constant.ts b/Report-V3-TS/build/constant.ts new file mode 100644 index 00000000..cbcc5f39 --- /dev/null +++ b/Report-V3-TS/build/constant.ts @@ -0,0 +1,6 @@ +/** + * The name of the configuration file entered in the production environment + */ +export const GLOB_CONFIG_FILE_NAME = 'app.config.js'; + +export const OUTPUT_DIR = 'dist'; diff --git a/Report-V3-TS/build/getConfigFileName.ts b/Report-V3-TS/build/getConfigFileName.ts new file mode 100644 index 00000000..d61cd416 --- /dev/null +++ b/Report-V3-TS/build/getConfigFileName.ts @@ -0,0 +1,9 @@ +/** + * Get the configuration file variable name + * @param env + */ +export const getConfigFileName = (env: Record) => { + return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__` + .toUpperCase() + .replace(/\s/g, ''); +}; diff --git a/Report-V3-TS/build/script/buildConf.ts b/Report-V3-TS/build/script/buildConf.ts new file mode 100644 index 00000000..0c8089cc --- /dev/null +++ b/Report-V3-TS/build/script/buildConf.ts @@ -0,0 +1,47 @@ +/** + * Generate additional configuration files when used for packaging. The file can be configured with some global variables, so that it can be changed directly externally without repackaging + */ +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 { + configName: string; + config: any; + configFileName?: string; +} + +function createConfig(params: CreateConfigParams) { + const { configName, config, configFileName } = params; + try { + const windowConf = `window.${configName}`; + // Ensure that the variable will not be modified + let configStr = `${windowConf}=${JSON.stringify(config)};`; + configStr += ` + Object.freeze(${windowConf}); + Object.defineProperty(window, "${configName}", { + configurable: false, + writable: false, + }); + `.replace(/\s/g, ''); + + fs.mkdirp(getRootPath(OUTPUT_DIR)); + 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() { + const config = getEnvConfig(); + const configFileName = getConfigFileName(config); + createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME }); +} diff --git a/Report-V3-TS/build/script/postBuild.ts b/Report-V3-TS/build/script/postBuild.ts new file mode 100644 index 00000000..42635d88 --- /dev/null +++ b/Report-V3-TS/build/script/postBuild.ts @@ -0,0 +1,23 @@ +// #!/usr/bin/env node + +import { runBuildConfig } from './buildConf'; +import colors from 'picocolors'; + +import pkg from '../../package.json'; + +export const runBuild = async () => { + try { + const argvList = process.argv.splice(2); + + // Generate configuration file + 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(); diff --git a/Report-V3-TS/build/utils.ts b/Report-V3-TS/build/utils.ts new file mode 100644 index 00000000..c201514f --- /dev/null +++ b/Report-V3-TS/build/utils.ts @@ -0,0 +1,92 @@ +import fs from 'fs'; +import path from 'path'; +import dotenv from 'dotenv'; + +export function isDevFn(mode: string): boolean { + return mode === 'development'; +} + +export function isProdFn(mode: string): boolean { + return mode === 'production'; +} + +/** + * Whether to generate package preview + */ +export function isReportMode(): boolean { + return process.env.REPORT === 'true'; +} + +// Read all environment variable configuration files to process.env +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; + if (typeof realName === 'string') { + process.env[envName] = realName; + } else if (typeof realName === 'object') { + process.env[envName] = JSON.stringify(realName); + } + } + return ret; +} + +/** + * 获取当前环境下生效的配置文件名 + */ +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']; +} + +/** + * Get the environment variables starting with the specified prefix + * @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; +} + +/** + * Get user root directory + * @param dir file path + */ +export function getRootPath(...dir: string[]) { + return path.resolve(process.cwd(), ...dir); +} diff --git a/Report-V3-TS/build/vite/plugin/compress.ts b/Report-V3-TS/build/vite/plugin/compress.ts new file mode 100644 index 00000000..0a340598 --- /dev/null +++ b/Report-V3-TS/build/vite/plugin/compress.ts @@ -0,0 +1,35 @@ +/** + * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated + * https://github.com/anncwb/vite-plugin-compression + */ +import type { Plugin } from 'vite'; + +import compressPlugin from 'vite-plugin-compression'; + +export function configCompressPlugin( + compress: 'gzip' | 'brotli' | 'none', + deleteOriginFile = false, +): Plugin | Plugin[] { + const compressList = compress.split(','); + + const plugins: Plugin[] = []; + + if (compressList.includes('gzip')) { + plugins.push( + compressPlugin({ + ext: '.gz', + deleteOriginFile, + }), + ); + } + if (compressList.includes('brotli')) { + plugins.push( + compressPlugin({ + ext: '.br', + algorithm: 'brotliCompress', + deleteOriginFile, + }), + ); + } + return plugins; +} diff --git a/Report-V3-TS/build/vite/plugin/html.ts b/Report-V3-TS/build/vite/plugin/html.ts new file mode 100644 index 00000000..6af034ac --- /dev/null +++ b/Report-V3-TS/build/vite/plugin/html.ts @@ -0,0 +1,40 @@ +/** + * Plugin to minimize and use ejs template syntax in index.html. + * 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({ + minify: isBuild, + inject: { + // Inject data into ejs template + data: { + title: VITE_GLOB_APP_TITLE, + }, + // Embed the generated app.config.js file + tags: isBuild + ? [ + { + tag: 'script', + attrs: { + src: getAppConfigSrc(), + }, + }, + ] + : [], + }, + }); + return htmlPlugin; +} diff --git a/Report-V3-TS/build/vite/plugin/index.ts b/Report-V3-TS/build/vite/plugin/index.ts new file mode 100644 index 00000000..c9bc582d --- /dev/null +++ b/Report-V3-TS/build/vite/plugin/index.ts @@ -0,0 +1,50 @@ +import type { PluginOption } from 'vite'; + +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; + +import Components from 'unplugin-vue-components/vite'; +import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'; + +import VueSetupExtend from 'vite-plugin-vue-setup-extend'; + +import { configHtmlPlugin } from './html'; +import { configMockPlugin } from './mock'; +import { configVisualizerPlugin } from './visualizer'; +import { configCompressPlugin } from './compress'; + +export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock) { + const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv; + + const vitePlugins: (PluginOption | PluginOption[])[] = [ + // have to + vue(), + // have to + vueJsx(), + // setup extend + VueSetupExtend(), + // 按需引入NaiveUi且自动创建组件声明 + Components({ + dts: true, + resolvers: [NaiveUiResolver()], + }), + ]; + + // vite-plugin-html + vitePlugins.push(configHtmlPlugin(viteEnv, isBuild)); + + // vite-plugin-mock + VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild, prodMock)); + + // rollup-plugin-visualizer + vitePlugins.push(configVisualizerPlugin()); + + if (isBuild) { + // rollup-plugin-gzip + vitePlugins.push( + configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE), + ); + } + + return vitePlugins; +} diff --git a/Report-V3-TS/build/vite/plugin/mock.ts b/Report-V3-TS/build/vite/plugin/mock.ts new file mode 100644 index 00000000..84bf1440 --- /dev/null +++ b/Report-V3-TS/build/vite/plugin/mock.ts @@ -0,0 +1,19 @@ +/** + * Mock plugin for development and production. + * https://github.com/anncwb/vite-plugin-mock + */ +import { viteMockServe } from 'vite-plugin-mock'; + +export function configMockPlugin(isBuild: boolean, prodMock: boolean) { + return viteMockServe({ + ignore: /^\_/, + mockPath: 'mock', + localEnabled: !isBuild, + prodEnabled: isBuild && prodMock, + injectCode: ` + import { setupProdMockServer } from '../mock/_createProductionServer'; + + setupProdMockServer(); + `, + }); +} diff --git a/Report-V3-TS/build/vite/plugin/visualizer.ts b/Report-V3-TS/build/vite/plugin/visualizer.ts new file mode 100644 index 00000000..e57e36bf --- /dev/null +++ b/Report-V3-TS/build/vite/plugin/visualizer.ts @@ -0,0 +1,15 @@ +import { PluginOption } from 'vite'; +import visualizer from 'rollup-plugin-visualizer'; +import { isReportMode } from '../../utils'; + +export function configVisualizerPlugin() { + if (isReportMode()) { + return visualizer({ + filename: './node_modules/.cache/visualizer/stats.html', + open: true, + gzipSize: true, + brotliSize: true, + }) as PluginOption; + } + return []; +} diff --git a/Report-V3-TS/build/vite/proxy.ts b/Report-V3-TS/build/vite/proxy.ts new file mode 100644 index 00000000..dc23646d --- /dev/null +++ b/Report-V3-TS/build/vite/proxy.ts @@ -0,0 +1,34 @@ +/** + * Used to parse the .env.development proxy configuration + */ +import type { ProxyOptions } from 'vite'; + +type ProxyItem = [string, string]; + +type ProxyList = ProxyItem[]; + +type ProxyTargetList = Record string }>; + +const httpsRE = /^https:\/\//; + +/** + * Generate proxy + * @param list + */ +export function createProxy(list: ProxyList = []) { + const ret: ProxyTargetList = {}; + for (const [prefix, target] of list) { + const isHttps = httpsRE.test(target); + + // https://github.com/http-party/node-http-proxy#options + ret[prefix] = { + target: target, + changeOrigin: true, + ws: true, + rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''), + // https is require secure=false + ...(isHttps ? { secure: false } : {}), + }; + } + return ret; +}