物业代码生成
This commit is contained in:
7
internal/vite-config/build.config.ts
Normal file
7
internal/vite-config/build.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
59
internal/vite-config/package.json
Normal file
59
internal/vite-config/package.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@vben/vite-config",
|
||||
"version": "5.5.6",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "internal/vite-config"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"stub": "pnpm unbuild --stub"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "catalog:",
|
||||
"@jspm/generator": "catalog:",
|
||||
"archiver": "catalog:",
|
||||
"cheerio": "catalog:",
|
||||
"get-port": "catalog:",
|
||||
"html-minifier-terser": "catalog:",
|
||||
"nitropack": "catalog:",
|
||||
"resolve.exports": "catalog:",
|
||||
"vite-plugin-pwa": "catalog:",
|
||||
"vite-plugin-vue-devtools": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pnpm/workspace.read-manifest": "catalog:",
|
||||
"@types/archiver": "catalog:",
|
||||
"@types/html-minifier-terser": "catalog:",
|
||||
"@vben/node-utils": "workspace:*",
|
||||
"@vitejs/plugin-vue": "catalog:",
|
||||
"@vitejs/plugin-vue-jsx": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"dotenv": "catalog:",
|
||||
"rollup": "catalog:",
|
||||
"rollup-plugin-visualizer": "catalog:",
|
||||
"sass": "catalog:",
|
||||
"vite": "catalog:",
|
||||
"vite-plugin-compression": "catalog:",
|
||||
"vite-plugin-dts": "catalog:",
|
||||
"vite-plugin-html": "catalog:",
|
||||
"vite-plugin-lazy-import": "catalog:"
|
||||
}
|
||||
}
|
125
internal/vite-config/src/config/application.ts
Normal file
125
internal/vite-config/src/config/application.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import type { CSSOptions, UserConfig } from 'vite';
|
||||
|
||||
import type { DefineApplicationOptions } from '../typing';
|
||||
|
||||
import path, { relative } from 'node:path';
|
||||
|
||||
import { findMonorepoRoot } from '@vben/node-utils';
|
||||
|
||||
import { NodePackageImporter } from 'sass';
|
||||
import { defineConfig, loadEnv, mergeConfig } from 'vite';
|
||||
|
||||
import { defaultImportmapOptions, getDefaultPwaOptions } from '../options';
|
||||
import { loadApplicationPlugins } from '../plugins';
|
||||
import { loadAndConvertEnv } from '../utils/env';
|
||||
import { getCommonConfig } from './common';
|
||||
|
||||
function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) {
|
||||
return defineConfig(async (config) => {
|
||||
const options = await userConfigPromise?.(config);
|
||||
const { appTitle, base, port, ...envConfig } = await loadAndConvertEnv();
|
||||
const { command, mode } = config;
|
||||
const { application = {}, vite = {} } = options || {};
|
||||
const root = process.cwd();
|
||||
const isBuild = command === 'build';
|
||||
const env = loadEnv(mode, root);
|
||||
|
||||
const plugins = await loadApplicationPlugins({
|
||||
archiver: true,
|
||||
archiverPluginOptions: {},
|
||||
compress: false,
|
||||
compressTypes: ['brotli', 'gzip'],
|
||||
devtools: true,
|
||||
env,
|
||||
extraAppConfig: true,
|
||||
html: true,
|
||||
i18n: true,
|
||||
importmapOptions: defaultImportmapOptions,
|
||||
injectAppLoading: true,
|
||||
injectMetadata: true,
|
||||
isBuild,
|
||||
license: true,
|
||||
mode,
|
||||
nitroMock: !isBuild,
|
||||
nitroMockOptions: {},
|
||||
print: !isBuild,
|
||||
printInfoMap: {
|
||||
'Vben Admin Docs': 'https://doc.vben.pro',
|
||||
},
|
||||
pwa: true,
|
||||
pwaOptions: getDefaultPwaOptions(appTitle),
|
||||
vxeTableLazyImport: true,
|
||||
...envConfig,
|
||||
...application,
|
||||
});
|
||||
|
||||
const { injectGlobalScss = true } = application;
|
||||
|
||||
const applicationConfig: UserConfig = {
|
||||
base,
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
assetFileNames: '[ext]/[name]-[hash].[ext]',
|
||||
chunkFileNames: 'js/[name]-[hash].js',
|
||||
entryFileNames: 'jse/index-[name]-[hash].js',
|
||||
},
|
||||
},
|
||||
target: 'es2015',
|
||||
},
|
||||
css: createCssOptions(injectGlobalScss),
|
||||
esbuild: {
|
||||
drop: isBuild
|
||||
? [
|
||||
// 'console',
|
||||
'debugger',
|
||||
]
|
||||
: [],
|
||||
legalComments: 'none',
|
||||
},
|
||||
plugins,
|
||||
server: {
|
||||
host: true,
|
||||
port,
|
||||
warmup: {
|
||||
// 预热文件
|
||||
clientFiles: [
|
||||
'./index.html',
|
||||
'./src/bootstrap.ts',
|
||||
'./src/{views,layouts,router,store,api,adapter}/*',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mergedCommonConfig = mergeConfig(
|
||||
await getCommonConfig(),
|
||||
applicationConfig,
|
||||
);
|
||||
return mergeConfig(mergedCommonConfig, vite);
|
||||
});
|
||||
}
|
||||
|
||||
function createCssOptions(injectGlobalScss = true): CSSOptions {
|
||||
const root = findMonorepoRoot();
|
||||
return {
|
||||
preprocessorOptions: injectGlobalScss
|
||||
? {
|
||||
scss: {
|
||||
additionalData: (content: string, filepath: string) => {
|
||||
const relativePath = relative(root, filepath);
|
||||
// apps下的包注入全局样式
|
||||
if (relativePath.startsWith(`apps${path.sep}`)) {
|
||||
return `@use "@vben/styles/global" as *;\n${content}`;
|
||||
}
|
||||
return content;
|
||||
},
|
||||
api: 'modern',
|
||||
importers: [new NodePackageImporter()],
|
||||
},
|
||||
}
|
||||
: {},
|
||||
};
|
||||
}
|
||||
|
||||
export { defineApplicationConfig };
|
13
internal/vite-config/src/config/common.ts
Normal file
13
internal/vite-config/src/config/common.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { UserConfig } from 'vite';
|
||||
|
||||
async function getCommonConfig(): Promise<UserConfig> {
|
||||
return {
|
||||
build: {
|
||||
chunkSizeWarningLimit: 2000,
|
||||
reportCompressedSize: false,
|
||||
sourcemap: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { getCommonConfig };
|
37
internal/vite-config/src/config/index.ts
Normal file
37
internal/vite-config/src/config/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { DefineConfig } from '../typing';
|
||||
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { defineApplicationConfig } from './application';
|
||||
import { defineLibraryConfig } from './library';
|
||||
|
||||
export * from './application';
|
||||
export * from './library';
|
||||
|
||||
function defineConfig(
|
||||
userConfigPromise?: DefineConfig,
|
||||
type: 'application' | 'auto' | 'library' = 'auto',
|
||||
) {
|
||||
let projectType = type;
|
||||
|
||||
// 根据包是否存在 index.html,自动判断类型
|
||||
if (projectType === 'auto') {
|
||||
const htmlPath = join(process.cwd(), 'index.html');
|
||||
projectType = existsSync(htmlPath) ? 'application' : 'library';
|
||||
}
|
||||
|
||||
switch (projectType) {
|
||||
case 'application': {
|
||||
return defineApplicationConfig(userConfigPromise);
|
||||
}
|
||||
case 'library': {
|
||||
return defineLibraryConfig(userConfigPromise);
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unsupported project type: ${projectType}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { defineConfig };
|
59
internal/vite-config/src/config/library.ts
Normal file
59
internal/vite-config/src/config/library.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { ConfigEnv, UserConfig } from 'vite';
|
||||
|
||||
import type { DefineLibraryOptions } from '../typing';
|
||||
|
||||
import { readPackageJSON } from '@vben/node-utils';
|
||||
|
||||
import { defineConfig, mergeConfig } from 'vite';
|
||||
|
||||
import { loadLibraryPlugins } from '../plugins';
|
||||
import { getCommonConfig } from './common';
|
||||
|
||||
function defineLibraryConfig(userConfigPromise?: DefineLibraryOptions) {
|
||||
return defineConfig(async (config: ConfigEnv) => {
|
||||
const options = await userConfigPromise?.(config);
|
||||
const { command, mode } = config;
|
||||
const { library = {}, vite = {} } = options || {};
|
||||
const root = process.cwd();
|
||||
const isBuild = command === 'build';
|
||||
|
||||
const plugins = await loadLibraryPlugins({
|
||||
dts: false,
|
||||
injectMetadata: true,
|
||||
isBuild,
|
||||
mode,
|
||||
...library,
|
||||
});
|
||||
|
||||
const { dependencies = {}, peerDependencies = {} } =
|
||||
await readPackageJSON(root);
|
||||
|
||||
const externalPackages = [
|
||||
...Object.keys(dependencies),
|
||||
...Object.keys(peerDependencies),
|
||||
];
|
||||
|
||||
const packageConfig: UserConfig = {
|
||||
build: {
|
||||
lib: {
|
||||
entry: 'src/index.ts',
|
||||
fileName: () => 'index.mjs',
|
||||
formats: ['es'],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: (id) => {
|
||||
return externalPackages.some(
|
||||
(pkg) => id === pkg || id.startsWith(`${pkg}/`),
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins,
|
||||
};
|
||||
const commonConfig = await getCommonConfig();
|
||||
const mergedConmonConfig = mergeConfig(commonConfig, packageConfig);
|
||||
return mergeConfig(mergedConmonConfig, vite);
|
||||
});
|
||||
}
|
||||
|
||||
export { defineLibraryConfig };
|
4
internal/vite-config/src/index.ts
Normal file
4
internal/vite-config/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './config';
|
||||
export * from './options';
|
||||
export * from './plugins';
|
||||
export { loadAndConvertEnv } from './utils/env';
|
45
internal/vite-config/src/options.ts
Normal file
45
internal/vite-config/src/options.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { Options as PwaPluginOptions } from 'vite-plugin-pwa';
|
||||
|
||||
import type { ImportmapPluginOptions } from './typing';
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
|
||||
const getDefaultPwaOptions = (name: string): Partial<PwaPluginOptions> => ({
|
||||
manifest: {
|
||||
description:
|
||||
'Vben Admin is a modern admin dashboard template based on Vue 3. ',
|
||||
icons: [
|
||||
{
|
||||
sizes: '192x192',
|
||||
src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-192.png',
|
||||
type: 'image/png',
|
||||
},
|
||||
{
|
||||
sizes: '512x512',
|
||||
src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-512.png',
|
||||
type: 'image/png',
|
||||
},
|
||||
],
|
||||
name: `${name}${isDevelopment ? ' dev' : ''}`,
|
||||
short_name: `${name}${isDevelopment ? ' dev' : ''}`,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* importmap CDN 暂时不开启,因为有些包不支持,且网络不稳定
|
||||
*/
|
||||
const defaultImportmapOptions: ImportmapPluginOptions = {
|
||||
// 通过 Importmap CDN 方式引入,
|
||||
// 目前只有esm.sh源兼容性好一点,jspm.io对于 esm 入口要求高
|
||||
defaultProvider: 'esm.sh',
|
||||
importmap: [
|
||||
{ name: 'vue' },
|
||||
{ name: 'pinia' },
|
||||
{ name: 'vue-router' },
|
||||
// { name: 'vue-i18n' },
|
||||
{ name: 'dayjs' },
|
||||
{ name: 'vue-demi' },
|
||||
],
|
||||
};
|
||||
|
||||
export { defaultImportmapOptions, getDefaultPwaOptions };
|
75
internal/vite-config/src/plugins/archiver.ts
Normal file
75
internal/vite-config/src/plugins/archiver.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import type { ArchiverPluginOptions } from '../typing';
|
||||
|
||||
import fs from 'node:fs';
|
||||
import fsp from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import archiver from 'archiver';
|
||||
|
||||
export const viteArchiverPlugin = (
|
||||
options: ArchiverPluginOptions = {},
|
||||
): PluginOption => {
|
||||
return {
|
||||
apply: 'build',
|
||||
closeBundle: {
|
||||
handler() {
|
||||
const { name = 'dist', outputDir = '.' } = options;
|
||||
|
||||
setTimeout(async () => {
|
||||
const folderToZip = 'dist';
|
||||
|
||||
const zipOutputDir = join(process.cwd(), outputDir);
|
||||
const zipOutputPath = join(zipOutputDir, `${name}.zip`);
|
||||
try {
|
||||
await fsp.mkdir(zipOutputDir, { recursive: true });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
await zipFolder(folderToZip, zipOutputPath);
|
||||
console.log(`Folder has been zipped to: ${zipOutputPath}`);
|
||||
} catch (error) {
|
||||
console.error('Error zipping folder:', error);
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
order: 'post',
|
||||
},
|
||||
enforce: 'post',
|
||||
name: 'vite:archiver',
|
||||
};
|
||||
};
|
||||
|
||||
async function zipFolder(
|
||||
folderPath: string,
|
||||
outputPath: string,
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const output = fs.createWriteStream(outputPath);
|
||||
const archive = archiver('zip', {
|
||||
zlib: { level: 9 }, // 设置压缩级别为 9 以实现最高压缩率
|
||||
});
|
||||
|
||||
output.on('close', () => {
|
||||
console.log(
|
||||
`ZIP file created: ${outputPath} (${archive.pointer()} total bytes)`,
|
||||
);
|
||||
resolve();
|
||||
});
|
||||
|
||||
archive.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
archive.pipe(output);
|
||||
|
||||
// 使用 directory 方法以流的方式压缩文件夹,减少内存消耗
|
||||
archive.directory(folderPath, false);
|
||||
|
||||
// 流式处理完成
|
||||
archive.finalize();
|
||||
});
|
||||
}
|
92
internal/vite-config/src/plugins/extra-app-config.ts
Normal file
92
internal/vite-config/src/plugins/extra-app-config.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import {
|
||||
colors,
|
||||
generatorContentHash,
|
||||
readPackageJSON,
|
||||
} from '@vben/node-utils';
|
||||
|
||||
import { loadEnv } from '../utils/env';
|
||||
|
||||
interface PluginOptions {
|
||||
isBuild: boolean;
|
||||
root: string;
|
||||
}
|
||||
|
||||
const GLOBAL_CONFIG_FILE_NAME = '_app.config.js';
|
||||
const VBEN_ADMIN_PRO_APP_CONF = '_VBEN_ADMIN_PRO_APP_CONF_';
|
||||
|
||||
/**
|
||||
* 用于将配置文件抽离出来并注入到项目中
|
||||
* @returns
|
||||
*/
|
||||
|
||||
async function viteExtraAppConfigPlugin({
|
||||
isBuild,
|
||||
root,
|
||||
}: PluginOptions): Promise<PluginOption | undefined> {
|
||||
let publicPath: string;
|
||||
let source: string;
|
||||
|
||||
if (!isBuild) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { version = '' } = await readPackageJSON(root);
|
||||
|
||||
return {
|
||||
async configResolved(config) {
|
||||
publicPath = ensureTrailingSlash(config.base);
|
||||
source = await getConfigSource();
|
||||
},
|
||||
async generateBundle() {
|
||||
try {
|
||||
this.emitFile({
|
||||
fileName: GLOBAL_CONFIG_FILE_NAME,
|
||||
source,
|
||||
type: 'asset',
|
||||
});
|
||||
|
||||
console.log(colors.cyan(`✨configuration file is build successfully!`));
|
||||
} catch (error) {
|
||||
console.log(
|
||||
colors.red(
|
||||
`configuration file configuration file failed to package:\n${error}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
name: 'vite:extra-app-config',
|
||||
async transformIndexHtml(html) {
|
||||
const hash = `v=${version}-${generatorContentHash(source, 8)}`;
|
||||
|
||||
const appConfigSrc = `${publicPath}${GLOBAL_CONFIG_FILE_NAME}?${hash}`;
|
||||
|
||||
return {
|
||||
html,
|
||||
tags: [{ attrs: { src: appConfigSrc }, tag: 'script' }],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function getConfigSource() {
|
||||
const config = await loadEnv();
|
||||
const windowVariable = `window.${VBEN_ADMIN_PRO_APP_CONF}`;
|
||||
// 确保变量不会被修改
|
||||
let source = `${windowVariable}=${JSON.stringify(config)};`;
|
||||
source += `
|
||||
Object.freeze(${windowVariable});
|
||||
Object.defineProperty(window, "${VBEN_ADMIN_PRO_APP_CONF}", {
|
||||
configurable: false,
|
||||
writable: false,
|
||||
});
|
||||
`.replaceAll(/\s/g, '');
|
||||
return source;
|
||||
}
|
||||
|
||||
function ensureTrailingSlash(path: string) {
|
||||
return path.endsWith('/') ? path : `${path}/`;
|
||||
}
|
||||
|
||||
export { viteExtraAppConfigPlugin };
|
245
internal/vite-config/src/plugins/importmap.ts
Normal file
245
internal/vite-config/src/plugins/importmap.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* 参考 https://github.com/jspm/vite-plugin-jspm,调整为需要的功能
|
||||
*/
|
||||
import type { GeneratorOptions } from '@jspm/generator';
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
import { Generator } from '@jspm/generator';
|
||||
import { load } from 'cheerio';
|
||||
import { minify } from 'html-minifier-terser';
|
||||
|
||||
const DEFAULT_PROVIDER = 'jspm.io';
|
||||
|
||||
type pluginOptions = {
|
||||
debug?: boolean;
|
||||
defaultProvider?: 'esm.sh' | 'jsdelivr' | 'jspm.io';
|
||||
importmap?: Array<{ name: string; range?: string }>;
|
||||
} & GeneratorOptions;
|
||||
|
||||
// async function getLatestVersionOfShims() {
|
||||
// const result = await fetch('https://ga.jspm.io/npm:es-module-shims');
|
||||
// const version = result.text();
|
||||
// return version;
|
||||
// }
|
||||
|
||||
async function getShimsUrl(provide: string) {
|
||||
// const version = await getLatestVersionOfShims();
|
||||
const version = '1.10.0';
|
||||
|
||||
const shimsSubpath = `dist/es-module-shims.js`;
|
||||
const providerShimsMap: Record<string, string> = {
|
||||
'esm.sh': `https://esm.sh/es-module-shims@${version}/${shimsSubpath}`,
|
||||
// unpkg: `https://unpkg.com/es-module-shims@${version}/${shimsSubpath}`,
|
||||
jsdelivr: `https://cdn.jsdelivr.net/npm/es-module-shims@${version}/${shimsSubpath}`,
|
||||
|
||||
// 下面两个CDN不稳定,暂时不用
|
||||
'jspm.io': `https://ga.jspm.io/npm:es-module-shims@${version}/${shimsSubpath}`,
|
||||
};
|
||||
|
||||
return providerShimsMap[provide] || providerShimsMap[DEFAULT_PROVIDER];
|
||||
}
|
||||
|
||||
let generator: Generator;
|
||||
|
||||
async function viteImportMapPlugin(
|
||||
pluginOptions?: pluginOptions,
|
||||
): Promise<Plugin[]> {
|
||||
const { importmap } = pluginOptions || {};
|
||||
|
||||
let isSSR = false;
|
||||
let isBuild = false;
|
||||
let installed = false;
|
||||
let installError: Error | null = null;
|
||||
|
||||
const options: pluginOptions = Object.assign(
|
||||
{},
|
||||
{
|
||||
debug: false,
|
||||
defaultProvider: 'jspm.io',
|
||||
env: ['production', 'browser', 'module'],
|
||||
importmap: [],
|
||||
},
|
||||
pluginOptions,
|
||||
);
|
||||
|
||||
generator = new Generator({
|
||||
...options,
|
||||
baseUrl: process.cwd(),
|
||||
});
|
||||
|
||||
if (options?.debug) {
|
||||
(async () => {
|
||||
for await (const { message, type } of generator.logStream()) {
|
||||
console.log(`${type}: ${message}`);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
const imports = options.inputMap?.imports ?? {};
|
||||
const scopes = options.inputMap?.scopes ?? {};
|
||||
const firstLayerKeys = Object.keys(scopes);
|
||||
const inputMapScopes: string[] = [];
|
||||
firstLayerKeys.forEach((key) => {
|
||||
inputMapScopes.push(...Object.keys(scopes[key] || {}));
|
||||
});
|
||||
const inputMapImports = Object.keys(imports);
|
||||
|
||||
const allDepNames: string[] = [
|
||||
...(importmap?.map((item) => item.name) || []),
|
||||
...inputMapImports,
|
||||
...inputMapScopes,
|
||||
];
|
||||
const depNames = new Set<string>(allDepNames);
|
||||
|
||||
const installDeps = importmap?.map((item) => ({
|
||||
range: item.range,
|
||||
target: item.name,
|
||||
}));
|
||||
|
||||
return [
|
||||
{
|
||||
async config(_, { command, isSsrBuild }) {
|
||||
isBuild = command === 'build';
|
||||
isSSR = !!isSsrBuild;
|
||||
},
|
||||
enforce: 'pre',
|
||||
name: 'importmap:external',
|
||||
resolveId(id) {
|
||||
if (isSSR || !isBuild) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!depNames.has(id)) {
|
||||
return null;
|
||||
}
|
||||
return { external: true, id };
|
||||
},
|
||||
},
|
||||
{
|
||||
enforce: 'post',
|
||||
name: 'importmap:install',
|
||||
async resolveId() {
|
||||
if (isSSR || !isBuild || installed) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
installed = true;
|
||||
await Promise.allSettled(
|
||||
(installDeps || []).map((dep) => generator.install(dep)),
|
||||
);
|
||||
} catch (error: any) {
|
||||
installError = error;
|
||||
installed = false;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
buildEnd() {
|
||||
// 未生成importmap时,抛出错误,防止被turbo缓存
|
||||
if (!installed && !isSSR) {
|
||||
installError && console.error(installError);
|
||||
throw new Error('Importmap installation failed.');
|
||||
}
|
||||
},
|
||||
enforce: 'post',
|
||||
name: 'importmap:html',
|
||||
transformIndexHtml: {
|
||||
async handler(html) {
|
||||
if (isSSR || !isBuild) {
|
||||
return html;
|
||||
}
|
||||
|
||||
const importmapJson = generator.getMap();
|
||||
|
||||
if (!importmapJson) {
|
||||
return html;
|
||||
}
|
||||
|
||||
const esModuleShimsSrc = await getShimsUrl(
|
||||
options.defaultProvider || DEFAULT_PROVIDER,
|
||||
);
|
||||
|
||||
const resultHtml = await injectShimsToHtml(
|
||||
html,
|
||||
esModuleShimsSrc || '',
|
||||
);
|
||||
html = await minify(resultHtml || html, {
|
||||
collapseWhitespace: true,
|
||||
minifyCSS: true,
|
||||
minifyJS: true,
|
||||
removeComments: false,
|
||||
});
|
||||
|
||||
return {
|
||||
html,
|
||||
tags: [
|
||||
{
|
||||
attrs: {
|
||||
type: 'importmap',
|
||||
},
|
||||
injectTo: 'head-prepend',
|
||||
tag: 'script',
|
||||
children: `${JSON.stringify(importmapJson)}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
order: 'post',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async function injectShimsToHtml(html: string, esModuleShimUrl: string) {
|
||||
const $ = load(html);
|
||||
|
||||
const $script = $(`script[type='module']`);
|
||||
|
||||
if (!$script) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = $script.attr('src');
|
||||
|
||||
$script.removeAttr('type');
|
||||
$script.removeAttr('crossorigin');
|
||||
$script.removeAttr('src');
|
||||
$script.html(`
|
||||
if (!HTMLScriptElement.supports || !HTMLScriptElement.supports('importmap')) {
|
||||
self.importShim = function () {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
document.head.appendChild(
|
||||
Object.assign(document.createElement('script'), {
|
||||
src: '${esModuleShimUrl}',
|
||||
crossorigin: 'anonymous',
|
||||
async: true,
|
||||
onload() {
|
||||
if (!importShim.$proxy) {
|
||||
resolve(importShim);
|
||||
} else {
|
||||
reject(new Error('No globalThis.importShim found:' + esModuleShimUrl));
|
||||
}
|
||||
},
|
||||
onerror(error) {
|
||||
reject(error);
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
importShim.$proxy = true;
|
||||
return promise.then((importShim) => importShim(...arguments));
|
||||
};
|
||||
}
|
||||
|
||||
var modules = ['${entry}'];
|
||||
typeof importShim === 'function'
|
||||
? modules.forEach((moduleName) => importShim(moduleName))
|
||||
: modules.forEach((moduleName) => import(moduleName));
|
||||
`);
|
||||
$('body').after($script);
|
||||
$('head').remove(`script[type='module']`);
|
||||
return $.html();
|
||||
}
|
||||
|
||||
export { viteImportMapPlugin };
|
247
internal/vite-config/src/plugins/index.ts
Normal file
247
internal/vite-config/src/plugins/index.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import type {
|
||||
ApplicationPluginOptions,
|
||||
CommonPluginOptions,
|
||||
ConditionPlugin,
|
||||
LibraryPluginOptions,
|
||||
} from '../typing';
|
||||
|
||||
import viteVueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
|
||||
import viteVue from '@vitejs/plugin-vue';
|
||||
import viteVueJsx from '@vitejs/plugin-vue-jsx';
|
||||
import { visualizer as viteVisualizerPlugin } from 'rollup-plugin-visualizer';
|
||||
import viteCompressPlugin from 'vite-plugin-compression';
|
||||
import viteDtsPlugin from 'vite-plugin-dts';
|
||||
import { createHtmlPlugin as viteHtmlPlugin } from 'vite-plugin-html';
|
||||
import { VitePWA } from 'vite-plugin-pwa';
|
||||
import viteVueDevTools from 'vite-plugin-vue-devtools';
|
||||
|
||||
import { viteArchiverPlugin } from './archiver';
|
||||
import { viteExtraAppConfigPlugin } from './extra-app-config';
|
||||
import { viteImportMapPlugin } from './importmap';
|
||||
import { viteInjectAppLoadingPlugin } from './inject-app-loading';
|
||||
import { viteMetadataPlugin } from './inject-metadata';
|
||||
import { viteLicensePlugin } from './license';
|
||||
import { viteNitroMockPlugin } from './nitro-mock';
|
||||
import { vitePrintPlugin } from './print';
|
||||
import { viteVxeTableImportsPlugin } from './vxe-table';
|
||||
|
||||
/**
|
||||
* 获取条件成立的 vite 插件
|
||||
* @param conditionPlugins
|
||||
*/
|
||||
async function loadConditionPlugins(conditionPlugins: ConditionPlugin[]) {
|
||||
const plugins: PluginOption[] = [];
|
||||
for (const conditionPlugin of conditionPlugins) {
|
||||
if (conditionPlugin.condition) {
|
||||
const realPlugins = await conditionPlugin.plugins();
|
||||
plugins.push(...realPlugins);
|
||||
}
|
||||
}
|
||||
return plugins.flat();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件获取通用的vite插件
|
||||
*/
|
||||
async function loadCommonPlugins(
|
||||
options: CommonPluginOptions,
|
||||
): Promise<ConditionPlugin[]> {
|
||||
const { devtools, injectMetadata, isBuild, visualizer } = options;
|
||||
return [
|
||||
{
|
||||
condition: true,
|
||||
plugins: () => [
|
||||
viteVue({
|
||||
script: {
|
||||
defineModel: true,
|
||||
// propsDestructure: true,
|
||||
},
|
||||
}),
|
||||
viteVueJsx(),
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
condition: !isBuild && devtools,
|
||||
plugins: () => [viteVueDevTools()],
|
||||
},
|
||||
{
|
||||
condition: injectMetadata,
|
||||
plugins: async () => [await viteMetadataPlugin()],
|
||||
},
|
||||
{
|
||||
condition: isBuild && !!visualizer,
|
||||
plugins: () => [<PluginOption>viteVisualizerPlugin({
|
||||
filename: './node_modules/.cache/visualizer/stats.html',
|
||||
gzipSize: true,
|
||||
open: true,
|
||||
})],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件获取应用类型的vite插件
|
||||
*/
|
||||
async function loadApplicationPlugins(
|
||||
options: ApplicationPluginOptions,
|
||||
): Promise<PluginOption[]> {
|
||||
// 单独取,否则commonOptions拿不到
|
||||
const isBuild = options.isBuild;
|
||||
const env = options.env;
|
||||
|
||||
const {
|
||||
archiver,
|
||||
archiverPluginOptions,
|
||||
compress,
|
||||
compressTypes,
|
||||
extraAppConfig,
|
||||
html,
|
||||
i18n,
|
||||
importmap,
|
||||
importmapOptions,
|
||||
injectAppLoading,
|
||||
license,
|
||||
nitroMock,
|
||||
nitroMockOptions,
|
||||
print,
|
||||
printInfoMap,
|
||||
pwa,
|
||||
pwaOptions,
|
||||
vxeTableLazyImport,
|
||||
...commonOptions
|
||||
} = options;
|
||||
|
||||
const commonPlugins = await loadCommonPlugins(commonOptions);
|
||||
|
||||
return await loadConditionPlugins([
|
||||
...commonPlugins,
|
||||
{
|
||||
condition: i18n,
|
||||
plugins: async () => {
|
||||
return [
|
||||
viteVueI18nPlugin({
|
||||
compositionOnly: true,
|
||||
fullInstall: true,
|
||||
runtimeOnly: true,
|
||||
}),
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: print,
|
||||
plugins: async () => {
|
||||
return [await vitePrintPlugin({ infoMap: printInfoMap })];
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: vxeTableLazyImport,
|
||||
plugins: async () => {
|
||||
return [await viteVxeTableImportsPlugin()];
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: nitroMock,
|
||||
plugins: async () => {
|
||||
return [await viteNitroMockPlugin(nitroMockOptions)];
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
condition: injectAppLoading,
|
||||
plugins: async () => [await viteInjectAppLoadingPlugin(!!isBuild, env)],
|
||||
},
|
||||
{
|
||||
condition: license,
|
||||
plugins: async () => [await viteLicensePlugin()],
|
||||
},
|
||||
{
|
||||
condition: pwa,
|
||||
plugins: () =>
|
||||
VitePWA({
|
||||
injectRegister: false,
|
||||
workbox: {
|
||||
globPatterns: [],
|
||||
},
|
||||
...pwaOptions,
|
||||
manifest: {
|
||||
display: 'standalone',
|
||||
start_url: '/',
|
||||
theme_color: '#ffffff',
|
||||
...pwaOptions?.manifest,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
condition: isBuild && !!compress,
|
||||
plugins: () => {
|
||||
const compressPlugins: PluginOption[] = [];
|
||||
if (compressTypes?.includes('brotli')) {
|
||||
compressPlugins.push(
|
||||
viteCompressPlugin({ deleteOriginFile: false, ext: '.br' }),
|
||||
);
|
||||
}
|
||||
if (compressTypes?.includes('gzip')) {
|
||||
compressPlugins.push(
|
||||
viteCompressPlugin({ deleteOriginFile: false, ext: '.gz' }),
|
||||
);
|
||||
}
|
||||
return compressPlugins;
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: !!html,
|
||||
plugins: () => [viteHtmlPlugin({ minify: true })],
|
||||
},
|
||||
{
|
||||
condition: isBuild && importmap,
|
||||
plugins: () => {
|
||||
return [viteImportMapPlugin(importmapOptions)];
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: isBuild && extraAppConfig,
|
||||
plugins: async () => [
|
||||
await viteExtraAppConfigPlugin({ isBuild: true, root: process.cwd() }),
|
||||
],
|
||||
},
|
||||
{
|
||||
condition: archiver,
|
||||
plugins: async () => {
|
||||
return [await viteArchiverPlugin(archiverPluginOptions)];
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件获取库类型的vite插件
|
||||
*/
|
||||
async function loadLibraryPlugins(
|
||||
options: LibraryPluginOptions,
|
||||
): Promise<PluginOption[]> {
|
||||
// 单独取,否则commonOptions拿不到
|
||||
const isBuild = options.isBuild;
|
||||
const { dts, ...commonOptions } = options;
|
||||
const commonPlugins = await loadCommonPlugins(commonOptions);
|
||||
return await loadConditionPlugins([
|
||||
...commonPlugins,
|
||||
{
|
||||
condition: isBuild && !!dts,
|
||||
plugins: () => [viteDtsPlugin({ logLevel: 'error' })],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
export {
|
||||
loadApplicationPlugins,
|
||||
loadLibraryPlugins,
|
||||
viteArchiverPlugin,
|
||||
viteCompressPlugin,
|
||||
viteDtsPlugin,
|
||||
viteHtmlPlugin,
|
||||
viteVisualizerPlugin,
|
||||
viteVxeTableImportsPlugin,
|
||||
};
|
@@ -0,0 +1,3 @@
|
||||
# inject-app-loading
|
||||
|
||||
用于在应用加载时显示加载动画的插件,可自行选择加载动画的样式。
|
@@ -0,0 +1,107 @@
|
||||
<style data-app-loading="inject-css">
|
||||
html {
|
||||
/* same as ant-design-vue/dist/reset.css setting, avoid the title line-height changed */
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.dark .loading {
|
||||
background-color: #0d0d10;
|
||||
}
|
||||
|
||||
.dark .loading .title {
|
||||
color: rgb(255 255 255 / 85%);
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
background-color: #f4f7f9;
|
||||
}
|
||||
|
||||
.loading.hidden {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.6s ease-out;
|
||||
}
|
||||
|
||||
.loading .title {
|
||||
margin-top: 36px;
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
color: rgb(0 0 0 / 85%);
|
||||
}
|
||||
|
||||
.dot {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-top: 30px;
|
||||
font-size: 32px;
|
||||
transform: rotate(45deg);
|
||||
animation: rotate-ani 1.2s infinite linear;
|
||||
}
|
||||
|
||||
.dot i {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: hsl(var(--primary, 210 100% 50%));
|
||||
border-radius: 100%;
|
||||
opacity: 0.3;
|
||||
transform: scale(0.75);
|
||||
transform-origin: 50% 50%;
|
||||
animation: spin-move-ani 1s infinite linear alternate;
|
||||
}
|
||||
|
||||
.dot i:nth-child(1) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.dot i:nth-child(2) {
|
||||
top: 0;
|
||||
right: 0;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.dot i:nth-child(3) {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
|
||||
.dot i:nth-child(4) {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
|
||||
@keyframes rotate-ani {
|
||||
to {
|
||||
transform: rotate(405deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin-move-ani {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="loading" id="__app-loading__">
|
||||
<span class="dot"><i></i><i></i><i></i><i></i></span>
|
||||
<div class="title"><%= VITE_APP_TITLE %></div>
|
||||
</div>
|
@@ -0,0 +1,113 @@
|
||||
<style data-app-loading="inject-css">
|
||||
html {
|
||||
/* same as ant-design-vue/dist/reset.css setting, avoid the title line-height changed */
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: #f4f7f9;
|
||||
|
||||
/* transition: all 0.8s ease-out; */
|
||||
}
|
||||
|
||||
.loading.hidden {
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.8s ease-out;
|
||||
}
|
||||
|
||||
.dark .loading {
|
||||
background: #0d0d10;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-top: 66px;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: rgb(0 0 0 / 85%);
|
||||
}
|
||||
|
||||
.dark .title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.loader {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.loader::before {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
left: 0;
|
||||
width: 48px;
|
||||
height: 5px;
|
||||
content: '';
|
||||
background: hsl(var(--primary, 210 100% 50%) / 50%);
|
||||
border-radius: 50%;
|
||||
animation: shadow-ani 0.5s linear infinite;
|
||||
}
|
||||
|
||||
.loader::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: '';
|
||||
background: hsl(var(--primary, 210 100% 50%));
|
||||
border-radius: 4px;
|
||||
animation: jump-ani 0.5s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes jump-ani {
|
||||
15% {
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: translateY(9px) rotate(22.5deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
border-bottom-right-radius: 40px;
|
||||
transform: translateY(18px) scale(1, 0.9) rotate(45deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translateY(9px) rotate(67.5deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0) rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shadow-ani {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.2, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="loading" id="__app-loading__">
|
||||
<div class="loader"></div>
|
||||
<div class="title"><%= VITE_APP_TITLE %></div>
|
||||
</div>
|
66
internal/vite-config/src/plugins/inject-app-loading/index.ts
Normal file
66
internal/vite-config/src/plugins/inject-app-loading/index.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import fs from 'node:fs';
|
||||
import fsp from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { readPackageJSON } from '@vben/node-utils';
|
||||
|
||||
/**
|
||||
* 用于生成将loading样式注入到项目中
|
||||
* 为多app提供loading样式,无需在每个 app -> index.html单独引入
|
||||
*/
|
||||
async function viteInjectAppLoadingPlugin(
|
||||
isBuild: boolean,
|
||||
env: Record<string, any> = {},
|
||||
loadingTemplate = 'loading.html',
|
||||
): Promise<PluginOption | undefined> {
|
||||
const loadingHtml = await getLoadingRawByHtmlTemplate(loadingTemplate);
|
||||
const { version } = await readPackageJSON(process.cwd());
|
||||
const envRaw = isBuild ? 'prod' : 'dev';
|
||||
const cacheName = `'${env.VITE_APP_NAMESPACE}-${version}-${envRaw}-preferences-theme'`;
|
||||
|
||||
// 获取缓存的主题
|
||||
// 保证黑暗主题下,刷新页面时,loading也是黑暗主题
|
||||
const injectScript = `
|
||||
<script data-app-loading="inject-js">
|
||||
var theme = localStorage.getItem(${cacheName});
|
||||
document.documentElement.classList.toggle('dark', /dark/.test(theme));
|
||||
</script>
|
||||
`;
|
||||
|
||||
if (!loadingHtml) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
enforce: 'pre',
|
||||
name: 'vite:inject-app-loading',
|
||||
transformIndexHtml: {
|
||||
handler(html) {
|
||||
const re = /<body\s*>/;
|
||||
html = html.replace(re, `<body>${injectScript}${loadingHtml}`);
|
||||
return html;
|
||||
},
|
||||
order: 'pre',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于获取loading的html模板
|
||||
*/
|
||||
async function getLoadingRawByHtmlTemplate(loadingTemplate: string) {
|
||||
// 支持在app内自定义loading模板,模版参考default-loading.html即可
|
||||
let appLoadingPath = join(process.cwd(), loadingTemplate);
|
||||
|
||||
if (!fs.existsSync(appLoadingPath)) {
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
appLoadingPath = join(__dirname, './default-loading.html');
|
||||
}
|
||||
|
||||
return await fsp.readFile(appLoadingPath, 'utf8');
|
||||
}
|
||||
|
||||
export { viteInjectAppLoadingPlugin };
|
111
internal/vite-config/src/plugins/inject-metadata.ts
Normal file
111
internal/vite-config/src/plugins/inject-metadata.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import {
|
||||
dateUtil,
|
||||
findMonorepoRoot,
|
||||
getPackages,
|
||||
readPackageJSON,
|
||||
} from '@vben/node-utils';
|
||||
|
||||
import { readWorkspaceManifest } from '@pnpm/workspace.read-manifest';
|
||||
|
||||
function resolvePackageVersion(
|
||||
pkgsMeta: Record<string, string>,
|
||||
name: string,
|
||||
value: string,
|
||||
catalog: Record<string, string>,
|
||||
) {
|
||||
if (value.includes('catalog:')) {
|
||||
return catalog[name];
|
||||
}
|
||||
|
||||
if (value.includes('workspace')) {
|
||||
return pkgsMeta[name];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async function resolveMonorepoDependencies() {
|
||||
const { packages } = await getPackages();
|
||||
const manifest = await readWorkspaceManifest(findMonorepoRoot());
|
||||
const catalog = manifest?.catalog || {};
|
||||
|
||||
const resultDevDependencies: Record<string, string | undefined> = {};
|
||||
const resultDependencies: Record<string, string | undefined> = {};
|
||||
const pkgsMeta: Record<string, string> = {};
|
||||
|
||||
for (const { packageJson } of packages) {
|
||||
pkgsMeta[packageJson.name] = packageJson.version;
|
||||
}
|
||||
|
||||
for (const { packageJson } of packages) {
|
||||
const { dependencies = {}, devDependencies = {} } = packageJson;
|
||||
for (const [key, value] of Object.entries(dependencies)) {
|
||||
resultDependencies[key] = resolvePackageVersion(
|
||||
pkgsMeta,
|
||||
key,
|
||||
value,
|
||||
catalog,
|
||||
);
|
||||
}
|
||||
for (const [key, value] of Object.entries(devDependencies)) {
|
||||
resultDevDependencies[key] = resolvePackageVersion(
|
||||
pkgsMeta,
|
||||
key,
|
||||
value,
|
||||
catalog,
|
||||
);
|
||||
}
|
||||
}
|
||||
return {
|
||||
dependencies: resultDependencies,
|
||||
devDependencies: resultDevDependencies,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于注入项目信息
|
||||
*/
|
||||
async function viteMetadataPlugin(
|
||||
root = process.cwd(),
|
||||
): Promise<PluginOption | undefined> {
|
||||
const { author, description, homepage, license, version } =
|
||||
await readPackageJSON(root);
|
||||
|
||||
const buildTime = dateUtil().format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
return {
|
||||
async config() {
|
||||
const { dependencies, devDependencies } =
|
||||
await resolveMonorepoDependencies();
|
||||
|
||||
const isAuthorObject = typeof author === 'object';
|
||||
const authorName = isAuthorObject ? author.name : author;
|
||||
const authorEmail = isAuthorObject ? author.email : null;
|
||||
const authorUrl = isAuthorObject ? author.url : null;
|
||||
|
||||
return {
|
||||
define: {
|
||||
__VBEN_ADMIN_METADATA__: JSON.stringify({
|
||||
authorEmail,
|
||||
authorName,
|
||||
authorUrl,
|
||||
buildTime,
|
||||
dependencies,
|
||||
description,
|
||||
devDependencies,
|
||||
homepage,
|
||||
license,
|
||||
version,
|
||||
}),
|
||||
'import.meta.env.VITE_APP_VERSION': JSON.stringify(version),
|
||||
},
|
||||
};
|
||||
},
|
||||
enforce: 'post',
|
||||
name: 'vite:inject-metadata',
|
||||
};
|
||||
}
|
||||
|
||||
export { viteMetadataPlugin };
|
63
internal/vite-config/src/plugins/license.ts
Normal file
63
internal/vite-config/src/plugins/license.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type {
|
||||
NormalizedOutputOptions,
|
||||
OutputBundle,
|
||||
OutputChunk,
|
||||
} from 'rollup';
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import { EOL } from 'node:os';
|
||||
|
||||
import { dateUtil, readPackageJSON } from '@vben/node-utils';
|
||||
|
||||
/**
|
||||
* 用于注入版权信息
|
||||
* @returns
|
||||
*/
|
||||
|
||||
async function viteLicensePlugin(
|
||||
root = process.cwd(),
|
||||
): Promise<PluginOption | undefined> {
|
||||
const {
|
||||
description = '',
|
||||
homepage = '',
|
||||
version = '',
|
||||
} = await readPackageJSON(root);
|
||||
|
||||
return {
|
||||
apply: 'build',
|
||||
enforce: 'post',
|
||||
generateBundle: {
|
||||
handler: (_options: NormalizedOutputOptions, bundle: OutputBundle) => {
|
||||
const date = dateUtil().format('YYYY-MM-DD ');
|
||||
const copyrightText = `/*!
|
||||
* Vben Admin
|
||||
* Version: ${version}
|
||||
* Author: vben
|
||||
* Copyright (C) 2024 Vben
|
||||
* License: MIT License
|
||||
* Description: ${description}
|
||||
* Date Created: ${date}
|
||||
* Homepage: ${homepage}
|
||||
* Contact: ann.vben@gmail.com
|
||||
*/
|
||||
`.trim();
|
||||
|
||||
for (const [, fileContent] of Object.entries(bundle)) {
|
||||
if (fileContent.type === 'chunk' && fileContent.isEntry) {
|
||||
const chunkContent = fileContent as OutputChunk;
|
||||
// 插入版权信息
|
||||
const content = chunkContent.code;
|
||||
const updatedContent = `${copyrightText}${EOL}${content}`;
|
||||
|
||||
// 更新bundle
|
||||
(fileContent as OutputChunk).code = updatedContent;
|
||||
}
|
||||
}
|
||||
},
|
||||
order: 'post',
|
||||
},
|
||||
name: 'vite:license',
|
||||
};
|
||||
}
|
||||
|
||||
export { viteLicensePlugin };
|
98
internal/vite-config/src/plugins/nitro-mock.ts
Normal file
98
internal/vite-config/src/plugins/nitro-mock.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import type { NitroMockPluginOptions } from '../typing';
|
||||
|
||||
import { colors, consola, getPackage } from '@vben/node-utils';
|
||||
|
||||
import getPort from 'get-port';
|
||||
import { build, createDevServer, createNitro, prepare } from 'nitropack';
|
||||
|
||||
const hmrKeyRe = /^runtimeConfig\.|routeRules\./;
|
||||
|
||||
export const viteNitroMockPlugin = ({
|
||||
mockServerPackage = '@vben/backend-mock',
|
||||
port = 5320,
|
||||
verbose = true,
|
||||
}: NitroMockPluginOptions = {}): PluginOption => {
|
||||
return {
|
||||
async configureServer(server) {
|
||||
const availablePort = await getPort({ port });
|
||||
if (availablePort !== port) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pkg = await getPackage(mockServerPackage);
|
||||
if (!pkg) {
|
||||
consola.log(
|
||||
`Package ${mockServerPackage} not found. Skip mock server.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
runNitroServer(pkg.dir, port, verbose);
|
||||
|
||||
const _printUrls = server.printUrls;
|
||||
server.printUrls = () => {
|
||||
_printUrls();
|
||||
|
||||
consola.log(
|
||||
` ${colors.green('➜')} ${colors.bold('Nitro Mock Server')}: ${colors.cyan(`http://localhost:${port}/api`)}`,
|
||||
);
|
||||
};
|
||||
},
|
||||
enforce: 'pre',
|
||||
name: 'vite:mock-server',
|
||||
};
|
||||
};
|
||||
|
||||
async function runNitroServer(rootDir: string, port: number, verbose: boolean) {
|
||||
let nitro: any;
|
||||
const reload = async () => {
|
||||
if (nitro) {
|
||||
consola.info('Restarting dev server...');
|
||||
if ('unwatch' in nitro.options._c12) {
|
||||
await nitro.options._c12.unwatch();
|
||||
}
|
||||
await nitro.close();
|
||||
}
|
||||
nitro = await createNitro(
|
||||
{
|
||||
dev: true,
|
||||
preset: 'nitro-dev',
|
||||
rootDir,
|
||||
},
|
||||
{
|
||||
c12: {
|
||||
async onUpdate({ getDiff, newConfig }) {
|
||||
const diff = getDiff();
|
||||
if (diff.length === 0) {
|
||||
return;
|
||||
}
|
||||
verbose &&
|
||||
consola.info(
|
||||
`Nitro config updated:\n${diff
|
||||
.map((entry) => ` ${entry.toString()}`)
|
||||
.join('\n')}`,
|
||||
);
|
||||
await (diff.every((e) => hmrKeyRe.test(e.key))
|
||||
? nitro.updateConfig(newConfig.config)
|
||||
: reload());
|
||||
},
|
||||
},
|
||||
watch: true,
|
||||
},
|
||||
);
|
||||
nitro.hooks.hookOnce('restart', reload);
|
||||
|
||||
const server = createDevServer(nitro);
|
||||
await server.listen(port, { showURL: false });
|
||||
await prepare(nitro);
|
||||
await build(nitro);
|
||||
|
||||
if (verbose) {
|
||||
console.log('');
|
||||
consola.success(colors.bold(colors.green('Nitro Mock Server started.')));
|
||||
}
|
||||
};
|
||||
return await reload();
|
||||
}
|
28
internal/vite-config/src/plugins/print.ts
Normal file
28
internal/vite-config/src/plugins/print.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import type { PrintPluginOptions } from '../typing';
|
||||
|
||||
import { colors } from '@vben/node-utils';
|
||||
|
||||
export const vitePrintPlugin = (
|
||||
options: PrintPluginOptions = {},
|
||||
): PluginOption => {
|
||||
const { infoMap = {} } = options;
|
||||
|
||||
return {
|
||||
configureServer(server) {
|
||||
const _printUrls = server.printUrls;
|
||||
server.printUrls = () => {
|
||||
_printUrls();
|
||||
|
||||
for (const [key, value] of Object.entries(infoMap)) {
|
||||
console.log(
|
||||
` ${colors.green('➜')} ${colors.bold(key)}: ${colors.cyan(value)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
enforce: 'pre',
|
||||
name: 'vite:print-info',
|
||||
};
|
||||
};
|
20
internal/vite-config/src/plugins/vxe-table.ts
Normal file
20
internal/vite-config/src/plugins/vxe-table.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import { lazyImport, VxeResolver } from 'vite-plugin-lazy-import';
|
||||
|
||||
async function viteVxeTableImportsPlugin(): Promise<PluginOption> {
|
||||
return [
|
||||
lazyImport({
|
||||
resolvers: [
|
||||
VxeResolver({
|
||||
libraryName: 'vxe-table',
|
||||
}),
|
||||
VxeResolver({
|
||||
libraryName: 'vxe-pc-ui',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
export { viteVxeTableImportsPlugin };
|
343
internal/vite-config/src/typing.ts
Normal file
343
internal/vite-config/src/typing.ts
Normal file
@@ -0,0 +1,343 @@
|
||||
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
|
||||
import type { ConfigEnv, PluginOption, UserConfig } from 'vite';
|
||||
import type { PluginOptions } from 'vite-plugin-dts';
|
||||
import type { Options as PwaPluginOptions } from 'vite-plugin-pwa';
|
||||
|
||||
/**
|
||||
* ImportMap 配置接口
|
||||
* @description 用于配置模块导入映射,支持自定义导入路径和范围
|
||||
* @example
|
||||
* ```typescript
|
||||
* {
|
||||
* imports: {
|
||||
* 'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js'
|
||||
* },
|
||||
* scopes: {
|
||||
* 'https://site.com/': {
|
||||
* 'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js'
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
interface IImportMap {
|
||||
/** 模块导入映射 */
|
||||
imports?: Record<string, string>;
|
||||
/** 作用域特定的导入映射 */
|
||||
scopes?: {
|
||||
[scope: string]: Record<string, string>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印插件配置选项
|
||||
* @description 用于配置控制台打印信息
|
||||
*/
|
||||
interface PrintPluginOptions {
|
||||
/**
|
||||
* 打印的数据映射
|
||||
* @description 键值对形式的数据,将在控制台打印
|
||||
* @example
|
||||
* ```typescript
|
||||
* {
|
||||
* 'App Version': '1.0.0',
|
||||
* 'Build Time': '2024-01-01'
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
infoMap?: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nitro Mock 插件配置选项
|
||||
* @description 用于配置 Nitro Mock 服务器的行为
|
||||
*/
|
||||
interface NitroMockPluginOptions {
|
||||
/**
|
||||
* Mock 服务器包名
|
||||
* @default '@vbenjs/nitro-mock'
|
||||
*/
|
||||
mockServerPackage?: string;
|
||||
|
||||
/**
|
||||
* Mock 服务端口
|
||||
* @default 3000
|
||||
*/
|
||||
port?: number;
|
||||
|
||||
/**
|
||||
* 是否打印 Mock 日志
|
||||
* @default false
|
||||
*/
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 归档插件配置选项
|
||||
* @description 用于配置构建产物的压缩归档
|
||||
*/
|
||||
interface ArchiverPluginOptions {
|
||||
/**
|
||||
* 输出文件名
|
||||
* @default 'dist'
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 输出目录
|
||||
* @default '.'
|
||||
*/
|
||||
outputDir?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ImportMap 插件配置
|
||||
* @description 用于配置模块的 CDN 导入
|
||||
*/
|
||||
interface ImportmapPluginOptions {
|
||||
/**
|
||||
* CDN 供应商
|
||||
* @default 'jspm.io'
|
||||
* @description 支持 esm.sh 和 jspm.io 两种 CDN 供应商
|
||||
*/
|
||||
defaultProvider?: 'esm.sh' | 'jspm.io';
|
||||
/**
|
||||
* ImportMap 配置数组
|
||||
* @description 配置需要从 CDN 导入的包
|
||||
* @example
|
||||
* ```typescript
|
||||
* [
|
||||
* { name: 'vue' },
|
||||
* { name: 'pinia', range: '^2.0.0' }
|
||||
* ]
|
||||
* ```
|
||||
*/
|
||||
importmap?: Array<{ name: string; range?: string }>;
|
||||
/**
|
||||
* 手动配置 ImportMap
|
||||
* @description 自定义 ImportMap 配置
|
||||
*/
|
||||
inputMap?: IImportMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件插件配置
|
||||
* @description 用于根据条件动态加载插件
|
||||
*/
|
||||
interface ConditionPlugin {
|
||||
/**
|
||||
* 判断条件
|
||||
* @description 当条件为 true 时加载插件
|
||||
*/
|
||||
condition?: boolean;
|
||||
/**
|
||||
* 插件对象
|
||||
* @description 返回插件数组或 Promise
|
||||
*/
|
||||
plugins: () => PluginOption[] | PromiseLike<PluginOption[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用插件配置选项
|
||||
* @description 所有插件共用的基础配置
|
||||
*/
|
||||
interface CommonPluginOptions {
|
||||
/**
|
||||
* 是否开启开发工具
|
||||
* @default false
|
||||
*/
|
||||
devtools?: boolean;
|
||||
/**
|
||||
* 环境变量
|
||||
* @description 自定义环境变量
|
||||
*/
|
||||
env?: Record<string, any>;
|
||||
/**
|
||||
* 是否注入元数据
|
||||
* @default true
|
||||
*/
|
||||
injectMetadata?: boolean;
|
||||
/**
|
||||
* 是否为构建模式
|
||||
* @default false
|
||||
*/
|
||||
isBuild?: boolean;
|
||||
/**
|
||||
* 构建模式
|
||||
* @default 'development'
|
||||
*/
|
||||
mode?: string;
|
||||
/**
|
||||
* 是否开启依赖分析
|
||||
* @default false
|
||||
* @description 使用 rollup-plugin-visualizer 分析依赖
|
||||
*/
|
||||
visualizer?: boolean | PluginVisualizerOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用插件配置选项
|
||||
* @description 用于配置应用构建时的插件选项
|
||||
*/
|
||||
interface ApplicationPluginOptions extends CommonPluginOptions {
|
||||
/**
|
||||
* 是否开启压缩归档
|
||||
* @default false
|
||||
* @description 开启后会在打包目录生成 zip 文件
|
||||
*/
|
||||
archiver?: boolean;
|
||||
/**
|
||||
* 压缩归档插件配置
|
||||
* @description 配置压缩归档的行为
|
||||
*/
|
||||
archiverPluginOptions?: ArchiverPluginOptions;
|
||||
/**
|
||||
* 是否开启压缩
|
||||
* @default false
|
||||
* @description 支持 gzip 和 brotli 压缩
|
||||
*/
|
||||
compress?: boolean;
|
||||
/**
|
||||
* 压缩类型
|
||||
* @default ['gzip']
|
||||
* @description 可选的压缩类型
|
||||
*/
|
||||
compressTypes?: ('brotli' | 'gzip')[];
|
||||
/**
|
||||
* 是否抽离配置文件
|
||||
* @default false
|
||||
* @description 在构建时抽离配置文件
|
||||
*/
|
||||
extraAppConfig?: boolean;
|
||||
/**
|
||||
* 是否开启 HTML 插件
|
||||
* @default true
|
||||
*/
|
||||
html?: boolean;
|
||||
/**
|
||||
* 是否开启国际化
|
||||
* @default false
|
||||
*/
|
||||
i18n?: boolean;
|
||||
/**
|
||||
* 是否开启 ImportMap CDN
|
||||
* @default false
|
||||
*/
|
||||
importmap?: boolean;
|
||||
/**
|
||||
* ImportMap 插件配置
|
||||
*/
|
||||
importmapOptions?: ImportmapPluginOptions;
|
||||
/**
|
||||
* 是否注入应用加载动画
|
||||
* @default true
|
||||
*/
|
||||
injectAppLoading?: boolean;
|
||||
/**
|
||||
* 是否注入全局 SCSS
|
||||
* @default true
|
||||
*/
|
||||
injectGlobalScss?: boolean;
|
||||
/**
|
||||
* 是否注入版权信息
|
||||
* @default true
|
||||
*/
|
||||
license?: boolean;
|
||||
/**
|
||||
* 是否开启 Nitro Mock
|
||||
* @default false
|
||||
*/
|
||||
nitroMock?: boolean;
|
||||
/**
|
||||
* Nitro Mock 插件配置
|
||||
*/
|
||||
nitroMockOptions?: NitroMockPluginOptions;
|
||||
/**
|
||||
* 是否开启控制台打印
|
||||
* @default false
|
||||
*/
|
||||
print?: boolean;
|
||||
/**
|
||||
* 打印插件配置
|
||||
*/
|
||||
printInfoMap?: PrintPluginOptions['infoMap'];
|
||||
/**
|
||||
* 是否开启 PWA
|
||||
* @default false
|
||||
*/
|
||||
pwa?: boolean;
|
||||
/**
|
||||
* PWA 插件配置
|
||||
*/
|
||||
pwaOptions?: Partial<PwaPluginOptions>;
|
||||
/**
|
||||
* 是否开启 VXE Table 懒加载
|
||||
* @default false
|
||||
*/
|
||||
vxeTableLazyImport?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 库插件配置选项
|
||||
* @description 用于配置库构建时的插件选项
|
||||
*/
|
||||
interface LibraryPluginOptions extends CommonPluginOptions {
|
||||
/**
|
||||
* 是否开启 DTS 输出
|
||||
* @default true
|
||||
* @description 生成 TypeScript 类型声明文件
|
||||
*/
|
||||
dts?: boolean | PluginOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用配置选项类型
|
||||
*/
|
||||
type ApplicationOptions = ApplicationPluginOptions;
|
||||
|
||||
/**
|
||||
* 库配置选项类型
|
||||
*/
|
||||
type LibraryOptions = LibraryPluginOptions;
|
||||
|
||||
/**
|
||||
* 应用配置定义函数类型
|
||||
* @description 用于定义应用构建配置
|
||||
*/
|
||||
type DefineApplicationOptions = (config?: ConfigEnv) => Promise<{
|
||||
/** 应用插件配置 */
|
||||
application?: ApplicationOptions;
|
||||
/** Vite 配置 */
|
||||
vite?: UserConfig;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* 库配置定义函数类型
|
||||
* @description 用于定义库构建配置
|
||||
*/
|
||||
type DefineLibraryOptions = (config?: ConfigEnv) => Promise<{
|
||||
/** 库插件配置 */
|
||||
library?: LibraryOptions;
|
||||
/** Vite 配置 */
|
||||
vite?: UserConfig;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* 配置定义类型
|
||||
* @description 应用或库的配置定义
|
||||
*/
|
||||
type DefineConfig = DefineApplicationOptions | DefineLibraryOptions;
|
||||
|
||||
export type {
|
||||
ApplicationPluginOptions,
|
||||
ArchiverPluginOptions,
|
||||
CommonPluginOptions,
|
||||
ConditionPlugin,
|
||||
DefineApplicationOptions,
|
||||
DefineConfig,
|
||||
DefineLibraryOptions,
|
||||
IImportMap,
|
||||
ImportmapPluginOptions,
|
||||
LibraryPluginOptions,
|
||||
NitroMockPluginOptions,
|
||||
PrintPluginOptions,
|
||||
};
|
109
internal/vite-config/src/utils/env.ts
Normal file
109
internal/vite-config/src/utils/env.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { ApplicationPluginOptions } from '../typing';
|
||||
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { fs } from '@vben/node-utils';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
const getBoolean = (value: string | undefined) => value === 'true';
|
||||
|
||||
const getString = (value: string | undefined, fallback: string) =>
|
||||
value ?? fallback;
|
||||
|
||||
const getNumber = (value: string | undefined, fallback: number) =>
|
||||
Number(value) || fallback;
|
||||
|
||||
/**
|
||||
* 获取当前环境下生效的配置文件名
|
||||
*/
|
||||
function getConfFiles() {
|
||||
const script = process.env.npm_lifecycle_script as string;
|
||||
const reg = /--mode ([\d_a-z]+)/;
|
||||
const result = reg.exec(script);
|
||||
let mode = 'production';
|
||||
if (result) {
|
||||
mode = result[1] as string;
|
||||
}
|
||||
return ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the environment variables starting with the specified prefix
|
||||
* @param match prefix
|
||||
* @param confFiles ext
|
||||
*/
|
||||
async function loadEnv<T = Record<string, string>>(
|
||||
match = 'VITE_GLOB_',
|
||||
confFiles = getConfFiles(),
|
||||
) {
|
||||
let envConfig = {};
|
||||
|
||||
for (const confFile of confFiles) {
|
||||
try {
|
||||
const confFilePath = join(process.cwd(), confFile);
|
||||
if (existsSync(confFilePath)) {
|
||||
const envPath = await fs.readFile(confFilePath, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
const env = dotenv.parse(envPath);
|
||||
envConfig = { ...envConfig, ...env };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error while parsing ${confFile}`, error);
|
||||
}
|
||||
}
|
||||
const reg = new RegExp(`^(${match})`);
|
||||
Object.keys(envConfig).forEach((key) => {
|
||||
if (!reg.test(key)) {
|
||||
Reflect.deleteProperty(envConfig, key);
|
||||
}
|
||||
});
|
||||
return envConfig as T;
|
||||
}
|
||||
|
||||
async function loadAndConvertEnv(
|
||||
match = 'VITE_',
|
||||
confFiles = getConfFiles(),
|
||||
): Promise<
|
||||
{
|
||||
appTitle: string;
|
||||
base: string;
|
||||
port: number;
|
||||
} & Partial<ApplicationPluginOptions>
|
||||
> {
|
||||
const envConfig = await loadEnv(match, confFiles);
|
||||
|
||||
const {
|
||||
VITE_APP_TITLE,
|
||||
VITE_ARCHIVER,
|
||||
VITE_BASE,
|
||||
VITE_COMPRESS,
|
||||
VITE_DEVTOOLS,
|
||||
VITE_INJECT_APP_LOADING,
|
||||
VITE_NITRO_MOCK,
|
||||
VITE_PORT,
|
||||
VITE_PWA,
|
||||
VITE_VISUALIZER,
|
||||
} = envConfig;
|
||||
|
||||
const compressTypes = (VITE_COMPRESS ?? '')
|
||||
.split(',')
|
||||
.filter((item) => item === 'brotli' || item === 'gzip');
|
||||
|
||||
return {
|
||||
appTitle: getString(VITE_APP_TITLE, 'Vben Admin'),
|
||||
archiver: getBoolean(VITE_ARCHIVER),
|
||||
base: getString(VITE_BASE, '/'),
|
||||
compress: compressTypes.length > 0,
|
||||
compressTypes,
|
||||
devtools: getBoolean(VITE_DEVTOOLS),
|
||||
injectAppLoading: getBoolean(VITE_INJECT_APP_LOADING),
|
||||
nitroMock: getBoolean(VITE_NITRO_MOCK),
|
||||
port: getNumber(VITE_PORT, 5173),
|
||||
pwa: getBoolean(VITE_PWA),
|
||||
visualizer: getBoolean(VITE_VISUALIZER),
|
||||
};
|
||||
}
|
||||
|
||||
export { loadAndConvertEnv, loadEnv };
|
6
internal/vite-config/tsconfig.json
Normal file
6
internal/vite-config/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/node.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Reference in New Issue
Block a user