chore: init project

This commit is contained in:
vben
2024-05-19 21:20:42 +08:00
commit 399334ac57
630 changed files with 45623 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
declaration: true,
entries: ['src/index'],
});

View 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"
}
}

View 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 };

View 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 };

View 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 };

View 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 };

View File

@@ -0,0 +1,2 @@
export * from './config';
export * from './plugins';

View 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 };

View 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 };

View 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,
};

View File

@@ -0,0 +1,3 @@
# inject-app-loading
用于在应用加载时显示加载动画的插件。可自行选择加载动画的样式。

View 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 };

View 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;
}
.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>

View 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>

View 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,
};

View 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;
}

View File

@@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/node.json",
"include": ["src"]
}