Compare commits

..

24 Commits

Author SHA1 Message Date
vben
358b77fe96 chore: release 2.9.1 2023-04-05 00:21:09 +08:00
vben
5e99463cd0 perf: Refactor vite configuration 2023-04-05 00:20:48 +08:00
vben
5e4be0adbc chore: update config 2023-04-04 17:29:58 +08:00
vben
c2590cbfb5 Merge branch 'main' of https://github.com/anncwb/vue-vben-admin 2023-04-04 17:14:06 +08:00
vben
08d4e34b77 chore: config ts-config 2023-04-04 17:13:09 +08:00
前端爱码士
7a8978dcff fix: 解决打包报内存溢出问题 (#2672) 2023-04-04 17:11:03 +08:00
oooplz
af54b0fbc6 feat: 多路径字段获取值 (#2664) 2023-04-04 16:59:43 +08:00
Cherelle Spencer
4418eccfab fix(deepMerge): 修复递归合并操作, 合并数组未去重的bug (#2667)
Co-authored-by: 苗大 <caoshengmiao@hypergryph.com>
2023-04-04 16:59:30 +08:00
Cherelle Spencer
02d41197b6 fix: propTypes.extend()方法已经废弃, 改为官方推荐的ES6+方法 (#2670)
Co-authored-by: 苗大 <caoshengmiao@hypergryph.com>
2023-04-04 16:59:19 +08:00
vben
ba2415c3b4 chore: Extract lint config 2023-04-04 16:55:34 +08:00
vben
84a5fc1256 chore: 项目所有依赖升级到最新 2023-04-02 00:20:01 +08:00
vben
8180b2d15f chore: 暂时移除主题切换及暗黑模式切换功能 2023-04-02 00:02:52 +08:00
vben
699cf6d7af chore: remove vite-plugin-pwa 2023-04-01 23:56:02 +08:00
vben
fea323cff1 chore: css is no longer imported on demand 2023-04-01 23:53:26 +08:00
vben
dc39567a14 chore: remove @vitejs/plugin-legacy vite-plugin-imagemin 2023-04-01 23:48:19 +08:00
vben
ab74480083 chore: Requires pnpm 8 and node 16 2023-04-01 23:39:36 +08:00
Wit〆苗大
9579a45b3c feat(excel导出): 增加批量导出功能, 增加demo示例 (#2657)
* feat(excel导出): 批量导出功能

* feat(excel导出): 批量导出功能, 增加参数类型定义;增加demo示例

---------

Co-authored-by: 苗大 <caoshengmiao@hypergryph.com>
2023-03-30 09:53:55 +08:00
Wit〆苗大
302afe5860 fix(项目配置): 修复 #2652 重置项目配置,导致左侧菜单栏整体变成白色 (#2656)
Co-authored-by: 苗大 <caoshengmiao@hypergryph.com>
2023-03-30 09:53:44 +08:00
Wit〆苗大
7b21e9dc24 fix(useFormEvents): 修复#2648修改导致的末尾大括号丢失 (#2655)
Co-authored-by: 苗大 <caoshengmiao@hypergryph.com>
2023-03-30 09:53:24 +08:00
Wit〆苗大
f05b90f86b fix(form-design): CollapseItem组件页面css添加prefixCls前缀, 防止打包后污染全局样式 (#2654)
Co-authored-by: 苗大 <caoshengmiao@hypergryph.com>
2023-03-30 09:53:03 +08:00
oooplz
1b30834eb3 feat: 解构字段设置value (#2648) 2023-03-29 14:59:11 +08:00
Wit〆苗大
c5f97b9d9f perf(BasicTable): css 内 ant-tag margin改动仅对表格内单元格生效, 防止影响其他地方的tag (#2653)
* perf(BasicTable): ant-tag margin改动仅对表格内单元格生效, 防止影响其他地方的tag, 如搜索表单内时间范围自定预设范围的tag

* perf(BasicTable):css 内 ant-tag margin改动仅对表格内单元格生效, 防止影响其他地方的tag, 如搜索表单内时间范围自定预设范围的tag

---------

Co-authored-by: 苗大 <caoshengmiao@hypergryph.com>
2023-03-29 14:58:42 +08:00
Lantz
3a1ec7ac86 fix(hooks): 修改在使用useEcharts时, getInstance无法获取到最新的echarts实例的问题 (#2650) 2023-03-29 14:58:18 +08:00
Kirk Lin
4c63b1abb7 refactor: deepMerge (#2649) 2023-03-28 21:34:11 +08:00
235 changed files with 6386 additions and 20968 deletions

4
.browserslistrc Normal file
View File

@@ -0,0 +1,4 @@
> 1%
last 2 versions
not dead
not ie 11

3
.env
View File

@@ -1,6 +1,3 @@
# port
VITE_PORT = 3100
# spa-title
VITE_GLOB_APP_TITLE = Vben Admin

23
.env.analyze Normal file
View File

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

View File

@@ -7,10 +7,6 @@ VITE_PUBLIC_PATH = /
# Cross-domain proxy, you can configure multiple
# Please note that no line breaks
VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","http://localhost:3300/upload"]]
# VITE_PROXY=[["/api","https://vvbin.cn/test"]]
# Delete console
VITE_DROP_CONSOLE = false
# Basic interface address SPA
VITE_GLOB_API_URL=/basic-api

View File

@@ -4,16 +4,11 @@ VITE_USE_MOCK = true
# public path
VITE_PUBLIC_PATH = /
# Delete console
VITE_DROP_CONSOLE = true
# 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'
# Whether to delete origin files when using compress, default false
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
# Basic interface address SPA
VITE_GLOB_API_URL=/basic-api
@@ -24,12 +19,3 @@ VITE_GLOB_UPLOAD_URL=/upload
# Interface prefix
VITE_GLOB_API_URL_PREFIX=
# Whether to enable image compression
VITE_USE_IMAGEMIN= true
# use pwa
VITE_USE_PWA = false
# Is it compatible with older browsers
VITE_LEGACY = false

View File

@@ -5,17 +5,11 @@ VITE_USE_MOCK = true
# public path
VITE_PUBLIC_PATH = /
# Delete console
VITE_DROP_CONSOLE = true
# 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'
# Whether to delete origin files when using compress, default false
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
# Basic interface address SPA
VITE_GLOB_API_URL=/basic-api
@@ -25,12 +19,3 @@ VITE_GLOB_UPLOAD_URL=/upload
# Interface prefix
VITE_GLOB_API_URL_PREFIX=
# Whether to enable image compression
VITE_USE_IMAGEMIN= true
# use pwa
VITE_USE_PWA = false
# Is it compatible with older browsers
VITE_LEGACY = false

View File

@@ -1,76 +1,4 @@
module.exports = {
root: true,
env: {
browser: true,
node: true,
es6: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true,
},
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
rules: {
'vue/script-setup-uses-vars': 'error',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'vue/custom-event-name-casing': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'space-before-function-paren': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
'vue/multi-word-component-names': 'off',
},
extends: ['@vben'],
};

View File

@@ -98,7 +98,7 @@ jobs:
- name: Build
env:
NODE_OPTIONS: "--max_old_space_size=4096"
NODE_OPTIONS: '--max_old_space_size=4096'
run: |
yarn install
yarn run build
@@ -116,8 +116,3 @@ jobs:
PUBLISH_BRANCH: gh-pages
PUBLISH_DIR: ./dist
CNAME: vben.vvbin.cn

2
.gitignore vendored
View File

@@ -1,8 +1,8 @@
node_modules
.DS_Store
dist
.npmrc
.cache
.turbo
tests/server/static
tests/server/static/upload

View File

@@ -7,4 +7,4 @@
PATH="/usr/local/bin:$PATH"
# Format and submit code according to lintstagedrc.js configuration
npm run lint:lint-staged
pnpm exec lint-staged

7
.npmrc Normal file
View File

@@ -0,0 +1,7 @@
public-hoist-pattern[]=husky
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
public-hoist-pattern[]=lint-staged
public-hoist-pattern[]=*stylelint*
public-hoist-pattern[]=@commitlint/cli
public-hoist-pattern[]=@vben/eslint-config

View File

@@ -1,9 +1,10 @@
/dist/*
dist
.local
.output.js
/node_modules/**
node_modules
**/*.svg
**/*.sh
/public/*
public
.npmrc

View File

@@ -7,4 +7,13 @@ module.exports = {
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
plugins: ['prettier-plugin-packagejson'],
overrides: [
{
files: '.*rc',
options: {
parser: 'json',
},
},
],
};

View File

@@ -1,3 +1,2 @@
/dist/*
/public/*
public/*
dist
public

4
.stylelintrc.js Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben/stylelint-config'],
};

View File

@@ -84,7 +84,8 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
"[vue]": {
"editor.codeActionsOnSave": {
@@ -164,7 +165,9 @@
"*.ts": "$(capture).test.ts, $(capture).test.tsx",
"*.tsx": "$(capture).test.ts, $(capture).test.tsx",
"*.env": "$(capture).env.*",
"package.json": "pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore"
"CHANGELOG.md": "CHANGELOG*",
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,README*,.npmrc,.browserslistrc",
".eslintrc.js": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.js,.prettierrc.js,.stylelintrc.js"
},
"terminal.integrated.scrollback": 10000
}

View File

@@ -131,9 +131,6 @@ If these plugins are helpful to you, you can give a star support
- [vite-plugin-mock](https://github.com/anncwb/vite-plugin-mock) - Used for local and development environment data mock
- [vite-plugin-html](https://github.com/anncwb/vite-plugin-html) - Used for html template conversion and compression
- [vite-plugin-style-import](https://github.com/anncwb/vite-plugin-style-import) - Used for component library style introduction on demand
- [vite-plugin-theme](https://github.com/anncwb/vite-plugin-theme) - Used for online switching of theme colors and other color-related configurations
- [vite-plugin-imagemin](https://github.com/anncwb/vite-plugin-imagemin) - Used to pack compressed image resources
- [vite-plugin-compression](https://github.com/anncwb/vite-plugin-compression) - Used to pack input .gz|.brotil files
- [vite-plugin-svg-icons](https://github.com/anncwb/vite-plugin-svg-icons) - Used to quickly generate svg sprite

View File

@@ -141,9 +141,6 @@ pnpm build
- [vite-plugin-mock](https://github.com/anncwb/vite-plugin-mock) - 用于本地及开发环境数据 mock
- [vite-plugin-html](https://github.com/anncwb/vite-plugin-html) - 用于 html 模版转换及压缩
- [vite-plugin-style-import](https://github.com/anncwb/vite-plugin-style-import) - 用于组件库样式按需引入
- [vite-plugin-theme](https://github.com/anncwb/vite-plugin-theme) - 用于在线切换主题色等颜色相关配置
- [vite-plugin-imagemin](https://github.com/anncwb/vite-plugin-imagemin) - 用于打包压缩图片资源
- [vite-plugin-compression](https://github.com/anncwb/vite-plugin-compression) - 用于打包输出.gz|.brotil 文件
- [vite-plugin-svg-icons](https://github.com/anncwb/vite-plugin-svg-icons) - 用于快速生成 svg 雪碧图

View File

View File

@@ -3,34 +3,34 @@
"version": "1.0.0",
"license": "MIT",
"scripts": {
"start": "nodemon",
"build": "rimraf ./dist && tsup ./index.ts --dts --format cjs,esm ",
"compile": "rimraf ./dist && tsup ./index.ts --dts --format cjs,esm ",
"prod": "npx pm2 start ecosystem.config.js --env production",
"restart": "pm2 restart ecosystem.config.js --env production",
"start": "nodemon",
"stop": "npx pm2 stop ecosystem.config.js"
},
"dependencies": {
"fs-extra": "^10.0.1",
"koa": "^2.13.4",
"koa-body": "^4.2.0",
"koa-bodyparser": "^4.3.0",
"fs-extra": "^11.1.1",
"koa": "^2.14.1",
"koa-body": "^6.0.1",
"koa-bodyparser": "^4.4.0",
"koa-route": "^3.2.0",
"koa-router": "^10.1.1",
"koa-router": "^12.0.0",
"koa-static": "^5.0.0",
"koa-websocket": "^6.0.0",
"koa-websocket": "^7.0.0",
"koa2-cors": "^2.0.6"
},
"devDependencies": {
"@types/koa": "^2.13.4",
"@types/koa": "^2.13.6",
"@types/koa-bodyparser": "^5.0.2",
"@types/koa-router": "^7.4.4",
"@types/node": "^17.0.21",
"nodemon": "^2.0.15",
"pm2": "^5.2.0",
"rimraf": "^3.0.2",
"ts-node": "^10.7.0",
"tsconfig-paths": "^3.14.0",
"tsup": "^5.12.1",
"typescript": "^4.6.2"
"@types/node": "^18.15.11",
"nodemon": "^2.0.22",
"pm2": "^5.3.0",
"rimraf": "^4.4.1",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"tsup": "^6.7.0",
"typescript": "^5.0.3"
}
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/node-server.json",
"compilerOptions": {
"noImplicitAny": false
}
}

View File

@@ -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('-'));
}

View File

@@ -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';

View File

@@ -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();

View File

@@ -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, '');
};

View File

@@ -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 });
}

View File

@@ -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();

View File

@@ -1,92 +0,0 @@
import fs from 'fs';
import path from 'path';
import dotenv from 'dotenv';
export function isDevFn(mode: string): boolean {
return mode === 'development';
}
export function isProdFn(mode: string): boolean {
return mode === 'production';
}
/**
* Whether to generate package preview
*/
export function isReportMode(): boolean {
return process.env.REPORT === 'true';
}
// Read all environment variable configuration files to process.env
export function wrapperEnv(envConf: Recordable): ViteEnv {
const ret: any = {};
for (const envName of Object.keys(envConf)) {
let realName = envConf[envName].replace(/\\n/g, '\n');
realName = realName === 'true' ? true : realName === 'false' ? false : realName;
if (envName === 'VITE_PORT') {
realName = Number(realName);
}
if (envName === 'VITE_PROXY' && realName) {
try {
realName = JSON.parse(realName.replace(/'/g, '"'));
} catch (error) {
realName = '';
}
}
ret[envName] = realName;
// if (typeof realName === 'string') {
// process.env[envName] = realName;
// } else if (typeof realName === 'object') {
// process.env[envName] = JSON.stringify(realName);
// }
}
return ret;
}
/**
* 获取当前环境下生效的配置文件名
*/
function getConfFiles() {
const script = process.env.npm_lifecycle_script;
const reg = new RegExp('--mode ([a-z_\\d]+)');
const result = reg.exec(script as string) as any;
if (result) {
const mode = result[1] as string;
return ['.env', `.env.${mode}`];
}
return ['.env', '.env.production'];
}
/**
* Get the environment variables starting with the specified prefix
* @param match prefix
* @param confFiles ext
*/
export function getEnvConfig(match = 'VITE_GLOB_', confFiles = getConfFiles()) {
let envConfig = {};
confFiles.forEach((item) => {
try {
const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)));
envConfig = { ...envConfig, ...env };
} catch (e) {
console.error(`Error in parsing ${item}`, e);
}
});
const reg = new RegExp(`^(${match})`);
Object.keys(envConfig).forEach((key) => {
if (!reg.test(key)) {
Reflect.deleteProperty(envConfig, key);
}
});
return envConfig;
}
/**
* Get user root directory
* @param dir file path
*/
export function getRootPath(...dir: string[]) {
return path.resolve(process.cwd(), ...dir);
}

View File

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

View File

@@ -1,34 +0,0 @@
// Image resource files used to compress the output of the production environment
// https://github.com/anncwb/vite-plugin-imagemin
import viteImagemin from 'vite-plugin-imagemin';
export function configImageminPlugin() {
const plugin = viteImagemin({
gifsicle: {
optimizationLevel: 7,
interlaced: false,
},
optipng: {
optimizationLevel: 7,
},
mozjpeg: {
quality: 20,
},
pngquant: {
quality: [0.8, 0.9],
speed: 4,
},
svgo: {
plugins: [
{
name: 'removeViewBox',
},
{
name: 'removeEmptyAttrs',
active: false,
},
],
},
});
return plugin;
}

View File

@@ -1,82 +0,0 @@
import { PluginOption } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import legacy from '@vitejs/plugin-legacy';
import purgeIcons from 'vite-plugin-purge-icons';
import windiCSS from 'vite-plugin-windicss';
import VitePluginCertificate from 'vite-plugin-mkcert';
//import vueSetupExtend from 'vite-plugin-vue-setup-extend';
import { configHtmlPlugin } from './html';
import { configPwaConfig } from './pwa';
import { configMockPlugin } from './mock';
import { configCompressPlugin } from './compress';
import { configStyleImportPlugin } from './styleImport';
import { configVisualizerConfig } from './visualizer';
import { configThemePlugin } from './theme';
import { configImageminPlugin } from './imagemin';
import { configSvgIconsPlugin } from './svgSprite';
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
const {
VITE_USE_IMAGEMIN,
VITE_USE_MOCK,
VITE_LEGACY,
VITE_BUILD_COMPRESS,
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE,
} = viteEnv;
const vitePlugins: (PluginOption | PluginOption[])[] = [
// have to
vue(),
// have to
vueJsx(),
// support name
//vueSetupExtend(),
VitePluginCertificate({
source: 'coding',
}),
];
// vite-plugin-windicss
vitePlugins.push(windiCSS());
// @vitejs/plugin-legacy
VITE_LEGACY && isBuild && vitePlugins.push(legacy());
// 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());
// vite-plugin-style-import
vitePlugins.push(configStyleImportPlugin(isBuild));
// rollup-plugin-visualizer
vitePlugins.push(configVisualizerConfig());
// vite-plugin-theme
vitePlugins.push(configThemePlugin(isBuild));
// The following plugins only work in the production environment
if (isBuild) {
// vite-plugin-imagemin
VITE_USE_IMAGEMIN && vitePlugins.push(configImageminPlugin());
// rollup-plugin-gzip
vitePlugins.push(
configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE),
);
// vite-plugin-pwa
vitePlugins.push(configPwaConfig(viteEnv));
}
return vitePlugins;
}

View File

@@ -1,33 +0,0 @@
/**
* Zero-config PWA for Vite
* https://github.com/antfu/vite-plugin-pwa
*/
import { VitePWA } from 'vite-plugin-pwa';
export function configPwaConfig(env: ViteEnv) {
const { VITE_USE_PWA, VITE_GLOB_APP_TITLE, VITE_GLOB_APP_SHORT_NAME } = env;
if (VITE_USE_PWA) {
// vite-plugin-pwa
const pwaPlugin = VitePWA({
manifest: {
name: VITE_GLOB_APP_TITLE,
short_name: VITE_GLOB_APP_SHORT_NAME,
icons: [
{
src: './resource/img/pwa-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: './resource/img/pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
},
});
return pwaPlugin;
}
return [];
}

View File

@@ -1,83 +0,0 @@
/**
* Introduces component library styles on demand.
* https://github.com/anncwb/vite-plugin-style-import
*/
import { createStyleImportPlugin, VxeTableResolve } from 'vite-plugin-style-import';
export function configStyleImportPlugin(_isBuild: boolean) {
if (!_isBuild) {
return [];
}
const styleImportPlugin = createStyleImportPlugin({
libs: [
{
libraryName: 'ant-design-vue',
esModule: true,
resolveStyle: (name) => {
// 这里是无需额外引入样式文件的“子组件”列表
const ignoreList = [
'anchor-link',
'sub-menu',
'menu-item',
'menu-divider',
'menu-item-group',
'breadcrumb-item',
'breadcrumb-separator',
'form-item',
'step',
'select-option',
'select-opt-group',
'card-grid',
'card-meta',
'collapse-panel',
'descriptions-item',
'list-item',
'list-item-meta',
'table-column',
'table-column-group',
'tab-pane',
'tab-content',
'timeline-item',
'tree-node',
'skeleton-input',
'skeleton-avatar',
'skeleton-title',
'skeleton-paragraph',
'skeleton-image',
'skeleton-button',
];
// 这里是需要额外引入样式的子组件列表
// 单独引入子组件时需引入组件样式,否则会在打包后导致子组件样式丢失
const replaceList = {
textarea: 'input',
'typography-text': 'typography',
'typography-title': 'typography',
'typography-paragraph': 'typography',
'typography-link': 'typography',
'dropdown-button': 'dropdown',
'input-password': 'input',
'input-search': 'input',
'input-group': 'input',
'radio-group': 'radio',
'checkbox-group': 'checkbox',
'layout-sider': 'layout',
'layout-content': 'layout',
'layout-footer': 'layout',
'layout-header': 'layout',
'month-picker': 'date-picker',
'range-picker': 'date-picker',
'image-preview-group': 'image',
};
return ignoreList.includes(name)
? ''
: replaceList.hasOwnProperty(name)
? `ant-design-vue/es/${replaceList[name]}/style/index`
: `ant-design-vue/es/${name}/style/index`;
},
},
],
resolves: [VxeTableResolve()],
});
return styleImportPlugin;
}

View File

@@ -1,17 +0,0 @@
/**
* Vite Plugin for fast creating SVG sprites.
* https://github.com/anncwb/vite-plugin-svg-icons
*/
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import path from 'path';
export function configSvgIconsPlugin(isBuild: boolean) {
const svgIconsPlugin = createSvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
svgoOptions: isBuild,
// default
symbolId: 'icon-[dir]-[name]',
});
return svgIconsPlugin;
}

View File

@@ -1,89 +0,0 @@
/**
* Vite plugin for website theme color switching
* https://github.com/anncwb/vite-plugin-theme
*/
import type { PluginOption } from 'vite';
import path from 'path';
import {
viteThemePlugin,
antdDarkThemePlugin,
mixLighten,
mixDarken,
tinycolor,
} from 'vite-plugin-theme';
import { getThemeColors, generateColors } from '../../config/themeConfig';
import { generateModifyVars } from '../../generate/generateModifyVars';
export function configThemePlugin(isBuild: boolean): PluginOption[] {
const colors = generateColors({
mixDarken,
mixLighten,
tinycolor,
});
const plugin = [
viteThemePlugin({
resolveSelector: (s) => {
s = s.trim();
switch (s) {
case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
return '.ant-steps-item-icon > .ant-steps-icon';
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)':
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover':
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active':
return s;
case '.ant-steps-item-icon > .ant-steps-icon':
return s;
case '.ant-select-item-option-selected:not(.ant-select-item-option-disabled)':
return s;
default:
if (s.indexOf('.ant-btn') >= -1) {
// 按钮被重新定制过需要过滤掉class防止覆盖
return s;
}
}
return s.startsWith('[data-theme') ? s : `[data-theme] ${s}`;
},
colorVariables: [...getThemeColors(), ...colors],
}),
antdDarkThemePlugin({
preloadFiles: [
path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/antd.less'),
//path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/antd.dark.less'),
path.resolve(process.cwd(), 'src/design/index.less'),
],
filter: (id) => (isBuild ? !id.endsWith('antd.less') : true),
// extractCss: false,
darkModifyVars: {
...generateModifyVars(true),
'text-color': '#c9d1d9',
'primary-1': 'rgb(255 255 255 / 8%)',
'text-color-base': '#c9d1d9',
'component-background': '#151515',
'heading-color': 'rgb(255 255 255 / 65%)',
// black: '#0e1117',
// #8b949e
'text-color-secondary': '#8b949e',
'border-color-base': '#303030',
// 'border-color-split': '#30363d',
'item-active-bg': '#111b26',
'app-content-background': '#1e1e1e',
'tree-node-selected-bg': '#11263c',
'alert-success-border-color': '#274916',
'alert-success-bg-color': '#162312',
'alert-success-icon-color': '#49aa19',
'alert-info-border-color': '#153450',
'alert-info-bg-color': '#111b26',
'alert-info-icon-color': '#177ddc',
'alert-warning-border-color': '#594214',
'alert-warning-bg-color': '#2b2111',
'alert-warning-icon-color': '#d89614',
'alert-error-border-color': '#58181c',
'alert-error-bg-color': '#2a1215',
'alert-error-icon-color': '#a61d24',
},
}),
];
return plugin as unknown as PluginOption[];
}

View File

@@ -1,17 +0,0 @@
/**
* Package file volume analysis
*/
import visualizer from 'rollup-plugin-visualizer';
import { isReportMode } from '../../utils';
export function configVisualizerConfig() {
if (isReportMode()) {
return visualizer({
filename: './node_modules/.cache/visualizer/stats.html',
open: true,
gzipSize: true,
brotliSize: true,
}) as Plugin;
}
return [];
}

View File

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

View File

@@ -8,20 +8,10 @@
name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
/>
<title><%= title %></title>
<title><%= VITE_GLOB_APP_TITLE %></title>
<link rel="icon" href="/favicon.ico" />
</head>
<body>
<script>
(() => {
var htmlRoot = document.getElementById('htmlRoot');
var theme = window.localStorage.getItem('__APP__DARK__MODE__');
if (htmlRoot && theme) {
htmlRoot.setAttribute('data-theme', theme);
theme = htmlRoot = null;
}
})();
</script>
<div id="app">
<style>
html[data-theme='dark'] .app-loading {
@@ -34,39 +24,39 @@
.app-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
flex-direction: column;
background-color: #f4f7f9;
}
.app-loading .app-loading-wrap {
display: flex;
position: absolute;
top: 50%;
left: 50%;
display: flex;
transform: translate3d(-50%, -50%, 0);
justify-content: center;
align-items: center;
flex-direction: column;
align-items: center;
justify-content: center;
transform: translate3d(-50%, -50%, 0);
}
.app-loading .dots {
display: flex;
padding: 98px;
justify-content: center;
align-items: center;
justify-content: center;
padding: 98px;
}
.app-loading .app-loading-title {
display: flex;
margin-top: 30px;
font-size: 30px;
color: rgb(0 0 0 / 85%);
justify-content: center;
align-items: center;
justify-content: center;
margin-top: 30px;
color: rgb(0 0 0 / 85%);
font-size: 30px;
}
.app-loading .app-loading-logo {
@@ -77,28 +67,28 @@
}
.dot {
position: relative;
display: inline-block;
position: relative;
box-sizing: border-box;
width: 48px;
height: 48px;
margin-top: 30px;
font-size: 32px;
transform: rotate(45deg);
box-sizing: border-box;
animation: antRotate 1.2s infinite linear;
animation: ant-rotate 1.2s infinite linear;
font-size: 32px;
}
.dot i {
position: absolute;
display: block;
position: absolute;
width: 20px;
height: 20px;
background-color: #0065cc;
border-radius: 100%;
opacity: 30%;
transform: scale(0.75);
animation: antSpinMove 1s infinite linear alternate;
transform-origin: 50% 50%;
animation: ant-spin-move 1s infinite linear alternate;
border-radius: 100%;
opacity: 0.3;
background-color: #0065cc;
}
.dot i:nth-child(1) {
@@ -123,34 +113,38 @@
left: 0;
animation-delay: 1.2s;
}
@keyframes antRotate {
@keyframes ant-rotate {
to {
transform: rotate(405deg);
}
}
@keyframes antRotate {
@keyframes ant-rotate {
to {
transform: rotate(405deg);
}
}
@keyframes antSpinMove {
@keyframes ant-spin-move {
to {
opacity: 100%;
opacity: 1;
}
}
@keyframes antSpinMove {
@keyframes ant-spin-move {
to {
opacity: 100%;
opacity: 1;
}
}
</style>
<div class="app-loading">
<div class="app-loading-wrap">
<img src="/resource/img/logo.png" class="app-loading-logo" alt="Logo" />
<img src="/logo.png" class="app-loading-logo" alt="Logo" />
<div class="app-loading-dots">
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
</div>
<div class="app-loading-title"><%= title %></div>
<div class="app-loading-title"><%= VITE_GLOB_APP_TITLE %></div>
</div>
</div>
</div>

View File

@@ -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: 自定义',
},
};

View File

@@ -0,0 +1,15 @@
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile

View File

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben'],
};

View File

@@ -0,0 +1,10 @@
dist
.local
.output.js
node_modules
**/*.svg
**/*.sh
public
.npmrc

View File

@@ -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',
},
},
],
};

View File

@@ -0,0 +1,2 @@
dist
public

View File

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben/stylelint-config'],
};

View File

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

View File

@@ -0,0 +1,42 @@
{
"name": "@vben/eslint-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/eslint-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"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"eslint": "^8.37.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.10.0",
"vue-eslint-parser": "^9.1.1"
}
}

View File

@@ -0,0 +1,82 @@
export default {
env: {
browser: true,
node: true,
es6: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true,
},
project: './tsconfig.*?.json',
createDefaultProgram: false,
extraFileExtensions: ['.vue'],
},
plugins: ['vue', '@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
rules: {
'no-case-declarations': 'off',
'vue/script-setup-uses-vars': 'error',
'vue/no-reserved-component-names': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'vue/custom-event-name-casing': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'space-before-function-paren': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
'vue/multi-word-component-names': 'off',
},
};

View File

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

View File

@@ -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: 自定义',
},
};

View File

@@ -0,0 +1,15 @@
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile

View File

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben'],
};

View File

@@ -0,0 +1,10 @@
dist
.local
.output.js
node_modules
**/*.svg
**/*.sh
public
.npmrc

View File

@@ -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',
},
},
],
};

View File

@@ -0,0 +1,2 @@
dist
public

View File

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben/stylelint-config'],
};

View File

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

View File

@@ -0,0 +1,49 @@
{
"name": "@vben/stylelint-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/stylelint-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"
},
"devDependencies": {
"postcss": "^8.4.21",
"postcss-html": "^1.5.0",
"postcss-less": "^6.0.0",
"postcss-scss": "^4.0.6",
"prettier": "^2.8.7",
"stylelint": "^15.4.0",
"stylelint-config-property-sort-order-smacss": "^9.1.0",
"stylelint-config-recommended": "^11.0.0",
"stylelint-config-recommended-scss": "^9.0.1",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^32.0.0",
"stylelint-config-standard-scss": "^7.0.1",
"stylelint-order": "^6.0.3",
"stylelint-prettier": "^3.0.0"
}
}

View File

@@ -1,8 +1,26 @@
module.exports = {
root: true,
plugins: ['stylelint-order'],
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
customSyntax: 'postcss-html',
export default {
extends: ['stylelint-config-standard', 'stylelint-config-property-sort-order-smacss'],
plugins: ['stylelint-order', 'stylelint-prettier'],
// customSyntax: 'postcss-html',
overrides: [
{
files: ['**/*.(css|html|vue)'],
customSyntax: 'postcss-html',
},
{
files: ['*.less', '**/*.less'],
customSyntax: 'postcss-less',
extends: ['stylelint-config-standard', 'stylelint-config-recommended-vue'],
},
{
files: ['*.scss', '**/*.scss'],
customSyntax: 'postcss-scss',
extends: ['stylelint-config-standard-scss', 'stylelint-config-recommended-vue/scss'],
rule: {
'scss/percent-placeholder-pattern': null,
},
},
],
rules: {
'function-no-unknown': null,
'selector-class-pattern': null,
@@ -32,18 +50,15 @@ module.exports = {
'each',
'include',
'mixin',
'extend',
],
},
],
'no-empty-source': null,
'string-quotes': null,
'named-grid-areas-no-invalid': null,
'unicode-bom': 'never',
'no-descending-specificity': null,
'font-family-no-missing-generic-family-keyword': null,
'declaration-colon-space-after': 'always-single-line',
'declaration-colon-space-before': 'never',
// 'declaration-block-trailing-semicolon': 'always',
'rule-empty-line-before': [
'always',
{
@@ -67,34 +82,8 @@ module.exports = {
},
'rules',
],
{ severity: 'warning' },
{ severity: 'error' },
],
},
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
overrides: [
{
files: ['*.vue', '**/*.vue', '*.html', '**/*.html'],
extends: ['stylelint-config-recommended'],
rules: {
'keyframes-name-pattern': null,
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['deep', 'global'],
},
],
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'],
},
],
},
},
{
files: ['*.less', '**/*.less'],
customSyntax: 'postcss-less',
extends: ['stylelint-config-standard', 'stylelint-config-recommended-vue'],
},
],
};

View File

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

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Base",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"declaration": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"esModuleInterop": true,
"useUnknownInCatchVariables": false,
"composite": false,
"declarationMap": false,
"forceConsistentCasingInFileNames": true,
"inlineSources": false,
"isolatedModules": true,
"skipLibCheck": true,
"noUnusedParameters": false,
"preserveWatchOutput": true,
"experimentalDecorators": true,
"resolveJsonModule": true,
"removeComments": true
},
"exclude": ["**/node_modules/**", "**/dist/**"]
}

View File

@@ -1,4 +1,7 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Node Server Config",
"extends": "./base.json",
"compilerOptions": {
"module": "commonjs",
"declaration": false,

View File

@@ -0,0 +1,12 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Node Config",
"extends": "./base.json",
"compilerOptions": {
"lib": ["ESNext"],
"noImplicitAny": true,
"sourceMap": true,
"noEmit": true,
"baseUrl": "./"
}
}

View File

@@ -0,0 +1,24 @@
{
"name": "@vben/ts-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/ts-config"
},
"license": "MIT",
"files": [
"base.json",
"node.json",
"vue.json",
"node-server.json"
],
"dependencies": {
"@types/node": "^18.15.11"
}
}

View File

@@ -0,0 +1,10 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Vue Library",
"extends": "./base.json",
"compilerOptions": {
"jsx": "preserve",
"lib": ["ESNext", "DOM"],
"noImplicitAny": false
}
}

View File

@@ -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: 自定义',
},
};

View File

@@ -0,0 +1,15 @@
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile

View File

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben'],
};

View File

@@ -0,0 +1,10 @@
dist
.local
.output.js
node_modules
**/*.svg
**/*.sh
public
.npmrc

View File

@@ -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',
},
},
],
};

View File

@@ -0,0 +1,2 @@
dist
public

View File

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben/stylelint-config'],
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
function definePackageConfig() {
// TODO:
}
export { definePackageConfig };

View File

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

View File

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

View File

@@ -5,10 +5,13 @@
import type { PluginOption } from 'vite';
import compressPlugin from 'vite-plugin-compression';
export function configCompressPlugin(
compress: 'gzip' | 'brotli' | 'none',
export function configCompressPlugin({
compress,
deleteOriginFile = false,
): PluginOption | PluginOption[] {
}: {
compress: string;
deleteOriginFile?: boolean;
}): PluginOption[] {
const compressList = compress.split(',');
const plugins: PluginOption[] = [];

View File

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

View File

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

View File

@@ -4,9 +4,9 @@
*/
import { viteMockServe } from 'vite-plugin-mock';
export function configMockPlugin(isBuild: boolean) {
export function configMockPlugin({ isBuild }: { isBuild: boolean }) {
return viteMockServe({
ignore: /^\_/,
ignore: /^_/,
mockPath: 'mock',
localEnabled: !isBuild,
prodEnabled: isBuild,

View File

@@ -0,0 +1,16 @@
/**
* Vite Plugin for fast creating SVG sprites.
* https://github.com/anncwb/vite-plugin-svg-icons
*/
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import { resolve } from 'node:path';
import type { PluginOption } from 'vite';
export function configSvgIconsPlugin({ isBuild }: { isBuild: boolean }) {
const svgIconsPlugin = createSvgIconsPlugin({
iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
svgoOptions: isBuild,
});
return svgIconsPlugin as PluginOption;
}

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,20 @@
import { generateAntColors, primaryColor } from '../config/themeConfig';
import { generate } from '@ant-design/colors';
import { resolve } from 'node:path';
// @ts-ignore
import { getThemeVariables } from 'ant-design-vue/dist/theme';
import { resolve } from 'path';
const primaryColor = '#0960bd';
function generateAntColors(color: string, theme: 'default' | 'dark' = 'default') {
return generate(color, {
theme,
});
}
/**
* less global variable
*/
export function generateModifyVars(dark = false) {
export function generateModifyVars() {
const palettes = generateAntColors(primaryColor);
const primary = palettes[5];
@@ -15,10 +24,9 @@ export function generateModifyVars(dark = false) {
primaryColorObj[`primary-${index + 1}`] = palettes[index];
}
const modifyVars = getThemeVariables({ dark });
const modifyVars = getThemeVariables();
return {
...modifyVars,
// Used for global import to avoid the need to import each style file separately
// reference: Avoid repeated references
hack: `${modifyVars.hack} @import (reference) "${resolve('src/design/config.less')}";`,
'primary-color': primary,
@@ -28,7 +36,6 @@ export function generateModifyVars(dark = false) {
'success-color': '#55D187', // Success color
'error-color': '#ED6F6F', // False color
'warning-color': '#EFBD47', // Warning color
//'border-color-base': '#EEEEEE',
'font-size-base': '14px', // Main font size
'border-radius-base': '2px', // Component/float fillet
'link-color': primary, // Link color

View File

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

Some files were not shown because too many files have changed in this diff Show More