68 changed files with 2441 additions and 3989 deletions
@ -0,0 +1,23 @@ |
|||
# Whether to open mock |
|||
VITE_USE_MOCK = true |
|||
|
|||
# public path |
|||
VITE_PUBLIC_PATH = / |
|||
|
|||
# Whether to enable gzip or brotli compression |
|||
# Optional: gzip | brotli | none |
|||
# If you need multiple forms, you can use `,` to separate |
|||
VITE_BUILD_COMPRESS = 'none' |
|||
|
|||
|
|||
# Basic interface address SPA |
|||
VITE_GLOB_API_URL=/basic-api |
|||
|
|||
# File upload address, optional |
|||
# It can be forwarded by nginx or write the actual address directly |
|||
VITE_GLOB_UPLOAD_URL=/upload |
|||
|
|||
# Interface prefix |
|||
VITE_GLOB_API_URL_PREFIX= |
|||
|
|||
VITE_ENABLE_ANALYZE = true |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"$schema": "https://json.schemastore.org/tsconfig", |
|||
"extends": "@vben/ts-config/node-server.json", |
|||
"compilerOptions": { |
|||
"noImplicitAny": false |
|||
} |
|||
} |
|||
@ -1,79 +0,0 @@ |
|||
import { generate } from '@ant-design/colors'; |
|||
|
|||
export const primaryColor = '#0960bd'; |
|||
|
|||
export const darkMode = 'light'; |
|||
|
|||
type Fn = (...arg: any) => any; |
|||
|
|||
type GenerateTheme = 'default' | 'dark'; |
|||
|
|||
export interface GenerateColorsParams { |
|||
mixLighten: Fn; |
|||
mixDarken: Fn; |
|||
tinycolor: any; |
|||
color?: string; |
|||
} |
|||
|
|||
export function generateAntColors(color: string, theme: GenerateTheme = 'default') { |
|||
return generate(color, { |
|||
theme, |
|||
}); |
|||
} |
|||
|
|||
export function getThemeColors(color?: string) { |
|||
const tc = color || primaryColor; |
|||
const lightColors = generateAntColors(tc); |
|||
const primary = lightColors[5]; |
|||
const modeColors = generateAntColors(primary, 'dark'); |
|||
|
|||
return [...lightColors, ...modeColors]; |
|||
} |
|||
|
|||
export function generateColors({ |
|||
color = primaryColor, |
|||
mixLighten, |
|||
mixDarken, |
|||
tinycolor, |
|||
}: GenerateColorsParams) { |
|||
const arr = new Array(19).fill(0); |
|||
const lightens = arr.map((_t, i) => { |
|||
return mixLighten(color, i / 5); |
|||
}); |
|||
|
|||
const darkens = arr.map((_t, i) => { |
|||
return mixDarken(color, i / 5); |
|||
}); |
|||
|
|||
const alphaColors = arr.map((_t, i) => { |
|||
return tinycolor(color) |
|||
.setAlpha(i / 20) |
|||
.toRgbString(); |
|||
}); |
|||
|
|||
const shortAlphaColors = alphaColors.map((item) => item.replace(/\s/g, '').replace(/0\./g, '.')); |
|||
|
|||
const tinycolorLightens = arr |
|||
.map((_t, i) => { |
|||
return tinycolor(color) |
|||
.lighten(i * 5) |
|||
.toHexString(); |
|||
}) |
|||
.filter((item) => item !== '#ffffff'); |
|||
|
|||
const tinycolorDarkens = arr |
|||
.map((_t, i) => { |
|||
return tinycolor(color) |
|||
.darken(i * 5) |
|||
.toHexString(); |
|||
}) |
|||
.filter((item) => item !== '#000000'); |
|||
return [ |
|||
...lightens, |
|||
...darkens, |
|||
...alphaColors, |
|||
...shortAlphaColors, |
|||
...tinycolorDarkens, |
|||
...tinycolorLightens, |
|||
].filter((item) => !item.includes('-')); |
|||
} |
|||
@ -1,6 +0,0 @@ |
|||
/** |
|||
* The name of the configuration file entered in the production environment |
|||
*/ |
|||
export const GLOB_CONFIG_FILE_NAME = '_app.config.js'; |
|||
|
|||
export const OUTPUT_DIR = 'dist'; |
|||
@ -1,72 +0,0 @@ |
|||
import path from 'path'; |
|||
import fs from 'fs-extra'; |
|||
import inquirer from 'inquirer'; |
|||
import colors from 'picocolors'; |
|||
import pkg from '../../../package.json'; |
|||
|
|||
async function generateIcon() { |
|||
const dir = path.resolve(process.cwd(), 'node_modules/@iconify/json'); |
|||
|
|||
const raw = await fs.readJSON(path.join(dir, 'collections.json')); |
|||
|
|||
const collections = Object.entries(raw).map(([id, v]) => ({ |
|||
...(v as any), |
|||
id, |
|||
})); |
|||
|
|||
const choices = collections.map((item) => ({ key: item.id, value: item.id, name: item.name })); |
|||
|
|||
inquirer |
|||
.prompt([ |
|||
{ |
|||
type: 'list', |
|||
name: 'useType', |
|||
choices: [ |
|||
{ key: 'local', value: 'local', name: 'Local' }, |
|||
{ key: 'onLine', value: 'onLine', name: 'OnLine' }, |
|||
], |
|||
message: 'How to use icons?', |
|||
}, |
|||
{ |
|||
type: 'list', |
|||
name: 'iconSet', |
|||
choices: choices, |
|||
message: 'Select the icon set that needs to be generated?', |
|||
}, |
|||
{ |
|||
type: 'input', |
|||
name: 'output', |
|||
message: 'Select the icon set that needs to be generated?', |
|||
default: 'src/components/Icon/data', |
|||
}, |
|||
]) |
|||
.then(async (answers) => { |
|||
const { iconSet, output, useType } = answers; |
|||
const outputDir = path.resolve(process.cwd(), output); |
|||
fs.ensureDir(outputDir); |
|||
const genCollections = collections.filter((item) => [iconSet].includes(item.id)); |
|||
const prefixSet: string[] = []; |
|||
for (const info of genCollections) { |
|||
const data = await fs.readJSON(path.join(dir, 'json', `${info.id}.json`)); |
|||
if (data) { |
|||
const { prefix } = data; |
|||
const isLocal = useType === 'local'; |
|||
const icons = Object.keys(data.icons).map( |
|||
(item) => `${isLocal ? prefix + ':' : ''}${item}`, |
|||
); |
|||
|
|||
await fs.writeFileSync( |
|||
path.join(output, `icons.data.ts`), |
|||
`export default ${isLocal ? JSON.stringify(icons) : JSON.stringify({ prefix, icons })}`, |
|||
); |
|||
prefixSet.push(prefix); |
|||
} |
|||
} |
|||
fs.emptyDir(path.join(process.cwd(), 'node_modules/.vite')); |
|||
console.log( |
|||
`✨ ${colors.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`, |
|||
); |
|||
}); |
|||
} |
|||
|
|||
generateIcon(); |
|||
@ -1,9 +0,0 @@ |
|||
/** |
|||
* Get the configuration file variable name |
|||
* @param env |
|||
*/ |
|||
export const getConfigFileName = (env: Record<string, any>) => { |
|||
return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__` |
|||
.toUpperCase() |
|||
.replace(/\s/g, ''); |
|||
}; |
|||
@ -1,47 +0,0 @@ |
|||
/** |
|||
* Generate additional configuration files when used for packaging. The file can be configured with some global variables, so that it can be changed directly externally without repackaging |
|||
*/ |
|||
import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant'; |
|||
import fs, { writeFileSync } from 'fs-extra'; |
|||
import colors from 'picocolors'; |
|||
|
|||
import { getEnvConfig, getRootPath } from '../utils'; |
|||
import { getConfigFileName } from '../getConfigFileName'; |
|||
|
|||
import pkg from '../../package.json'; |
|||
|
|||
interface CreateConfigParams { |
|||
configName: string; |
|||
config: any; |
|||
configFileName?: string; |
|||
} |
|||
|
|||
function createConfig(params: CreateConfigParams) { |
|||
const { configName, config, configFileName } = params; |
|||
try { |
|||
const windowConf = `window.${configName}`; |
|||
// Ensure that the variable will not be modified
|
|||
let configStr = `${windowConf}=${JSON.stringify(config)};`; |
|||
configStr += ` |
|||
Object.freeze(${windowConf}); |
|||
Object.defineProperty(window, "${configName}", { |
|||
configurable: false, |
|||
writable: false, |
|||
}); |
|||
`.replace(/\s/g, '');
|
|||
|
|||
fs.mkdirp(getRootPath(OUTPUT_DIR)); |
|||
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr); |
|||
|
|||
console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`); |
|||
console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n'); |
|||
} catch (error) { |
|||
console.log(colors.red('configuration file configuration file failed to package:\n' + error)); |
|||
} |
|||
} |
|||
|
|||
export function runBuildConfig() { |
|||
const config = getEnvConfig(); |
|||
const configFileName = getConfigFileName(config); |
|||
createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME }); |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
// #!/usr/bin/env node
|
|||
|
|||
import { runBuildConfig } from './buildConf'; |
|||
import colors from 'picocolors'; |
|||
|
|||
import pkg from '../../package.json'; |
|||
|
|||
export const runBuild = async () => { |
|||
try { |
|||
const argvList = process.argv.splice(2); |
|||
|
|||
// Generate configuration file
|
|||
if (!argvList.includes('disabled-config')) { |
|||
runBuildConfig(); |
|||
} |
|||
|
|||
console.log(`✨ ${colors.cyan(`[${pkg.name}]`)}` + ' - build successfully!'); |
|||
} catch (error) { |
|||
console.log(colors.red('vite build error:\n' + error)); |
|||
process.exit(1); |
|||
} |
|||
}; |
|||
runBuild(); |
|||
@ -1,73 +0,0 @@ |
|||
import fs from 'fs'; |
|||
import path from 'path'; |
|||
import dotenv from 'dotenv'; |
|||
|
|||
// Read all environment variable configuration files to process.env
|
|||
export function wrapperEnv(envConf: Recordable): ViteEnv { |
|||
const ret: any = {}; |
|||
|
|||
for (const envName of Object.keys(envConf)) { |
|||
let realName = envConf[envName].replace(/\\n/g, '\n'); |
|||
realName = realName === 'true' ? true : realName === 'false' ? false : realName; |
|||
if (envName === 'VITE_PROXY' && realName) { |
|||
try { |
|||
realName = JSON.parse(realName.replace(/'/g, '"')); |
|||
} catch (error) { |
|||
realName = ''; |
|||
} |
|||
} |
|||
ret[envName] = realName; |
|||
// if (typeof realName === 'string') {
|
|||
// process.env[envName] = realName;
|
|||
// } else if (typeof realName === 'object') {
|
|||
// process.env[envName] = JSON.stringify(realName);
|
|||
// }
|
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
/** |
|||
* 获取当前环境下生效的配置文件名 |
|||
*/ |
|||
function getConfFiles() { |
|||
const script = process.env.npm_lifecycle_script; |
|||
const reg = new RegExp('--mode ([a-z_\\d]+)'); |
|||
const result = reg.exec(script as string) as any; |
|||
if (result) { |
|||
const mode = result[1] as string; |
|||
return ['.env', `.env.${mode}`]; |
|||
} |
|||
return ['.env', '.env.production']; |
|||
} |
|||
|
|||
/** |
|||
* Get the environment variables starting with the specified prefix |
|||
* @param match prefix |
|||
* @param confFiles ext |
|||
*/ |
|||
export function getEnvConfig(match = 'VITE_GLOB_', confFiles = getConfFiles()) { |
|||
let envConfig = {}; |
|||
confFiles.forEach((item) => { |
|||
try { |
|||
const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item))); |
|||
envConfig = { ...envConfig, ...env }; |
|||
} catch (e) { |
|||
console.error(`Error in parsing ${item}`, e); |
|||
} |
|||
}); |
|||
const reg = new RegExp(`^(${match})`); |
|||
Object.keys(envConfig).forEach((key) => { |
|||
if (!reg.test(key)) { |
|||
Reflect.deleteProperty(envConfig, key); |
|||
} |
|||
}); |
|||
return envConfig; |
|||
} |
|||
|
|||
/** |
|||
* Get user root directory |
|||
* @param dir file path |
|||
*/ |
|||
export function getRootPath(...dir: string[]) { |
|||
return path.resolve(process.cwd(), ...dir); |
|||
} |
|||
@ -1,40 +0,0 @@ |
|||
/** |
|||
* Plugin to minimize and use ejs template syntax in index.html. |
|||
* https://github.com/anncwb/vite-plugin-html
|
|||
*/ |
|||
import type { PluginOption } from 'vite'; |
|||
import { createHtmlPlugin } from 'vite-plugin-html'; |
|||
import pkg from '../../../package.json'; |
|||
import { GLOB_CONFIG_FILE_NAME } from '../../constant'; |
|||
|
|||
export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) { |
|||
const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env; |
|||
|
|||
const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`; |
|||
|
|||
const getAppConfigSrc = () => { |
|||
return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`; |
|||
}; |
|||
|
|||
const htmlPlugin: PluginOption[] = createHtmlPlugin({ |
|||
minify: isBuild, |
|||
inject: { |
|||
// Inject data into ejs template
|
|||
data: { |
|||
title: VITE_GLOB_APP_TITLE, |
|||
}, |
|||
// Embed the generated app.config.js file
|
|||
tags: isBuild |
|||
? [ |
|||
{ |
|||
tag: 'script', |
|||
attrs: { |
|||
src: getAppConfigSrc(), |
|||
}, |
|||
}, |
|||
] |
|||
: [], |
|||
}, |
|||
}); |
|||
return htmlPlugin; |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
import { PluginOption } from 'vite'; |
|||
import vue from '@vitejs/plugin-vue'; |
|||
import vueJsx from '@vitejs/plugin-vue-jsx'; |
|||
import purgeIcons from 'vite-plugin-purge-icons'; |
|||
import windiCSS from 'vite-plugin-windicss'; |
|||
import { configHtmlPlugin } from './html'; |
|||
import { configMockPlugin } from './mock'; |
|||
import { configCompressPlugin } from './compress'; |
|||
import { configVisualizerConfig } from './visualizer'; |
|||
import { configSvgIconsPlugin } from './svgSprite'; |
|||
|
|||
export async function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { |
|||
const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv; |
|||
|
|||
const vitePlugins: (PluginOption | PluginOption[])[] = [ |
|||
// have to
|
|||
vue(), |
|||
// have to
|
|||
vueJsx(), |
|||
]; |
|||
|
|||
// vite-plugin-windicss
|
|||
vitePlugins.push(windiCSS()); |
|||
|
|||
// vite-plugin-html
|
|||
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild)); |
|||
|
|||
// vite-plugin-svg-icons
|
|||
vitePlugins.push(configSvgIconsPlugin(isBuild)); |
|||
|
|||
// vite-plugin-mock
|
|||
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild)); |
|||
|
|||
// vite-plugin-purge-icons
|
|||
vitePlugins.push(purgeIcons()); |
|||
|
|||
// rollup-plugin-visualizer
|
|||
vitePlugins.push(configVisualizerConfig()); |
|||
|
|||
// The following plugins only work in the production environment
|
|||
if (isBuild) { |
|||
// rollup-plugin-gzip
|
|||
vitePlugins.push( |
|||
configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE), |
|||
); |
|||
} |
|||
|
|||
return vitePlugins; |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
/** |
|||
* Package file volume analysis |
|||
*/ |
|||
import visualizer from 'rollup-plugin-visualizer'; |
|||
|
|||
export function configVisualizerConfig() { |
|||
if (process.env.REPORT === 'true') { |
|||
return visualizer({ |
|||
filename: './node_modules/.cache/visualizer/stats.html', |
|||
open: true, |
|||
gzipSize: true, |
|||
brotliSize: true, |
|||
}) as Plugin; |
|||
} |
|||
return []; |
|||
} |
|||
@ -1,34 +0,0 @@ |
|||
/** |
|||
* Used to parse the .env.development proxy configuration |
|||
*/ |
|||
import type { ProxyOptions } from 'vite'; |
|||
|
|||
type ProxyItem = [string, string]; |
|||
|
|||
type ProxyList = ProxyItem[]; |
|||
|
|||
type ProxyTargetList = Record<string, ProxyOptions>; |
|||
|
|||
const httpsRE = /^https:\/\//; |
|||
|
|||
/** |
|||
* Generate proxy |
|||
* @param list |
|||
*/ |
|||
export function createProxy(list: ProxyList = []) { |
|||
const ret: ProxyTargetList = {}; |
|||
for (const [prefix, target] of list) { |
|||
const isHttps = httpsRE.test(target); |
|||
|
|||
// https://github.com/http-party/node-http-proxy#options
|
|||
ret[prefix] = { |
|||
target: target, |
|||
changeOrigin: true, |
|||
ws: true, |
|||
rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''), |
|||
// https is require secure=false
|
|||
...(isHttps ? { secure: false } : {}), |
|||
}; |
|||
} |
|||
return ret; |
|||
} |
|||
@ -1,4 +1,7 @@ |
|||
{ |
|||
"$schema": "https://json.schemastore.org/tsconfig", |
|||
"display": "Node Server Config", |
|||
"extends": "./base.json", |
|||
"compilerOptions": { |
|||
"module": "commonjs", |
|||
"declaration": false, |
|||
@ -0,0 +1,107 @@ |
|||
const fs = require('fs'); |
|||
const path = require('path'); |
|||
const { execSync } = require('child_process'); |
|||
|
|||
const scopes = fs |
|||
.readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true }) |
|||
.filter((dirent) => dirent.isDirectory()) |
|||
.map((dirent) => dirent.name.replace(/s$/, '')); |
|||
|
|||
// precomputed scope
|
|||
const scopeComplete = execSync('git status --porcelain || true') |
|||
.toString() |
|||
.trim() |
|||
.split('\n') |
|||
.find((r) => ~r.indexOf('M src')) |
|||
?.replace(/(\/)/g, '%%') |
|||
?.match(/src%%((\w|-)*)/)?.[1] |
|||
?.replace(/s$/, ''); |
|||
|
|||
/** @type {import('cz-git').UserConfig} */ |
|||
module.exports = { |
|||
ignores: [(commit) => commit.includes('init')], |
|||
extends: ['@commitlint/config-conventional'], |
|||
rules: { |
|||
'body-leading-blank': [2, 'always'], |
|||
'footer-leading-blank': [1, 'always'], |
|||
'header-max-length': [2, 'always', 108], |
|||
'subject-empty': [2, 'never'], |
|||
'type-empty': [2, 'never'], |
|||
'subject-case': [0], |
|||
'type-enum': [ |
|||
2, |
|||
'always', |
|||
[ |
|||
'feat', |
|||
'fix', |
|||
'perf', |
|||
'style', |
|||
'docs', |
|||
'test', |
|||
'refactor', |
|||
'build', |
|||
'ci', |
|||
'chore', |
|||
'revert', |
|||
'wip', |
|||
'workflow', |
|||
'types', |
|||
'release', |
|||
], |
|||
], |
|||
}, |
|||
prompt: { |
|||
/** @use `yarn commit :f` */ |
|||
alias: { |
|||
f: 'docs: fix typos', |
|||
r: 'docs: update README', |
|||
s: 'style: update code format', |
|||
b: 'build: bump dependencies', |
|||
c: 'chore: update config', |
|||
}, |
|||
customScopesAlign: !scopeComplete ? 'top' : 'bottom', |
|||
defaultScope: scopeComplete, |
|||
scopes: [...scopes, 'mock'], |
|||
allowEmptyIssuePrefixs: false, |
|||
allowCustomIssuePrefixs: false, |
|||
|
|||
// English
|
|||
typesAppend: [ |
|||
{ value: 'wip', name: 'wip: work in process' }, |
|||
{ value: 'workflow', name: 'workflow: workflow improvements' }, |
|||
{ value: 'types', name: 'types: type definition file changes' }, |
|||
], |
|||
|
|||
// 中英文对照版
|
|||
// messages: {
|
|||
// type: '选择你要提交的类型 :',
|
|||
// scope: '选择一个提交范围 (可选):',
|
|||
// customScope: '请输入自定义的提交范围 :',
|
|||
// subject: '填写简短精炼的变更描述 :\n',
|
|||
// body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n',
|
|||
// breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n',
|
|||
// footerPrefixsSelect: '选择关联issue前缀 (可选):',
|
|||
// customFooterPrefixs: '输入自定义issue前缀 :',
|
|||
// footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
|
|||
// confirmCommit: '是否提交或修改commit ?',
|
|||
// },
|
|||
// types: [
|
|||
// { value: 'feat', name: 'feat: 新增功能' },
|
|||
// { value: 'fix', name: 'fix: 修复缺陷' },
|
|||
// { value: 'docs', name: 'docs: 文档变更' },
|
|||
// { value: 'style', name: 'style: 代码格式' },
|
|||
// { value: 'refactor', name: 'refactor: 代码重构' },
|
|||
// { value: 'perf', name: 'perf: 性能优化' },
|
|||
// { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' },
|
|||
// { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' },
|
|||
// { value: 'ci', name: 'ci: 修改 CI 配置、脚本' },
|
|||
// { value: 'revert', name: 'revert: 回滚 commit' },
|
|||
// { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' },
|
|||
// { value: 'wip', name: 'wip: 正在开发中' },
|
|||
// { value: 'workflow', name: 'workflow: 工作流程改进' },
|
|||
// { value: 'types', name: 'types: 类型定义文件修改' },
|
|||
// ],
|
|||
// emptyScopesAlias: 'empty: 不填写',
|
|||
// customScopesAlias: 'custom: 自定义',
|
|||
}, |
|||
}; |
|||
@ -0,0 +1,15 @@ |
|||
|
|||
*.sh |
|||
node_modules |
|||
*.md |
|||
*.woff |
|||
*.ttf |
|||
.vscode |
|||
.idea |
|||
dist |
|||
/public |
|||
/docs |
|||
.husky |
|||
.local |
|||
/bin |
|||
Dockerfile |
|||
@ -0,0 +1,4 @@ |
|||
module.exports = { |
|||
root: true, |
|||
extends: ['@vben'], |
|||
}; |
|||
@ -0,0 +1,10 @@ |
|||
dist |
|||
.local |
|||
.output.js |
|||
node_modules |
|||
|
|||
**/*.svg |
|||
**/*.sh |
|||
|
|||
public |
|||
.npmrc |
|||
@ -0,0 +1,19 @@ |
|||
module.exports = { |
|||
printWidth: 100, |
|||
semi: true, |
|||
vueIndentScriptAndStyle: true, |
|||
singleQuote: true, |
|||
trailingComma: 'all', |
|||
proseWrap: 'never', |
|||
htmlWhitespaceSensitivity: 'strict', |
|||
endOfLine: 'auto', |
|||
plugins: ['prettier-plugin-packagejson'], |
|||
overrides: [ |
|||
{ |
|||
files: '.*rc', |
|||
options: { |
|||
parser: 'json', |
|||
}, |
|||
}, |
|||
], |
|||
}; |
|||
@ -0,0 +1,2 @@ |
|||
dist |
|||
public |
|||
@ -0,0 +1,4 @@ |
|||
module.exports = { |
|||
root: true, |
|||
extends: ['@vben/stylelint-config'], |
|||
}; |
|||
@ -0,0 +1,10 @@ |
|||
import { defineBuildConfig } from 'unbuild'; |
|||
|
|||
export default defineBuildConfig({ |
|||
clean: true, |
|||
entries: ['src/index'], |
|||
declaration: true, |
|||
rollup: { |
|||
emitCJS: true, |
|||
}, |
|||
}); |
|||
@ -0,0 +1,54 @@ |
|||
{ |
|||
"name": "@vben/vite-config", |
|||
"version": "1.0.0", |
|||
"private": true, |
|||
"homepage": "https://github.com/vbenjs/vue-vben-admin", |
|||
"bugs": { |
|||
"url": "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", |
|||
"exports": { |
|||
".": { |
|||
"types": "./dist/index.d.ts", |
|||
"import": "./dist/index.mjs", |
|||
"require": "./dist/index.cjs" |
|||
} |
|||
}, |
|||
"main": "./dist/index.cjs", |
|||
"module": "./dist/index.mjs", |
|||
"types": "./dist/index.d.ts", |
|||
"files": [ |
|||
"dist" |
|||
], |
|||
"scripts": { |
|||
"clean": "pnpm rimraf .turbo node_modules dist", |
|||
"lint": "pnpm eslint .", |
|||
"stub": "pnpm unbuild --stub" |
|||
}, |
|||
"dependencies": { |
|||
"vite": "^4.3.0-beta.1" |
|||
}, |
|||
"devDependencies": { |
|||
"@types/fs-extra": "^11.0.1", |
|||
"ant-design-vue": "^3.2.16", |
|||
"dayjs": "^1.11.7", |
|||
"dotenv": "^16.0.3", |
|||
"fs-extra": "^11.1.1", |
|||
"less": "^4.1.3", |
|||
"picocolors": "^1.0.0", |
|||
"pkg-types": "^1.0.2", |
|||
"rollup-plugin-visualizer": "^5.9.0", |
|||
"sass": "^1.60.0", |
|||
"vite-plugin-compression": "^0.5.1", |
|||
"vite-plugin-html": "^3.2.0", |
|||
"vite-plugin-mock": "^2.9.6", |
|||
"vite-plugin-purge-icons": "^0.9.2", |
|||
"vite-plugin-svg-icons": "^2.0.1", |
|||
"vite-plugin-windicss": "^1.8.10" |
|||
} |
|||
} |
|||
@ -0,0 +1,116 @@ |
|||
import { type UserConfig, defineConfig, mergeConfig, loadEnv } from 'vite'; |
|||
import { resolve } from 'node:path'; |
|||
import { readPackageJSON } from 'pkg-types'; |
|||
import { generateModifyVars } from '../utils/modifyVars'; |
|||
import { commonConfig } from './common'; |
|||
import { createPlugins } from '../plugins'; |
|||
import dayjs from 'dayjs'; |
|||
|
|||
interface DefineOptions { |
|||
overrides?: UserConfig; |
|||
options?: {}; |
|||
} |
|||
|
|||
function defineApplicationConfig(defineOptions: DefineOptions = {}) { |
|||
const { overrides = {} } = defineOptions; |
|||
|
|||
return defineConfig(async ({ command, mode }) => { |
|||
const root = process.cwd(); |
|||
const isBuild = command === 'build'; |
|||
const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_ENABLE_ANALYZE } = loadEnv(mode, root); |
|||
|
|||
const defineData = await createDefineData(root); |
|||
const plugins = await createPlugins({ |
|||
isBuild, |
|||
root, |
|||
enableAnalyze: VITE_ENABLE_ANALYZE === 'true', |
|||
enableMock: VITE_USE_MOCK === 'true', |
|||
compress: VITE_BUILD_COMPRESS, |
|||
}); |
|||
|
|||
const pathResolve = (pathname: string) => resolve(root, '.', pathname); |
|||
|
|||
const applicationConfig: UserConfig = { |
|||
optimizeDeps: { |
|||
include: [ |
|||
'@iconify/iconify', |
|||
'ant-design-vue/es/locale/zh_CN', |
|||
'ant-design-vue/es/locale/en_US', |
|||
], |
|||
}, |
|||
resolve: { |
|||
alias: [ |
|||
{ |
|||
find: 'vue-i18n', |
|||
replacement: 'vue-i18n/dist/vue-i18n.cjs.js', |
|||
}, |
|||
// /@/xxxx => src/xxxx
|
|||
{ |
|||
find: /\/@\//, |
|||
replacement: pathResolve('src') + '/', |
|||
}, |
|||
// /#/xxxx => types/xxxx
|
|||
{ |
|||
find: /\/#\//, |
|||
replacement: pathResolve('types') + '/', |
|||
}, |
|||
// @/xxxx => src/xxxx
|
|||
{ |
|||
find: /@\//, |
|||
replacement: pathResolve('src') + '/', |
|||
}, |
|||
// #/xxxx => types/xxxx
|
|||
{ |
|||
find: /#\//, |
|||
replacement: pathResolve('types') + '/', |
|||
}, |
|||
], |
|||
}, |
|||
define: defineData, |
|||
build: { |
|||
target: 'es2015', |
|||
cssTarget: 'chrome80', |
|||
rollupOptions: { |
|||
output: { |
|||
manualChunks: { |
|||
vue: ['vue', 'pinia', 'vue-router'], |
|||
antdv: ['ant-design-vue', '@ant-design/icons-vue'], |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
css: { |
|||
preprocessorOptions: { |
|||
less: { |
|||
modifyVars: generateModifyVars(), |
|||
javascriptEnabled: true, |
|||
}, |
|||
}, |
|||
}, |
|||
plugins, |
|||
}; |
|||
|
|||
const mergedConfig = mergeConfig(commonConfig, applicationConfig); |
|||
|
|||
return mergeConfig(mergedConfig, overrides); |
|||
}); |
|||
} |
|||
|
|||
async function createDefineData(root: string) { |
|||
try { |
|||
const pkgJson = await readPackageJSON(root); |
|||
const { dependencies, devDependencies, name, version } = pkgJson; |
|||
|
|||
const __APP_INFO__ = { |
|||
pkg: { dependencies, devDependencies, name, version }, |
|||
lastBuildTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), |
|||
}; |
|||
return { |
|||
__APP_INFO__: JSON.stringify(__APP_INFO__), |
|||
}; |
|||
} catch (error) { |
|||
return {}; |
|||
} |
|||
} |
|||
|
|||
export { defineApplicationConfig }; |
|||
@ -0,0 +1,20 @@ |
|||
import { type UserConfig } from 'vite'; |
|||
|
|||
const commonConfig: UserConfig = { |
|||
server: { |
|||
host: true, |
|||
}, |
|||
esbuild: { |
|||
drop: ['console', 'debugger'], |
|||
}, |
|||
build: { |
|||
reportCompressedSize: false, |
|||
chunkSizeWarningLimit: 1500, |
|||
rollupOptions: { |
|||
// TODO: Prevent memory overflow
|
|||
maxParallelFileOps: 3, |
|||
}, |
|||
}, |
|||
}; |
|||
|
|||
export { commonConfig }; |
|||
@ -0,0 +1,5 @@ |
|||
function definePackageConfig() { |
|||
// TODO:
|
|||
} |
|||
|
|||
export { definePackageConfig }; |
|||
@ -0,0 +1 @@ |
|||
export * from './config/application'; |
|||
@ -0,0 +1,94 @@ |
|||
import { type PluginOption } from 'vite'; |
|||
import { getEnvConfig } from '../utils/env'; |
|||
import { createContentHash } from '../utils/hash'; |
|||
import { readPackageJSON } from 'pkg-types'; |
|||
import colors from 'picocolors'; |
|||
|
|||
const GLOBAL_CONFIG_FILE_NAME = '_app.config.js'; |
|||
const PLUGIN_NAME = 'app-config'; |
|||
|
|||
async function createAppConfigPlugin({ |
|||
root, |
|||
isBuild, |
|||
}: { |
|||
root: string; |
|||
isBuild: boolean; |
|||
}): Promise<PluginOption> { |
|||
let publicPath: string; |
|||
let source: string; |
|||
if (!isBuild) { |
|||
return { |
|||
name: PLUGIN_NAME, |
|||
}; |
|||
} |
|||
const { version = '' } = await readPackageJSON(root); |
|||
|
|||
return { |
|||
name: PLUGIN_NAME, |
|||
async configResolved(_config) { |
|||
const appTitle = _config?.env?.VITE_GLOB_APP_SHORT_NAME ?? ''; |
|||
publicPath = _config.base; |
|||
source = await getConfigSource(appTitle); |
|||
}, |
|||
async transformIndexHtml(html) { |
|||
publicPath = publicPath.endsWith('/') ? publicPath : `${publicPath}/`; |
|||
|
|||
const appConfigSrc = `${ |
|||
publicPath || '/' |
|||
}${GLOBAL_CONFIG_FILE_NAME}?v=${version}-${createContentHash(source)}}`;
|
|||
|
|||
return { |
|||
html, |
|||
tags: [ |
|||
{ |
|||
tag: 'script', |
|||
attrs: { |
|||
src: appConfigSrc, |
|||
}, |
|||
}, |
|||
], |
|||
}; |
|||
}, |
|||
async generateBundle() { |
|||
try { |
|||
this.emitFile({ |
|||
type: 'asset', |
|||
fileName: GLOBAL_CONFIG_FILE_NAME, |
|||
source, |
|||
}); |
|||
|
|||
console.log(colors.cyan(`✨configuration file is build successfully!`)); |
|||
} catch (error) { |
|||
console.log( |
|||
colors.red('configuration file configuration file failed to package:\n' + error), |
|||
); |
|||
} |
|||
}, |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Get the configuration file variable name |
|||
* @param env |
|||
*/ |
|||
const getVariableName = (title: string) => { |
|||
return `__PRODUCTION__${title || '__APP'}__CONF__`.toUpperCase().replace(/\s/g, ''); |
|||
}; |
|||
|
|||
async function getConfigSource(appTitle: string) { |
|||
const config = await getEnvConfig(); |
|||
const variableName = getVariableName(appTitle); |
|||
const windowVariable = `window.${variableName}`; |
|||
// Ensure that the variable will not be modified
|
|||
let source = `${windowVariable}=${JSON.stringify(config)};`; |
|||
source += ` |
|||
Object.freeze(${windowVariable}); |
|||
Object.defineProperty(window, "${variableName}", { |
|||
configurable: false, |
|||
writable: false, |
|||
}); |
|||
`.replace(/\s/g, '');
|
|||
return source; |
|||
} |
|||
|
|||
export { createAppConfigPlugin }; |
|||
@ -0,0 +1,13 @@ |
|||
/** |
|||
* Plugin to minimize and use ejs template syntax in index.html. |
|||
* https://github.com/anncwb/vite-plugin-html
|
|||
*/ |
|||
import type { PluginOption } from 'vite'; |
|||
import { createHtmlPlugin } from 'vite-plugin-html'; |
|||
|
|||
export function configHtmlPlugin({ isBuild }: { isBuild: boolean }) { |
|||
const htmlPlugin: PluginOption[] = createHtmlPlugin({ |
|||
minify: isBuild, |
|||
}); |
|||
return htmlPlugin; |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
import { type PluginOption } from 'vite'; |
|||
import { configHtmlPlugin } from './html'; |
|||
import { configMockPlugin } from './mock'; |
|||
import { configCompressPlugin } from './compress'; |
|||
import { configVisualizerConfig } from './visualizer'; |
|||
import { configSvgIconsPlugin } from './svgSprite'; |
|||
import { createAppConfigPlugin } from './appConfig'; |
|||
import vue from '@vitejs/plugin-vue'; |
|||
import vueJsx from '@vitejs/plugin-vue-jsx'; |
|||
import purgeIcons from 'vite-plugin-purge-icons'; |
|||
import windiCSS from 'vite-plugin-windicss'; |
|||
|
|||
interface Options { |
|||
isBuild: boolean; |
|||
root: string; |
|||
compress: string; |
|||
enableMock?: boolean; |
|||
enableAnalyze?: boolean; |
|||
} |
|||
|
|||
async function createPlugins({ isBuild, root, enableMock, compress, enableAnalyze }: Options) { |
|||
const vitePlugins: (PluginOption | PluginOption[])[] = [vue(), vueJsx()]; |
|||
|
|||
const appConfigPlugin = await createAppConfigPlugin({ root, isBuild }); |
|||
vitePlugins.push(appConfigPlugin); |
|||
|
|||
// vite-plugin-windicss
|
|||
vitePlugins.push(windiCSS()); |
|||
|
|||
// vite-plugin-html
|
|||
vitePlugins.push(configHtmlPlugin({ isBuild })); |
|||
|
|||
// vite-plugin-svg-icons
|
|||
vitePlugins.push(configSvgIconsPlugin({ isBuild })); |
|||
|
|||
// vite-plugin-purge-icons
|
|||
vitePlugins.push(purgeIcons()); |
|||
|
|||
// The following plugins only work in the production environment
|
|||
if (isBuild) { |
|||
// rollup-plugin-gzip
|
|||
vitePlugins.push( |
|||
configCompressPlugin({ |
|||
compress, |
|||
}), |
|||
); |
|||
} |
|||
|
|||
// rollup-plugin-visualizer
|
|||
if (enableAnalyze) { |
|||
vitePlugins.push(configVisualizerConfig()); |
|||
} |
|||
|
|||
// vite-plugin-mock
|
|||
if (enableMock) { |
|||
vitePlugins.push(configMockPlugin({ isBuild })); |
|||
} |
|||
|
|||
return vitePlugins; |
|||
} |
|||
|
|||
export { createPlugins }; |
|||
@ -0,0 +1,14 @@ |
|||
/** |
|||
* Package file volume analysis |
|||
*/ |
|||
import { type PluginOption } from 'vite'; |
|||
import visualizer from 'rollup-plugin-visualizer'; |
|||
|
|||
export function configVisualizerConfig() { |
|||
return visualizer({ |
|||
filename: './node_modules/.cache/visualizer/stats.html', |
|||
open: true, |
|||
gzipSize: true, |
|||
brotliSize: true, |
|||
}) as PluginOption; |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
import dotenv from 'dotenv'; |
|||
import { readFile } from 'fs-extra'; |
|||
import { join } from 'node:path'; |
|||
|
|||
/** |
|||
* 获取当前环境下生效的配置文件名 |
|||
*/ |
|||
function getConfFiles() { |
|||
const script = process.env.npm_lifecycle_script as string; |
|||
const reg = new RegExp('--mode ([a-z_\\d]+)'); |
|||
const result = reg.exec(script); |
|||
if (result) { |
|||
const mode = result[1]; |
|||
return ['.env', `.env.${mode}`]; |
|||
} |
|||
return ['.env', '.env.production']; |
|||
} |
|||
|
|||
/** |
|||
* 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 readFile(join(process.cwd(), confFile), { encoding: 'utf8' }); |
|||
const env = dotenv.parse(envPath); |
|||
envConfig = { ...envConfig, ...env }; |
|||
} catch (e) { |
|||
console.error(`Error in parsing ${confFile}`, e); |
|||
} |
|||
} |
|||
const reg = new RegExp(`^(${match})`); |
|||
Object.keys(envConfig).forEach((key) => { |
|||
if (!reg.test(key)) { |
|||
Reflect.deleteProperty(envConfig, key); |
|||
} |
|||
}); |
|||
return envConfig; |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
import { createHash } from 'node:crypto'; |
|||
|
|||
function createContentHash(content: string, hashLSize = 12) { |
|||
const hash = createHash('sha256').update(content); |
|||
return hash.digest('hex').slice(0, hashLSize); |
|||
} |
|||
|
|||
export { createContentHash }; |
|||
@ -0,0 +1,5 @@ |
|||
{ |
|||
"$schema": "https://json.schemastore.org/tsconfig", |
|||
"extends": "@vben/ts-config/node.json", |
|||
"include": ["src"] |
|||
} |
|||
File diff suppressed because it is too large
@ -1,3 +1,4 @@ |
|||
packages: |
|||
- 'internal/*' |
|||
- 'packages/*' |
|||
- 'apps/*' |
|||
|
|||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
File diff suppressed because it is too large
@ -1,95 +1,24 @@ |
|||
import type { UserConfig, ConfigEnv } from 'vite'; |
|||
import pkg from './package.json'; |
|||
import dayjs from 'dayjs'; |
|||
import { loadEnv } from 'vite'; |
|||
import { resolve } from 'path'; |
|||
import { generateModifyVars } from './build/generate/generateModifyVars'; |
|||
import { createProxy } from './build/vite/proxy'; |
|||
import { wrapperEnv } from './build/utils'; |
|||
import { createVitePlugins } from './build/vite/plugin'; |
|||
import { OUTPUT_DIR } from './build/constant'; |
|||
import { defineApplicationConfig } from '@vben/vite-config'; |
|||
|
|||
function pathResolve(dir: string) { |
|||
return resolve(process.cwd(), '.', dir); |
|||
} |
|||
|
|||
const { dependencies, devDependencies, name, version } = pkg; |
|||
const __APP_INFO__ = { |
|||
pkg: { dependencies, devDependencies, name, version }, |
|||
lastBuildTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), |
|||
}; |
|||
|
|||
export default async ({ command, mode }: ConfigEnv): Promise<UserConfig> => { |
|||
const root = process.cwd(); |
|||
|
|||
const env = loadEnv(mode, root); |
|||
|
|||
// The boolean type read by loadEnv is a string. This function can be converted to boolean type
|
|||
const viteEnv = wrapperEnv(env); |
|||
|
|||
const { VITE_PUBLIC_PATH, VITE_PROXY } = viteEnv; |
|||
|
|||
const isBuild = command === 'build'; |
|||
|
|||
return { |
|||
base: VITE_PUBLIC_PATH, |
|||
root, |
|||
resolve: { |
|||
alias: [ |
|||
{ |
|||
find: 'vue-i18n', |
|||
replacement: 'vue-i18n/dist/vue-i18n.cjs.js', |
|||
}, |
|||
// /@/xxxx => src/xxxx
|
|||
{ |
|||
find: /\/@\//, |
|||
replacement: pathResolve('src') + '/', |
|||
}, |
|||
// /#/xxxx => types/xxxx
|
|||
{ |
|||
find: /\/#\//, |
|||
replacement: pathResolve('types') + '/', |
|||
}, |
|||
], |
|||
}, |
|||
export default defineApplicationConfig({ |
|||
overrides: { |
|||
server: { |
|||
host: true, |
|||
// Load proxy configuration from .env
|
|||
proxy: createProxy(VITE_PROXY), |
|||
}, |
|||
esbuild: { |
|||
drop: ['console', 'debugger'], |
|||
}, |
|||
build: { |
|||
target: 'es2015', |
|||
cssTarget: 'chrome80', |
|||
outDir: OUTPUT_DIR, |
|||
reportCompressedSize: false, |
|||
chunkSizeWarningLimit: 2000, |
|||
}, |
|||
define: { |
|||
__APP_INFO__: JSON.stringify(__APP_INFO__), |
|||
}, |
|||
|
|||
css: { |
|||
preprocessorOptions: { |
|||
less: { |
|||
modifyVars: generateModifyVars(), |
|||
javascriptEnabled: true, |
|||
proxy: { |
|||
'/basic-api': { |
|||
target: 'http://localhost:3000', |
|||
changeOrigin: true, |
|||
ws: true, |
|||
rewrite: (path) => path.replace(new RegExp(`^/basic-api`), ''), |
|||
// only https
|
|||
// secure: false
|
|||
}, |
|||
'/upload': { |
|||
target: 'http://localhost:3300/upload', |
|||
changeOrigin: true, |
|||
ws: true, |
|||
rewrite: (path) => path.replace(new RegExp(`^/upload`), ''), |
|||
}, |
|||
}, |
|||
}, |
|||
|
|||
// The vite plugin used by the project. The quantity is large, so it is separately extracted and managed
|
|||
plugins: await createVitePlugins(viteEnv, isBuild), |
|||
|
|||
optimizeDeps: { |
|||
// @iconify/iconify: The dependency is dynamically and virtually loaded by @purge-icons/generated, so it needs to be specified explicitly
|
|||
include: [ |
|||
'@iconify/iconify', |
|||
'ant-design-vue/es/locale/zh_CN', |
|||
'ant-design-vue/es/locale/en_US', |
|||
], |
|||
}, |
|||
}; |
|||
}; |
|||
}, |
|||
}); |
|||
|
|||
Loading…
Reference in new issue