chore: init project
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": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "internal/vite-config"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/vbenjs/vue-vben-admin/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"stub": "pnpm unbuild --stub"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"imports": {
|
||||
"#*": "./src/*"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||
"@jspm/generator": "^2.0.1",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"html-minifier-terser": "^7.2.0",
|
||||
"resolve.exports": "^2.0.2",
|
||||
"vite-plugin-lib-inject-css": "^2.1.1",
|
||||
"vite-plugin-vue-devtools": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/html-minifier-terser": "^7.0.2",
|
||||
"@vben/node-utils": "workspace:*",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"dayjs": "^1.11.11",
|
||||
"dotenv": "^16.4.5",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.77.2",
|
||||
"unplugin-turbo-console": "^1.8.6",
|
||||
"vite": "^5.2.11",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-dts": "^3.9.1",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
"vite-plugin-mock": "^3.0.2"
|
||||
}
|
||||
}
|
104
internal/vite-config/src/config/application.ts
Normal file
104
internal/vite-config/src/config/application.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { UserConfig } from 'vite';
|
||||
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import { defineConfig, mergeConfig } from 'vite';
|
||||
|
||||
import { getApplicationConditionPlugins } from '../plugins';
|
||||
import { getCommonConfig } from './common';
|
||||
|
||||
import type { DefineAppcationOptions } from '../typing';
|
||||
|
||||
function defineApplicationConfig(options: DefineAppcationOptions = {}) {
|
||||
return defineConfig(async ({ command, mode }) => {
|
||||
const { appcation = {}, vite = {} } = options;
|
||||
const root = process.cwd();
|
||||
const isBuild = command === 'build';
|
||||
// const env = loadEnv(mode, root);
|
||||
|
||||
const plugins = await getApplicationConditionPlugins({
|
||||
compress: false,
|
||||
compressTypes: ['brotli', 'gzip'],
|
||||
devtools: true,
|
||||
extraAppConfig: true,
|
||||
html: true,
|
||||
i18n: true,
|
||||
injectAppLoading: true,
|
||||
isBuild,
|
||||
mock: true,
|
||||
mode,
|
||||
turboConsole: false,
|
||||
...appcation,
|
||||
});
|
||||
|
||||
const applicationConfig: UserConfig = {
|
||||
// },
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
assetFileNames: '[ext]/[name]-[hash].[ext]',
|
||||
chunkFileNames: 'js/[name]-[hash].mjs',
|
||||
entryFileNames: 'jse/index-[name]-[hash].mjs',
|
||||
},
|
||||
},
|
||||
target: 'es2015',
|
||||
},
|
||||
// },
|
||||
esbuild: {
|
||||
drop: isBuild
|
||||
? [
|
||||
// 'console',
|
||||
'debugger',
|
||||
]
|
||||
: [],
|
||||
legalComments: 'none',
|
||||
},
|
||||
plugins,
|
||||
// css: {
|
||||
// preprocessorOptions: {
|
||||
// scss: {
|
||||
// additionalData: `@import "@vben-core/design/global";`,
|
||||
// },
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
find: /@\//,
|
||||
replacement: `${resolve(root, '.', 'src')}/`,
|
||||
},
|
||||
/**
|
||||
* 确保大仓内的子包,如果通过源码方式引用,可以直接使用@别名
|
||||
*/
|
||||
// {
|
||||
// find: '@',
|
||||
// replacement: '@',
|
||||
// customResolver(source, importer) {
|
||||
// if (source[0] === '@') {
|
||||
// const realPath = source.replace(
|
||||
// /^@/,
|
||||
// resolve(findUpPackageDir(importer), 'src'),
|
||||
// );
|
||||
// return findFileByExtension(realPath);
|
||||
// }
|
||||
// return null;
|
||||
// },
|
||||
// },
|
||||
],
|
||||
},
|
||||
server: {
|
||||
host: true,
|
||||
warmup: {
|
||||
// 预热文件
|
||||
clientFiles: ['./index.html', './src/{views}/*'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mergedConfig = mergeConfig(
|
||||
await getCommonConfig(),
|
||||
applicationConfig,
|
||||
);
|
||||
return mergeConfig(mergedConfig, vite);
|
||||
});
|
||||
}
|
||||
|
||||
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: 1000,
|
||||
reportCompressedSize: false,
|
||||
sourcemap: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { getCommonConfig };
|
31
internal/vite-config/src/config/index.ts
Normal file
31
internal/vite-config/src/config/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { fs } from '@vben/node-utils';
|
||||
|
||||
import { defineApplicationConfig } from './application';
|
||||
import { defineLibraryConfig } from './library';
|
||||
|
||||
import type { DefineConfig } from '../typing';
|
||||
|
||||
export * from './application';
|
||||
export * from './library';
|
||||
|
||||
function defineConfig(options: DefineConfig = {}) {
|
||||
const { type = 'auto', ...defineOptions } = options;
|
||||
|
||||
let projectType = type;
|
||||
|
||||
// 根据包是否存在 index.html,自动判断类型
|
||||
if (type === 'auto') {
|
||||
const htmlPath = join(process.cwd(), 'index.html');
|
||||
projectType = fs.existsSync(htmlPath) ? 'appcation' : 'library';
|
||||
}
|
||||
|
||||
if (projectType === 'appcation') {
|
||||
return defineApplicationConfig(defineOptions);
|
||||
} else if (projectType === 'library') {
|
||||
return defineLibraryConfig(defineOptions);
|
||||
}
|
||||
}
|
||||
|
||||
export { defineConfig };
|
51
internal/vite-config/src/config/library.ts
Normal file
51
internal/vite-config/src/config/library.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import type { UserConfig } from 'vite';
|
||||
|
||||
import { readPackageJSON } from '@vben/node-utils';
|
||||
import { defineConfig, mergeConfig } from 'vite';
|
||||
|
||||
import { getLibraryConditionPlugins } from '../plugins';
|
||||
import { getCommonConfig } from './common';
|
||||
|
||||
import type { DefineLibraryOptions } from '../typing';
|
||||
|
||||
function defineLibraryConfig(options: DefineLibraryOptions = {}) {
|
||||
return defineConfig(async ({ command, mode }) => {
|
||||
const root = process.cwd();
|
||||
const { library = {}, vite = {} } = options;
|
||||
const isBuild = command === 'build';
|
||||
|
||||
const plugins = await getLibraryConditionPlugins({
|
||||
dts: false,
|
||||
injectLibCss: true,
|
||||
isBuild,
|
||||
mode,
|
||||
...library,
|
||||
});
|
||||
|
||||
const { dependencies = {}, peerDependencies = {} } =
|
||||
await readPackageJSON(root);
|
||||
|
||||
const external = [
|
||||
...Object.keys(dependencies),
|
||||
...Object.keys(peerDependencies),
|
||||
];
|
||||
const packageConfig: UserConfig = {
|
||||
build: {
|
||||
lib: {
|
||||
entry: 'src/index.ts',
|
||||
fileName: () => 'index.mjs',
|
||||
formats: ['es'],
|
||||
},
|
||||
rollupOptions: {
|
||||
external,
|
||||
},
|
||||
},
|
||||
plugins,
|
||||
};
|
||||
const commonConfig = await getCommonConfig();
|
||||
const mergedConfig = mergeConfig(commonConfig, packageConfig);
|
||||
return mergeConfig(mergedConfig, vite);
|
||||
});
|
||||
}
|
||||
|
||||
export { defineLibraryConfig };
|
2
internal/vite-config/src/index.ts
Normal file
2
internal/vite-config/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './config';
|
||||
export * from './plugins';
|
97
internal/vite-config/src/plugins/extra-app-config.ts
Normal file
97
internal/vite-config/src/plugins/extra-app-config.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import {
|
||||
colors,
|
||||
generatorContentHash,
|
||||
readPackageJSON,
|
||||
} from '@vben/node-utils';
|
||||
import { type PluginOption } from 'vite';
|
||||
|
||||
import { getEnvConfig } 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 = config.base;
|
||||
source = await getConfigSource();
|
||||
},
|
||||
async generateBundle() {
|
||||
try {
|
||||
this.emitFile({
|
||||
fileName: GLOBAL_CONFIG_FILE_NAME,
|
||||
source,
|
||||
type: 'asset',
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(colors.cyan(`✨configuration file is build successfully!`));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
colors.red(
|
||||
`configuration file configuration file failed to package:\n${error}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
name: 'vite:extra-app-config',
|
||||
async transformIndexHtml(html) {
|
||||
publicPath = publicPath.endsWith('/') ? publicPath : `${publicPath}/`;
|
||||
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 getEnvConfig();
|
||||
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;
|
||||
}
|
||||
|
||||
export { viteExtraAppConfigPlugin };
|
243
internal/vite-config/src/plugins/importmap.ts
Normal file
243
internal/vite-config/src/plugins/importmap.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* 参考 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()) {
|
||||
// eslint-disable-next-line no-console
|
||||
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 install 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 };
|
211
internal/vite-config/src/plugins/index.ts
Normal file
211
internal/vite-config/src/plugins/index.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import { join } from 'node:path';
|
||||
|
||||
import viteVueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
|
||||
import { getPackage } from '@vben/node-utils';
|
||||
import viteVue from '@vitejs/plugin-vue';
|
||||
import viteVueJsx from '@vitejs/plugin-vue-jsx';
|
||||
import { visualizer as viteVisualizerPlugin } from 'rollup-plugin-visualizer';
|
||||
import viteTurboConsolePlugin from 'unplugin-turbo-console/vite';
|
||||
import viteCompressPlugin from 'vite-plugin-compression';
|
||||
import viteDtsPlugin from 'vite-plugin-dts';
|
||||
import { createHtmlPlugin as viteHtmlPlugin } from 'vite-plugin-html';
|
||||
import { libInjectCss as viteLibInjectCss } from 'vite-plugin-lib-inject-css';
|
||||
import { viteMockServe as viteMockPlugin } from 'vite-plugin-mock';
|
||||
import viteVueDevTools from 'vite-plugin-vue-devtools';
|
||||
|
||||
import { viteExtraAppConfigPlugin } from './extra-app-config';
|
||||
import { viteImportMapPlugin } from './importmap';
|
||||
import { viteInjectAppLoadingPlugin } from './inject-app-loading';
|
||||
|
||||
import type {
|
||||
AppcationPluginOptions,
|
||||
CommonPluginOptions,
|
||||
ConditionPlugin,
|
||||
LibraryPluginOptions,
|
||||
} from '../typing';
|
||||
|
||||
/**
|
||||
* 获取条件成立的 vite 插件
|
||||
* @param conditionPlugins
|
||||
*/
|
||||
async function getConditionEstablishedPlugins(
|
||||
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 getCommonConditionPlugins(
|
||||
options: CommonPluginOptions,
|
||||
): Promise<ConditionPlugin[]> {
|
||||
const { devtools, isBuild, visualizer } = options;
|
||||
return [
|
||||
{
|
||||
condition: true,
|
||||
plugins: () => [
|
||||
viteVue({
|
||||
script: {
|
||||
defineModel: true,
|
||||
// propsDestructure: true,
|
||||
},
|
||||
}),
|
||||
viteVueJsx(),
|
||||
],
|
||||
},
|
||||
{
|
||||
condition: !isBuild && devtools,
|
||||
plugins: () => [viteVueDevTools()],
|
||||
},
|
||||
{
|
||||
condition: isBuild && !!visualizer,
|
||||
plugins: () => [<PluginOption>viteVisualizerPlugin({
|
||||
filename: './node_modules/.cache/visualizer/stats.html',
|
||||
gzipSize: true,
|
||||
open: true,
|
||||
})],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件获取应用类型的vite插件
|
||||
*/
|
||||
async function getApplicationConditionPlugins(
|
||||
options: AppcationPluginOptions,
|
||||
): Promise<PluginOption[]> {
|
||||
// 单独取,否则commonOptions拿不到
|
||||
const isBuild = options.isBuild;
|
||||
|
||||
const {
|
||||
compress,
|
||||
compressTypes,
|
||||
extraAppConfig,
|
||||
|
||||
html,
|
||||
i18n,
|
||||
importmap,
|
||||
importmapOptions,
|
||||
injectAppLoading,
|
||||
mock,
|
||||
turboConsole,
|
||||
...commonOptions
|
||||
} = options;
|
||||
|
||||
const commonPlugins = await getCommonConditionPlugins(commonOptions);
|
||||
|
||||
return await getConditionEstablishedPlugins([
|
||||
...commonPlugins,
|
||||
{
|
||||
condition: i18n,
|
||||
plugins: async () => {
|
||||
const pkg = await getPackage('@vben/locales');
|
||||
const include = `${join(pkg?.dir ?? '', isBuild ? 'dist' : 'src', 'langs')}/*.yaml`;
|
||||
return [
|
||||
viteVueI18nPlugin({
|
||||
compositionOnly: true,
|
||||
fullInstall: true,
|
||||
include,
|
||||
runtimeOnly: true,
|
||||
}),
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: injectAppLoading,
|
||||
plugins: async () => [await viteInjectAppLoadingPlugin()],
|
||||
},
|
||||
{
|
||||
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: !isBuild && !!turboConsole,
|
||||
plugins: () => [viteTurboConsolePlugin()],
|
||||
},
|
||||
{
|
||||
condition: !!mock,
|
||||
plugins: () => [
|
||||
viteMockPlugin({
|
||||
enable: true,
|
||||
ignore: /^_/,
|
||||
mockPath: 'mock',
|
||||
}),
|
||||
],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件获取库类型的vite插件
|
||||
*/
|
||||
async function getLibraryConditionPlugins(
|
||||
options: LibraryPluginOptions,
|
||||
): Promise<PluginOption[]> {
|
||||
// 单独取,否则commonOptions拿不到
|
||||
const isBuild = options.isBuild;
|
||||
const { dts, injectLibCss, ...commonOptions } = options;
|
||||
const commonPlugins = await getCommonConditionPlugins(commonOptions);
|
||||
return await getConditionEstablishedPlugins([
|
||||
...commonPlugins,
|
||||
{
|
||||
condition: isBuild && !!dts,
|
||||
plugins: () => [viteDtsPlugin({ logLevel: 'error' })],
|
||||
},
|
||||
{
|
||||
condition: injectLibCss,
|
||||
plugins: () => [viteLibInjectCss()],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
export {
|
||||
getApplicationConditionPlugins,
|
||||
getLibraryConditionPlugins,
|
||||
viteCompressPlugin,
|
||||
viteDtsPlugin,
|
||||
viteHtmlPlugin,
|
||||
viteMockPlugin,
|
||||
viteTurboConsolePlugin,
|
||||
viteVisualizerPlugin,
|
||||
};
|
@@ -0,0 +1,3 @@
|
||||
# inject-app-loading
|
||||
|
||||
用于在应用加载时显示加载动画的插件。可自行选择加载动画的样式。
|
46
internal/vite-config/src/plugins/inject-app-loading/index.ts
Normal file
46
internal/vite-config/src/plugins/inject-app-loading/index.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { fs } from '@vben/node-utils';
|
||||
import { type PluginOption } from 'vite';
|
||||
|
||||
/**
|
||||
* 用于生成将loading样式注入到项目中
|
||||
* 为多app提供loading样式,无需在每个 app -> index.html单独引入
|
||||
*/
|
||||
async function viteInjectAppLoadingPlugin(): Promise<PluginOption | undefined> {
|
||||
const loadingHtml = await getLoadingRawByHtmlTemplate();
|
||||
|
||||
if (!loadingHtml) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
enforce: 'pre',
|
||||
name: 'vite:inject-app-loading',
|
||||
transformIndexHtml: {
|
||||
handler(html) {
|
||||
const re = /<div\s*id\s*=\s*"app"\s*>(\s*)<\/div>/;
|
||||
html = html.replace(re, `<div id="app">${loadingHtml}</div>`);
|
||||
return html;
|
||||
},
|
||||
order: 'pre',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于获取loading的html模板
|
||||
*/
|
||||
async function getLoadingRawByHtmlTemplate() {
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
const loadingPath = join(__dirname, './loading.html');
|
||||
if (!fs.existsSync(loadingPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const htmlRaw = await fs.readFile(loadingPath, 'utf8');
|
||||
return htmlRaw;
|
||||
}
|
||||
|
||||
export { viteInjectAppLoadingPlugin };
|
@@ -0,0 +1,102 @@
|
||||
<style>
|
||||
html {
|
||||
/* same as ant-design-vue/dist/reset.css setting, avoid the title line-height changed */
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.dark .loading {
|
||||
background-color: #2c344a;
|
||||
}
|
||||
|
||||
.dark .loading .title {
|
||||
color: rgb(255 255 255 / 85%);
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f4f7f9;
|
||||
}
|
||||
|
||||
.loading .dots {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 98px;
|
||||
}
|
||||
|
||||
.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: #0065cc;
|
||||
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">
|
||||
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
|
||||
<div class="title"><%= VITE_GLOB_APP_TITLE %></div>
|
||||
</div>
|
102
internal/vite-config/src/plugins/inject-app-loading/loading.html
Normal file
102
internal/vite-config/src/plugins/inject-app-loading/loading.html
Normal file
@@ -0,0 +1,102 @@
|
||||
<style>
|
||||
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;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f4f7f9;
|
||||
}
|
||||
|
||||
.dark .loading {
|
||||
background: #101827;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-top: 66px;
|
||||
font-size: 30px;
|
||||
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: #0065cc50;
|
||||
border-radius: 50%;
|
||||
animation: shadow-ani 0.5s linear infinite;
|
||||
}
|
||||
|
||||
.loader::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: '';
|
||||
background: #0065cc;
|
||||
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">
|
||||
<div class="loader"></div>
|
||||
<div class="title"><%= VITE_GLOB_APP_TITLE %></div>
|
||||
</div>
|
109
internal/vite-config/src/typing.ts
Normal file
109
internal/vite-config/src/typing.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
|
||||
import type { PluginOption, UserConfig } from 'vite';
|
||||
import type { PluginOptions } from 'vite-plugin-dts';
|
||||
|
||||
import viteTurboConsolePlugin from 'unplugin-turbo-console/vite';
|
||||
|
||||
export interface IImportMap {
|
||||
imports?: Record<string, string>;
|
||||
scopes?: {
|
||||
[scope: string]: Record<string, string>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* importmap 插件配置
|
||||
*/
|
||||
interface ImportmapPluginOptions {
|
||||
/**
|
||||
* CDN 供应商
|
||||
* @default jspm.io
|
||||
*/
|
||||
defaultProvider?: 'esm.sh' | 'jspm.io';
|
||||
/** importmap 配置 */
|
||||
importmap?: Array<{ name: string; range?: string }>;
|
||||
/** 手动配置importmap */
|
||||
inputMap?: IImportMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于判断是否需要加载插件
|
||||
*/
|
||||
interface ConditionPlugin {
|
||||
// 判断条件
|
||||
condition?: boolean;
|
||||
// 插件对象
|
||||
plugins: () => PluginOption[] | PromiseLike<PluginOption[]>;
|
||||
}
|
||||
|
||||
interface CommonPluginOptions {
|
||||
/** 是否开启devtools */
|
||||
devtools?: boolean;
|
||||
/** 是否构建模式 */
|
||||
isBuild?: boolean;
|
||||
/** 构建模式 */
|
||||
mode?: string;
|
||||
/** 开启依赖分析 */
|
||||
visualizer?: PluginVisualizerOptions | boolean;
|
||||
}
|
||||
|
||||
interface AppcationPluginOptions extends CommonPluginOptions {
|
||||
/** 开启 gzip 压缩 */
|
||||
compress?: boolean;
|
||||
/** 压缩类型 */
|
||||
compressTypes?: ('brotli' | 'gzip')[];
|
||||
/** 在构建的时候抽离配置文件 */
|
||||
extraAppConfig?: boolean;
|
||||
/** html 插件配置 */
|
||||
html?: boolean;
|
||||
/** 是否开启i18n */
|
||||
i18n?: boolean;
|
||||
/** 是否开启 importmap CDN */
|
||||
importmap?: boolean;
|
||||
/** importmap 插件配置 */
|
||||
importmapOptions?: ImportmapPluginOptions;
|
||||
/** 是否注入app loading */
|
||||
injectAppLoading?: boolean;
|
||||
/** mock 插件配置 */
|
||||
mock?: boolean;
|
||||
/** turbo-console 插件配置 */
|
||||
turboConsole?: Parameters<typeof viteTurboConsolePlugin>[0] | boolean;
|
||||
}
|
||||
|
||||
interface LibraryPluginOptions extends CommonPluginOptions {
|
||||
/** 开启 dts 输出 */
|
||||
dts?: PluginOptions | boolean;
|
||||
|
||||
/** 是否注入lib css */
|
||||
injectLibCss?: boolean;
|
||||
}
|
||||
|
||||
interface AppcationOptions extends AppcationPluginOptions {}
|
||||
|
||||
interface LibraryOptions extends LibraryPluginOptions {}
|
||||
|
||||
interface DefineAppcationOptions {
|
||||
appcation?: AppcationOptions;
|
||||
vite?: UserConfig;
|
||||
}
|
||||
|
||||
interface DefineLibraryOptions {
|
||||
library?: LibraryOptions;
|
||||
vite?: UserConfig;
|
||||
}
|
||||
|
||||
type DefineConfig = {
|
||||
type?: 'appcation' | 'auto' | 'library';
|
||||
} & DefineAppcationOptions &
|
||||
DefineLibraryOptions;
|
||||
|
||||
export type {
|
||||
AppcationPluginOptions,
|
||||
CommonPluginOptions,
|
||||
ConditionPlugin,
|
||||
DefineAppcationOptions,
|
||||
DefineConfig,
|
||||
DefineLibraryOptions,
|
||||
ImportmapPluginOptions,
|
||||
LibraryPluginOptions,
|
||||
};
|
49
internal/vite-config/src/utils/env.ts
Normal file
49
internal/vite-config/src/utils/env.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { fs } from '@vben/node-utils';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
/**
|
||||
* 获取当前环境下生效的配置文件名
|
||||
*/
|
||||
function getConfFiles() {
|
||||
const script = process.env.npm_lifecycle_script as string;
|
||||
const reg = /--mode ([\d_a-z]+)/;
|
||||
const result = reg.exec(script);
|
||||
if (result) {
|
||||
const mode = result[1];
|
||||
return ['.env', `.env.${mode}`];
|
||||
}
|
||||
return ['.env', '.env.production'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the environment variables starting with the specified prefix
|
||||
* @param match prefix
|
||||
* @param confFiles ext
|
||||
*/
|
||||
export async function getEnvConfig(
|
||||
match = 'VITE_GLOB_',
|
||||
confFiles = getConfFiles(),
|
||||
) {
|
||||
let envConfig = {};
|
||||
|
||||
for (const confFile of confFiles) {
|
||||
try {
|
||||
const envPath = await fs.readFile(join(process.cwd(), confFile), {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
const env = dotenv.parse(envPath);
|
||||
envConfig = { ...envConfig, ...env };
|
||||
} catch (error) {
|
||||
console.error(`Error in parsing ${confFile}`, error);
|
||||
}
|
||||
}
|
||||
const reg = new RegExp(`^(${match})`);
|
||||
Object.keys(envConfig).forEach((key) => {
|
||||
if (!reg.test(key)) {
|
||||
Reflect.deleteProperty(envConfig, key);
|
||||
}
|
||||
});
|
||||
return envConfig;
|
||||
}
|
5
internal/vite-config/tsconfig.json
Normal file
5
internal/vite-config/tsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/node.json",
|
||||
"include": ["src"]
|
||||
}
|
Reference in New Issue
Block a user