gengx
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
// eslint-disable-next-line n/no-extraneous-import
|
||||
import type { UserConfig } from '@commitlint/types';
|
||||
|
||||
declare const userConfig: UserConfig;
|
||||
|
||||
export default userConfig;
|
||||
@@ -0,0 +1,153 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
import { getPackagesSync } from '@vben/node-utils';
|
||||
|
||||
const { packages } = getPackagesSync();
|
||||
|
||||
const allowedScopes = [
|
||||
...packages.map((pkg) => pkg.packageJson.name),
|
||||
'project',
|
||||
'style',
|
||||
'lint',
|
||||
'ci',
|
||||
'dev',
|
||||
'deploy',
|
||||
'other',
|
||||
];
|
||||
|
||||
// precomputed scope
|
||||
const scopeComplete = execSync('git status --porcelain || true')
|
||||
.toString()
|
||||
.trim()
|
||||
.split('\n')
|
||||
.find((r) => ~r.indexOf('M src'))
|
||||
?.replaceAll(/(\/)/g, '%%')
|
||||
?.match(/src%%((\w|-)*)/)?.[1]
|
||||
?.replace(/s$/, '');
|
||||
|
||||
/**
|
||||
* @type {import('cz-git').UserConfig}
|
||||
*/
|
||||
const userConfig = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
plugins: ['commitlint-plugin-function-rules'],
|
||||
prompt: {
|
||||
/** @use `pnpm commit :f` */
|
||||
alias: {
|
||||
b: 'build: bump dependencies',
|
||||
c: 'chore: update config',
|
||||
f: 'docs: fix typos',
|
||||
r: 'docs: update README',
|
||||
s: 'style: update code format',
|
||||
},
|
||||
allowCustomIssuePrefixs: false,
|
||||
// scopes: [...scopes, 'mock'],
|
||||
allowEmptyIssuePrefixs: false,
|
||||
customScopesAlign: scopeComplete ? 'bottom' : 'top',
|
||||
defaultScope: scopeComplete,
|
||||
// English
|
||||
typesAppend: [
|
||||
{ name: 'workflow: workflow improvements', value: 'workflow' },
|
||||
{ name: 'types: type definition file changes', value: 'types' },
|
||||
],
|
||||
|
||||
// 中英文对照版
|
||||
// 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: 自定义',
|
||||
},
|
||||
rules: {
|
||||
/**
|
||||
* type[scope]: [function] description
|
||||
*
|
||||
* ^^^^^^^^^^^^^^ empty line.
|
||||
* - Something here
|
||||
*/
|
||||
'body-leading-blank': [2, 'always'],
|
||||
/**
|
||||
* type[scope]: [function] description
|
||||
*
|
||||
* - something here
|
||||
*
|
||||
* ^^^^^^^^^^^^^^
|
||||
*/
|
||||
'footer-leading-blank': [1, 'always'],
|
||||
/**
|
||||
* type[scope]: [function] description
|
||||
* ^^^^^
|
||||
*/
|
||||
'function-rules/scope-enum': [
|
||||
2, // level: error
|
||||
'always',
|
||||
(parsed) => {
|
||||
if (!parsed.scope || allowedScopes.includes(parsed.scope)) {
|
||||
return [true];
|
||||
}
|
||||
|
||||
return [false, `scope must be one of ${allowedScopes.join(', ')}`];
|
||||
},
|
||||
],
|
||||
/**
|
||||
* type[scope]: [function] description [No more than 108 characters]
|
||||
* ^^^^^
|
||||
*/
|
||||
'header-max-length': [2, 'always', 108],
|
||||
|
||||
'scope-enum': [0],
|
||||
'subject-case': [0],
|
||||
'subject-empty': [2, 'never'],
|
||||
'type-empty': [2, 'never'],
|
||||
/**
|
||||
* type[scope]: [function] description
|
||||
* ^^^^
|
||||
*/
|
||||
'type-enum': [
|
||||
2,
|
||||
'always',
|
||||
[
|
||||
'feat',
|
||||
'fix',
|
||||
'perf',
|
||||
'style',
|
||||
'docs',
|
||||
'test',
|
||||
'refactor',
|
||||
'build',
|
||||
'ci',
|
||||
'chore',
|
||||
'revert',
|
||||
'types',
|
||||
'release',
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default userConfig;
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@vben/commitlint-config",
|
||||
"version": "5.7.0",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "internal/lint-configs/commitlint-config"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./index.mjs",
|
||||
"module": "./index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.mjs",
|
||||
"default": "./index.mjs"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@commitlint/cli": "catalog:",
|
||||
"@commitlint/config-conventional": "catalog:",
|
||||
"@vben/node-utils": "workspace:*",
|
||||
"commitlint-plugin-function-rules": "catalog:",
|
||||
"cz-git": "catalog:",
|
||||
"czg": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@vben/eslint-config",
|
||||
"version": "5.7.0",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "internal/lint-configs/eslint-config"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"stub": "pnpm exec tsdown"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/js": "catalog:",
|
||||
"@typescript-eslint/parser": "catalog:",
|
||||
"@vben/oxlint-config": "workspace:*",
|
||||
"eslint": "catalog:",
|
||||
"eslint-plugin-jsonc": "catalog:",
|
||||
"eslint-plugin-n": "catalog:",
|
||||
"eslint-plugin-perfectionist": "catalog:",
|
||||
"eslint-plugin-pnpm": "catalog:",
|
||||
"eslint-plugin-unused-imports": "catalog:",
|
||||
"eslint-plugin-vue": "catalog:",
|
||||
"eslint-plugin-yml": "catalog:",
|
||||
"globals": "catalog:",
|
||||
"vue-eslint-parser": "catalog:",
|
||||
"yaml-eslint-parser": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
export async function ignores(): Promise<Linter.Config[]> {
|
||||
return [
|
||||
{
|
||||
ignores: [
|
||||
'**/node_modules',
|
||||
'**/dist',
|
||||
'**/dist-*',
|
||||
'**/*-dist',
|
||||
'**/.husky',
|
||||
'**/.nitro',
|
||||
'**/.output',
|
||||
'**/Dockerfile',
|
||||
'**/package-lock.json',
|
||||
'**/yarn.lock',
|
||||
'**/pnpm-lock.yaml',
|
||||
'**/bun.lockb',
|
||||
'**/output',
|
||||
'**/coverage',
|
||||
'**/temp',
|
||||
'**/.temp',
|
||||
'**/tmp',
|
||||
'**/.tmp',
|
||||
'**/.history',
|
||||
'**/.turbo',
|
||||
'**/.nuxt',
|
||||
'**/.next',
|
||||
'**/.vercel',
|
||||
'**/.changeset',
|
||||
'**/.idea',
|
||||
'**/.cache',
|
||||
'**/.output',
|
||||
'**/.vite-inspect',
|
||||
|
||||
'**/CHANGELOG*.md',
|
||||
'**/*.min.*',
|
||||
'**/LICENSE*',
|
||||
'**/__snapshots__',
|
||||
'**/*.snap',
|
||||
'**/fixtures/**',
|
||||
'**/.vitepress/cache/**',
|
||||
'**/auto-import?(s).d.ts',
|
||||
'**/components.d.ts',
|
||||
'**/vite.config.mts.*',
|
||||
'**/*.sh',
|
||||
'**/*.ttf',
|
||||
'**/*.woff',
|
||||
'**/.github',
|
||||
'**/lefthook.yml',
|
||||
|
||||
'**/.agent/**',
|
||||
'**/.agents/**',
|
||||
'**/.codex/**',
|
||||
'**/.claude/**',
|
||||
'**/.cursor/**',
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export * from './ignores';
|
||||
export * from './javascript';
|
||||
export * from './jsonc';
|
||||
export * from './node';
|
||||
export * from './perfectionist';
|
||||
export * from './pnpm';
|
||||
export * from './typescript';
|
||||
export * from './vue';
|
||||
export * from './yaml';
|
||||
@@ -0,0 +1,185 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
import js from '@eslint/js';
|
||||
import pluginUnusedImports from 'eslint-plugin-unused-imports';
|
||||
import globals from 'globals';
|
||||
|
||||
const rulesCoveredByOxlint = new Set([
|
||||
'constructor-super',
|
||||
'for-direction',
|
||||
'getter-return',
|
||||
'no-async-promise-executor',
|
||||
'no-case-declarations',
|
||||
'no-class-assign',
|
||||
'no-compare-neg-zero',
|
||||
'no-cond-assign',
|
||||
'no-const-assign',
|
||||
'no-constant-binary-expression',
|
||||
'no-constant-condition',
|
||||
'no-control-regex',
|
||||
'no-debugger',
|
||||
'no-delete-var',
|
||||
'no-dupe-args',
|
||||
'no-dupe-class-members',
|
||||
'no-dupe-else-if',
|
||||
'no-dupe-keys',
|
||||
'no-duplicate-case',
|
||||
'no-empty',
|
||||
'no-empty-character-class',
|
||||
'no-empty-pattern',
|
||||
'no-empty-static-block',
|
||||
'no-ex-assign',
|
||||
'no-extra-boolean-cast',
|
||||
'no-fallthrough',
|
||||
'no-func-assign',
|
||||
'no-global-assign',
|
||||
'no-import-assign',
|
||||
'no-invalid-regexp',
|
||||
'no-irregular-whitespace',
|
||||
'no-loss-of-precision',
|
||||
'no-misleading-character-class',
|
||||
'no-new-native-nonconstructor',
|
||||
'no-nonoctal-decimal-escape',
|
||||
'no-obj-calls',
|
||||
'no-prototype-builtins',
|
||||
'no-redeclare',
|
||||
'no-regex-spaces',
|
||||
'no-self-assign',
|
||||
'no-setter-return',
|
||||
'no-shadow-restricted-names',
|
||||
'no-sparse-arrays',
|
||||
'no-this-before-super',
|
||||
'no-unassigned-vars',
|
||||
'no-unexpected-multiline',
|
||||
'no-unreachable',
|
||||
'no-unsafe-finally',
|
||||
'no-unsafe-negation',
|
||||
'no-unsafe-optional-chaining',
|
||||
'no-unused-labels',
|
||||
'no-unused-private-class-members',
|
||||
'no-unused-vars',
|
||||
'no-useless-backreference',
|
||||
'no-useless-catch',
|
||||
'no-useless-escape',
|
||||
'no-with',
|
||||
'preserve-caught-error',
|
||||
'require-yield',
|
||||
'use-isnan',
|
||||
'valid-typeof',
|
||||
]);
|
||||
|
||||
export async function javascript(): Promise<Linter.Config[]> {
|
||||
const recommendedRules = Object.fromEntries(
|
||||
Object.entries(js.configs.recommended.rules).filter(
|
||||
([ruleName]) => !rulesCoveredByOxlint.has(ruleName),
|
||||
),
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.es2021,
|
||||
...globals.node,
|
||||
document: 'readonly',
|
||||
navigator: 'readonly',
|
||||
window: 'readonly',
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
sourceType: 'module',
|
||||
},
|
||||
linterOptions: {
|
||||
reportUnusedDisableDirectives: true,
|
||||
},
|
||||
plugins: {
|
||||
'unused-imports': pluginUnusedImports,
|
||||
},
|
||||
rules: {
|
||||
...recommendedRules,
|
||||
'dot-notation': ['error', { allowKeywords: true }],
|
||||
'keyword-spacing': 'off',
|
||||
'no-empty-function': 'off',
|
||||
'no-octal': 'error',
|
||||
'no-octal-escape': 'error',
|
||||
'no-restricted-properties': [
|
||||
'error',
|
||||
{
|
||||
message:
|
||||
'Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.',
|
||||
property: '__proto__',
|
||||
},
|
||||
{
|
||||
message: 'Use `Object.defineProperty` instead.',
|
||||
property: '__defineGetter__',
|
||||
},
|
||||
{
|
||||
message: 'Use `Object.defineProperty` instead.',
|
||||
property: '__defineSetter__',
|
||||
},
|
||||
{
|
||||
message: 'Use `Object.getOwnPropertyDescriptor` instead.',
|
||||
property: '__lookupGetter__',
|
||||
},
|
||||
{
|
||||
message: 'Use `Object.getOwnPropertyDescriptor` instead.',
|
||||
property: '__lookupSetter__',
|
||||
},
|
||||
],
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
'DebuggerStatement',
|
||||
'LabeledStatement',
|
||||
'WithStatement',
|
||||
'TSEnumDeclaration[const=true]',
|
||||
'TSExportAssignment',
|
||||
],
|
||||
'no-undef-init': 'error',
|
||||
'no-undef': 'off',
|
||||
'no-unreachable-loop': 'error',
|
||||
'object-shorthand': [
|
||||
'error',
|
||||
'always',
|
||||
{
|
||||
avoidQuotes: true,
|
||||
ignoreConstructors: false,
|
||||
},
|
||||
],
|
||||
'one-var': ['error', { initialized: 'never' }],
|
||||
'prefer-arrow-callback': [
|
||||
'error',
|
||||
{
|
||||
allowNamedFunctions: false,
|
||||
allowUnboundThis: true,
|
||||
},
|
||||
],
|
||||
'prefer-regex-literals': [
|
||||
'error',
|
||||
{
|
||||
disallowRedundantWrapping: true,
|
||||
},
|
||||
],
|
||||
'spaced-comment': 'error',
|
||||
'space-before-function-paren': 'off',
|
||||
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'unused-imports/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
vars: 'all',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
import { interopDefault } from '../util';
|
||||
|
||||
export async function jsonc(): Promise<Linter.Config[]> {
|
||||
const pluginJsonc = await interopDefault(import('eslint-plugin-jsonc'));
|
||||
|
||||
return [
|
||||
{
|
||||
files: ['**/*.json', '**/*.json5', '**/*.jsonc', '*.code-workspace'],
|
||||
language: 'jsonc/x',
|
||||
plugins: {
|
||||
jsonc: pluginJsonc as any,
|
||||
},
|
||||
rules: {
|
||||
'jsonc/no-bigint-literals': 'error',
|
||||
'jsonc/no-binary-expression': 'error',
|
||||
'jsonc/no-binary-numeric-literals': 'error',
|
||||
'jsonc/no-dupe-keys': 'error',
|
||||
'jsonc/no-escape-sequence-in-identifier': 'error',
|
||||
'jsonc/no-floating-decimal': 'error',
|
||||
'jsonc/no-hexadecimal-numeric-literals': 'error',
|
||||
'jsonc/no-infinity': 'error',
|
||||
'jsonc/no-multi-str': 'error',
|
||||
'jsonc/no-nan': 'error',
|
||||
'jsonc/no-number-props': 'error',
|
||||
'jsonc/no-numeric-separators': 'error',
|
||||
'jsonc/no-octal': 'error',
|
||||
'jsonc/no-octal-escape': 'error',
|
||||
'jsonc/no-octal-numeric-literals': 'error',
|
||||
'jsonc/no-parenthesized': 'error',
|
||||
'jsonc/no-plus-sign': 'error',
|
||||
'jsonc/no-regexp-literals': 'error',
|
||||
'jsonc/no-sparse-arrays': 'error',
|
||||
'jsonc/no-template-literals': 'error',
|
||||
'jsonc/no-undefined-value': 'error',
|
||||
'jsonc/no-unicode-codepoint-escapes': 'error',
|
||||
'jsonc/no-useless-escape': 'error',
|
||||
'jsonc/space-unary-ops': 'error',
|
||||
'jsonc/valid-json-number': 'error',
|
||||
'jsonc/vue-custom-block/no-parsing-error': 'error',
|
||||
},
|
||||
},
|
||||
sortTsconfig(),
|
||||
sortPackageJson(),
|
||||
sortCspellJson(),
|
||||
];
|
||||
}
|
||||
|
||||
function sortPackageJson(): Linter.Config {
|
||||
return {
|
||||
files: ['**/package.json'],
|
||||
rules: {
|
||||
'jsonc/sort-array-values': [
|
||||
'error',
|
||||
{
|
||||
order: { type: 'asc' },
|
||||
pathPattern: '^files$|^pnpm.neverBuiltDependencies$',
|
||||
},
|
||||
],
|
||||
'jsonc/sort-keys': [
|
||||
'error',
|
||||
{
|
||||
order: [
|
||||
'name',
|
||||
'version',
|
||||
'description',
|
||||
'private',
|
||||
'keywords',
|
||||
'homepage',
|
||||
'bugs',
|
||||
'repository',
|
||||
'license',
|
||||
'author',
|
||||
'contributors',
|
||||
'categories',
|
||||
'funding',
|
||||
'type',
|
||||
'scripts',
|
||||
'files',
|
||||
'sideEffects',
|
||||
'bin',
|
||||
'main',
|
||||
'module',
|
||||
'unpkg',
|
||||
'jsdelivr',
|
||||
'types',
|
||||
'typesVersions',
|
||||
'imports',
|
||||
'exports',
|
||||
'publishConfig',
|
||||
'icon',
|
||||
'activationEvents',
|
||||
'contributes',
|
||||
'peerDependencies',
|
||||
'peerDependenciesMeta',
|
||||
'dependencies',
|
||||
'optionalDependencies',
|
||||
'devDependencies',
|
||||
'engines',
|
||||
'packageManager',
|
||||
'pnpm',
|
||||
'overrides',
|
||||
'resolutions',
|
||||
'husky',
|
||||
'simple-git-hooks',
|
||||
'lint-staged',
|
||||
'eslintConfig',
|
||||
],
|
||||
pathPattern: '^$',
|
||||
},
|
||||
{
|
||||
order: { type: 'asc' },
|
||||
pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$',
|
||||
},
|
||||
{
|
||||
order: { type: 'asc' },
|
||||
pathPattern: '^(?:resolutions|overrides|pnpm.overrides)$',
|
||||
},
|
||||
{
|
||||
order: ['types', 'import', 'require', 'default'],
|
||||
pathPattern: '^exports.*$',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function sortCspellJson(): Linter.Config {
|
||||
return {
|
||||
files: ['**/cspell.json', '**/.cspell.json'],
|
||||
rules: {
|
||||
'jsonc/sort-array-values': [
|
||||
'error',
|
||||
{
|
||||
order: { type: 'asc' },
|
||||
pathPattern: '^words$|^ignorePaths$',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function sortTsconfig(): Linter.Config {
|
||||
return {
|
||||
files: [
|
||||
'**/tsconfig.json',
|
||||
'**/tsconfig.*.json',
|
||||
'internal/tsconfig/*.json',
|
||||
],
|
||||
rules: {
|
||||
'jsonc/sort-keys': [
|
||||
'error',
|
||||
{
|
||||
order: [
|
||||
'extends',
|
||||
'compilerOptions',
|
||||
'references',
|
||||
'files',
|
||||
'include',
|
||||
'exclude',
|
||||
],
|
||||
pathPattern: '^$',
|
||||
},
|
||||
{
|
||||
order: [
|
||||
/* Projects */
|
||||
'incremental',
|
||||
'composite',
|
||||
'tsBuildInfoFile',
|
||||
'disableSourceOfProjectReferenceRedirect',
|
||||
'disableSolutionSearching',
|
||||
'disableReferencedProjectLoad',
|
||||
/* Language and Environment */
|
||||
'target',
|
||||
'jsx',
|
||||
'jsxFactory',
|
||||
'jsxFragmentFactory',
|
||||
'jsxImportSource',
|
||||
'lib',
|
||||
'moduleDetection',
|
||||
'noLib',
|
||||
'reactNamespace',
|
||||
'useDefineForClassFields',
|
||||
'emitDecoratorMetadata',
|
||||
'experimentalDecorators',
|
||||
/* Modules */
|
||||
'baseUrl',
|
||||
'rootDir',
|
||||
'rootDirs',
|
||||
'customConditions',
|
||||
'module',
|
||||
'moduleResolution',
|
||||
'moduleSuffixes',
|
||||
'noResolve',
|
||||
'paths',
|
||||
'resolveJsonModule',
|
||||
'resolvePackageJsonExports',
|
||||
'resolvePackageJsonImports',
|
||||
'typeRoots',
|
||||
'types',
|
||||
'allowArbitraryExtensions',
|
||||
'allowImportingTsExtensions',
|
||||
'allowUmdGlobalAccess',
|
||||
/* JavaScript Support */
|
||||
'allowJs',
|
||||
'checkJs',
|
||||
'maxNodeModuleJsDepth',
|
||||
/* Type Checking */
|
||||
'strict',
|
||||
'strictBindCallApply',
|
||||
'strictFunctionTypes',
|
||||
'strictNullChecks',
|
||||
'strictPropertyInitialization',
|
||||
'allowUnreachableCode',
|
||||
'allowUnusedLabels',
|
||||
'alwaysStrict',
|
||||
'exactOptionalPropertyTypes',
|
||||
'noFallthroughCasesInSwitch',
|
||||
'noImplicitAny',
|
||||
'noImplicitOverride',
|
||||
'noImplicitReturns',
|
||||
'noImplicitThis',
|
||||
'noPropertyAccessFromIndexSignature',
|
||||
'noUncheckedIndexedAccess',
|
||||
'noUnusedLocals',
|
||||
'noUnusedParameters',
|
||||
'useUnknownInCatchVariables',
|
||||
/* Emit */
|
||||
'declaration',
|
||||
'declarationDir',
|
||||
'declarationMap',
|
||||
'downlevelIteration',
|
||||
'emitBOM',
|
||||
'emitDeclarationOnly',
|
||||
'importHelpers',
|
||||
'importsNotUsedAsValues',
|
||||
'inlineSourceMap',
|
||||
'inlineSources',
|
||||
'mapRoot',
|
||||
'newLine',
|
||||
'noEmit',
|
||||
'noEmitHelpers',
|
||||
'noEmitOnError',
|
||||
'outDir',
|
||||
'outFile',
|
||||
'preserveConstEnums',
|
||||
'preserveValueImports',
|
||||
'removeComments',
|
||||
'sourceMap',
|
||||
'sourceRoot',
|
||||
'stripInternal',
|
||||
/* Interop Constraints */
|
||||
'allowSyntheticDefaultImports',
|
||||
'esModuleInterop',
|
||||
'forceConsistentCasingInFileNames',
|
||||
'isolatedModules',
|
||||
'preserveSymlinks',
|
||||
'verbatimModuleSyntax',
|
||||
/* Completeness */
|
||||
'skipDefaultLibCheck',
|
||||
'skipLibCheck',
|
||||
],
|
||||
pathPattern: '^compilerOptions$',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
import { interopDefault } from '../util';
|
||||
|
||||
export async function node(): Promise<Linter.Config[]> {
|
||||
const pluginNode = await interopDefault(import('eslint-plugin-n'));
|
||||
|
||||
return [
|
||||
{
|
||||
plugins: {
|
||||
n: pluginNode,
|
||||
},
|
||||
rules: {
|
||||
'n/handle-callback-err': ['error', '^(err|error)$'],
|
||||
'n/no-deprecated-api': 'error',
|
||||
'n/no-extraneous-import': [
|
||||
'error',
|
||||
{
|
||||
allowModules: [
|
||||
'tsdown',
|
||||
'unplugin-vue',
|
||||
'@vben/vite-config',
|
||||
'vitest',
|
||||
'vite',
|
||||
'@vue/test-utils',
|
||||
'@playwright/test',
|
||||
],
|
||||
},
|
||||
],
|
||||
// 'n/no-unpublished-import': 'off',
|
||||
'n/no-unsupported-features/es-syntax': [
|
||||
'error',
|
||||
{
|
||||
ignores: [],
|
||||
version: '>=22.18.0',
|
||||
},
|
||||
],
|
||||
'n/prefer-global/buffer': ['error', 'never'],
|
||||
// 'n/no-missing-import': 'off',
|
||||
'n/prefer-global/process': ['error', 'never'],
|
||||
'n/process-exit-as-throw': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/**/*.?([cm])[jt]s?(x)',
|
||||
'**/*.spec.?([cm])[jt]s?(x)',
|
||||
'**/*.test.?([cm])[jt]s?(x)',
|
||||
'**/*.bench.?([cm])[jt]s?(x)',
|
||||
'**/*.benchmark.?([cm])[jt]s?(x)',
|
||||
],
|
||||
rules: {
|
||||
'n/prefer-global/process': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['apps/backend-mock/**/**', 'docs/**/**'],
|
||||
rules: {
|
||||
'n/no-extraneous-import': 'off',
|
||||
'n/prefer-global/buffer': 'off',
|
||||
'n/prefer-global/process': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/**/playwright.config.ts'],
|
||||
rules: {
|
||||
'n/prefer-global/buffer': 'off',
|
||||
'n/prefer-global/process': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'scripts/**/*.?([cm])[jt]s?(x)',
|
||||
'internal/**/*.?([cm])[jt]s?(x)',
|
||||
],
|
||||
rules: {
|
||||
'n/prefer-global/process': 'off',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
import { interopDefault } from '../util';
|
||||
|
||||
export async function perfectionist(): Promise<Linter.Config[]> {
|
||||
const perfectionistPlugin = await interopDefault(
|
||||
import('eslint-plugin-perfectionist'),
|
||||
);
|
||||
|
||||
return [
|
||||
perfectionistPlugin.configs['recommended-natural'],
|
||||
{
|
||||
rules: {
|
||||
'perfectionist/sort-exports': [
|
||||
'error',
|
||||
{
|
||||
order: 'asc',
|
||||
type: 'natural',
|
||||
},
|
||||
],
|
||||
'perfectionist/sort-imports': [
|
||||
'error',
|
||||
{
|
||||
customGroups: [
|
||||
{
|
||||
selector: 'type',
|
||||
groupName: 'vben-core-type',
|
||||
elementNamePattern: '^@vben-core/.+',
|
||||
},
|
||||
{
|
||||
selector: 'type',
|
||||
groupName: 'vben-type',
|
||||
elementNamePattern: '^@vben/.+',
|
||||
},
|
||||
{
|
||||
selector: 'type',
|
||||
groupName: 'vue-type',
|
||||
elementNamePattern: ['^vue$', '^vue-.+', '^@vue/.+'],
|
||||
},
|
||||
{
|
||||
groupName: 'vben',
|
||||
elementNamePattern: '^@vben/.+',
|
||||
},
|
||||
{
|
||||
groupName: 'vben-core',
|
||||
elementNamePattern: '^@vben-core/.+',
|
||||
},
|
||||
{
|
||||
groupName: 'vue',
|
||||
elementNamePattern: ['^vue$', '^vue-.+', '^@vue/.+'],
|
||||
},
|
||||
],
|
||||
environment: 'node',
|
||||
groups: [
|
||||
['type-external', 'type-builtin', 'type-import'],
|
||||
'vue-type',
|
||||
'vben-type',
|
||||
'vben-core-type',
|
||||
['type-parent', 'type-sibling', 'type-index'],
|
||||
['type-internal'],
|
||||
'value-builtin',
|
||||
'vue',
|
||||
'vben',
|
||||
'vben-core',
|
||||
'value-external',
|
||||
'value-internal',
|
||||
['value-parent', 'value-sibling', 'value-index'],
|
||||
'side-effect',
|
||||
'side-effect-style',
|
||||
'style',
|
||||
'ts-equals-import',
|
||||
'unknown',
|
||||
],
|
||||
internalPattern: ['^#/.+'],
|
||||
newlinesBetween: 1,
|
||||
order: 'asc',
|
||||
type: 'natural',
|
||||
},
|
||||
],
|
||||
'perfectionist/sort-modules': 'off',
|
||||
'perfectionist/sort-named-exports': [
|
||||
'error',
|
||||
{
|
||||
order: 'asc',
|
||||
type: 'natural',
|
||||
},
|
||||
],
|
||||
'perfectionist/sort-objects': [
|
||||
'off',
|
||||
{
|
||||
customGroups: {
|
||||
items: 'items',
|
||||
list: 'list',
|
||||
children: 'children',
|
||||
},
|
||||
groups: ['unknown', 'items', 'list', 'children'],
|
||||
ignorePattern: ['children'],
|
||||
order: 'asc',
|
||||
type: 'natural',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
import { interopDefault } from '../util';
|
||||
|
||||
export async function pnpm(): Promise<Linter.Config[]> {
|
||||
const [pluginPnpm, parserPnpm] = await Promise.all([
|
||||
interopDefault(import('eslint-plugin-pnpm')),
|
||||
interopDefault(import('yaml-eslint-parser')),
|
||||
] as const);
|
||||
|
||||
return [
|
||||
{
|
||||
files: ['package.json', '**/package.json'],
|
||||
language: 'jsonc/x',
|
||||
plugins: {
|
||||
pnpm: pluginPnpm,
|
||||
},
|
||||
rules: {
|
||||
'pnpm/json-enforce-catalog': 'error',
|
||||
'pnpm/json-prefer-workspace-settings': 'error',
|
||||
'pnpm/json-valid-catalog': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['pnpm-workspace.yaml'],
|
||||
languageOptions: {
|
||||
parser: parserPnpm,
|
||||
},
|
||||
plugins: {
|
||||
pnpm: pluginPnpm,
|
||||
},
|
||||
rules: {
|
||||
'pnpm/yaml-no-duplicate-catalog-item': 'error',
|
||||
'pnpm/yaml-no-unused-catalog-item': 'error',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
import { interopDefault } from '../util';
|
||||
|
||||
/**
|
||||
* @typescript-eslint 的规则已迁移到 oxlint(typescript 插件)。
|
||||
* 这里仅保留 TS 解析器,供其它 eslint 插件(perfectionist、n 等)解析 TS/TSX 文件。
|
||||
* 因不再有类型感知规则,已移除 parserOptions.project,eslint 解析更快。
|
||||
*
|
||||
* 注意:移除 @typescript-eslint 插件后,unused-imports/no-unused-vars 会退回核心实现,
|
||||
* 无法识别 TS 类型签名里的形参(会误报)。故对 TS/TSX/Vue 统一关闭该规则,
|
||||
* 未使用变量改由 oxlint 的 no-unused-vars(类型感知)负责。
|
||||
*/
|
||||
export async function typescript(): Promise<Linter.Config[]> {
|
||||
const parserTs = await interopDefault(import('@typescript-eslint/parser'));
|
||||
|
||||
return [
|
||||
{
|
||||
files: ['**/*.?([cm])[jt]s?(x)'],
|
||||
languageOptions: {
|
||||
parser: parserTs,
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
ecmaVersion: 'latest',
|
||||
extraFileExtensions: ['.vue'],
|
||||
jsxPragma: 'React',
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'unused-imports/no-unused-vars': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Vue `<script>` 的未使用变量同样交给 oxlint,避免核心规则误报 TS 类型签名形参
|
||||
files: ['**/*.vue'],
|
||||
rules: {
|
||||
'unused-imports/no-unused-vars': 'off',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
import { interopDefault } from '../util';
|
||||
|
||||
export async function vue(): Promise<Linter.Config[]> {
|
||||
const [pluginVue, parserVue, parserTs] = await Promise.all([
|
||||
interopDefault(import('eslint-plugin-vue')),
|
||||
interopDefault(import('vue-eslint-parser')),
|
||||
interopDefault(import('@typescript-eslint/parser')),
|
||||
] as const);
|
||||
|
||||
const flatEssential = pluginVue.configs?.['flat/essential'] || [];
|
||||
const flatStronglyRecommended =
|
||||
pluginVue.configs?.['flat/strongly-recommended'] || [];
|
||||
const flatRecommended = pluginVue.configs?.['flat/recommended'] || [];
|
||||
|
||||
return [
|
||||
...flatEssential,
|
||||
...flatStronglyRecommended,
|
||||
...flatRecommended,
|
||||
{
|
||||
files: ['**/*.vue'],
|
||||
languageOptions: {
|
||||
// globals: {
|
||||
// computed: 'readonly',
|
||||
// defineEmits: 'readonly',
|
||||
// defineExpose: 'readonly',
|
||||
// defineProps: 'readonly',
|
||||
// onMounted: 'readonly',
|
||||
// onUnmounted: 'readonly',
|
||||
// reactive: 'readonly',
|
||||
// ref: 'readonly',
|
||||
// shallowReactive: 'readonly',
|
||||
// shallowRef: 'readonly',
|
||||
// toRef: 'readonly',
|
||||
// toRefs: 'readonly',
|
||||
// watch: 'readonly',
|
||||
// watchEffect: 'readonly',
|
||||
// },
|
||||
parser: parserVue,
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
extraFileExtensions: ['.vue'],
|
||||
parser: parserTs,
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
vue: pluginVue,
|
||||
},
|
||||
processor: pluginVue.processors?.['.vue'],
|
||||
rules: {
|
||||
...pluginVue.configs?.base?.rules,
|
||||
|
||||
'vue/attribute-hyphenation': [
|
||||
'error',
|
||||
'always',
|
||||
{
|
||||
ignore: [],
|
||||
},
|
||||
],
|
||||
'vue/attributes-order': 'off',
|
||||
'vue/block-order': [
|
||||
'error',
|
||||
{
|
||||
order: ['script', 'template', 'style'],
|
||||
},
|
||||
],
|
||||
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
|
||||
'vue/component-options-name-casing': ['error', 'PascalCase'],
|
||||
'vue/custom-event-name-casing': ['error', 'camelCase'],
|
||||
'vue/define-macros-order': [
|
||||
'error',
|
||||
{
|
||||
order: [
|
||||
'defineOptions',
|
||||
'defineProps',
|
||||
'defineEmits',
|
||||
'defineSlots',
|
||||
],
|
||||
},
|
||||
],
|
||||
'vue/dot-location': ['error', 'property'],
|
||||
'vue/dot-notation': ['error', { allowKeywords: true }],
|
||||
'vue/eqeqeq': ['error', 'smart'],
|
||||
'vue/html-closing-bracket-newline': 'error',
|
||||
'vue/html-indent': 'off',
|
||||
// 'vue/html-indent': ['error', 2],
|
||||
'vue/html-quotes': ['error', 'double'],
|
||||
'vue/html-self-closing': [
|
||||
'error',
|
||||
{
|
||||
html: {
|
||||
component: 'always',
|
||||
normal: 'never',
|
||||
void: 'always',
|
||||
},
|
||||
math: 'always',
|
||||
svg: 'always',
|
||||
},
|
||||
],
|
||||
'vue/max-attributes-per-line': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/multiline-html-element-content-newline': 'error',
|
||||
'vue/no-empty-pattern': 'error',
|
||||
'vue/no-extra-parens': ['error', 'functions'],
|
||||
'vue/no-irregular-whitespace': 'error',
|
||||
'vue/no-loss-of-precision': 'error',
|
||||
'vue/no-reserved-component-names': 'off',
|
||||
'vue/no-restricted-syntax': [
|
||||
'error',
|
||||
'DebuggerStatement',
|
||||
'LabeledStatement',
|
||||
'WithStatement',
|
||||
],
|
||||
'vue/no-restricted-v-bind': ['error', '/^v-/'],
|
||||
'vue/no-sparse-arrays': 'error',
|
||||
'vue/no-unused-refs': 'error',
|
||||
'vue/no-useless-v-bind': 'error',
|
||||
'vue/object-shorthand': [
|
||||
'error',
|
||||
'always',
|
||||
{
|
||||
avoidQuotes: true,
|
||||
ignoreConstructors: false,
|
||||
},
|
||||
],
|
||||
'vue/one-component-per-file': 'error',
|
||||
'vue/prefer-separate-static-class': 'error',
|
||||
'vue/prefer-template': 'error',
|
||||
'vue/prop-name-casing': ['error', 'camelCase'],
|
||||
'vue/require-default-prop': 'error',
|
||||
'vue/require-explicit-emits': 'error',
|
||||
'vue/require-prop-types': 'off',
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'vue/space-infix-ops': 'error',
|
||||
'vue/space-unary-ops': ['error', { nonwords: false, words: true }],
|
||||
'vue/v-on-event-hyphenation': [
|
||||
'error',
|
||||
'always',
|
||||
{
|
||||
autofix: true,
|
||||
ignore: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
import { interopDefault } from '../util';
|
||||
|
||||
export async function yaml(): Promise<Linter.Config[]> {
|
||||
const [pluginYaml, parserYaml] = await Promise.all([
|
||||
interopDefault(import('eslint-plugin-yml')),
|
||||
interopDefault(import('yaml-eslint-parser')),
|
||||
] as const);
|
||||
|
||||
return [
|
||||
{
|
||||
files: ['**/*.y?(a)ml'],
|
||||
plugins: {
|
||||
yaml: pluginYaml,
|
||||
},
|
||||
languageOptions: {
|
||||
parser: parserYaml,
|
||||
},
|
||||
rules: {
|
||||
'style/spaced-comment': 'off',
|
||||
|
||||
'yaml/block-mapping': 'error',
|
||||
'yaml/block-sequence': 'error',
|
||||
'yaml/no-empty-key': 'error',
|
||||
'yaml/no-empty-sequence-entry': 'error',
|
||||
'yaml/no-irregular-whitespace': 'error',
|
||||
'yaml/plain-scalar': 'error',
|
||||
|
||||
'yaml/vue-custom-block/no-parsing-error': 'error',
|
||||
|
||||
'yaml/block-mapping-question-indicator-newline': 'error',
|
||||
'yaml/block-sequence-hyphen-indicator-newline': 'error',
|
||||
'yaml/flow-mapping-curly-newline': 'error',
|
||||
'yaml/flow-mapping-curly-spacing': 'error',
|
||||
'yaml/flow-sequence-bracket-newline': 'error',
|
||||
'yaml/flow-sequence-bracket-spacing': 'error',
|
||||
'yaml/indent': ['error', 2],
|
||||
'yaml/key-spacing': 'error',
|
||||
'yaml/no-tab-indent': 'error',
|
||||
'yaml/quotes': [
|
||||
'error',
|
||||
{
|
||||
avoidEscape: true,
|
||||
prefer: 'single',
|
||||
},
|
||||
],
|
||||
'yaml/spaced-comment': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['pnpm-workspace.yaml'],
|
||||
rules: {
|
||||
'yaml/sort-keys': [
|
||||
'error',
|
||||
{
|
||||
order: [
|
||||
'packages',
|
||||
'publicHoistPattern',
|
||||
'strictPeerDependencies',
|
||||
'autoInstallPeers',
|
||||
'dedupePeerDependents',
|
||||
'verifyDepsBeforeRun',
|
||||
'overrides',
|
||||
'patchedDependencies',
|
||||
'hoistPattern',
|
||||
'catalog',
|
||||
'catalogs',
|
||||
|
||||
'allowedDeprecatedVersions',
|
||||
'allowBuilds',
|
||||
'allowNonAppliedPatches',
|
||||
'configDependencies',
|
||||
'ignoredBuiltDependencies',
|
||||
'ignoredOptionalDependencies',
|
||||
'neverBuiltDependencies',
|
||||
'onlyBuiltDependencies',
|
||||
'onlyBuiltDependenciesFile',
|
||||
'packageExtensions',
|
||||
'peerDependencyRules',
|
||||
'supportedArchitectures',
|
||||
],
|
||||
pathPattern: '^$',
|
||||
},
|
||||
{
|
||||
order: { type: 'asc' },
|
||||
pathPattern: '^.+$',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
const restrictedImportIgnores = ['**/vite.config.mts'];
|
||||
|
||||
const customConfig: Linter.Config[] = [
|
||||
// shadcn-ui 内部组件是自动生成的,不做太多限制
|
||||
{
|
||||
files: ['packages/@core/ui-kit/shadcn-ui/**/**'],
|
||||
rules: {
|
||||
'vue/require-default-prop': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'apps/**/**',
|
||||
'packages/effects/**/**',
|
||||
'packages/utils/**/**',
|
||||
'packages/types/**/**',
|
||||
'packages/locales/**/**',
|
||||
],
|
||||
ignores: restrictedImportIgnores,
|
||||
rules: {
|
||||
'perfectionist/sort-interfaces': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// apps内部的一些基础规则
|
||||
files: ['apps/**/**'],
|
||||
ignores: restrictedImportIgnores,
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: ['#/api/*'],
|
||||
message:
|
||||
'The #/api package cannot be imported, please use the @core package itself',
|
||||
},
|
||||
{
|
||||
group: ['#/layouts/*'],
|
||||
message:
|
||||
'The #/layouts package cannot be imported, please use the @core package itself',
|
||||
},
|
||||
{
|
||||
group: ['#/locales/*'],
|
||||
message:
|
||||
'The #/locales package cannot be imported, please use the @core package itself',
|
||||
},
|
||||
{
|
||||
group: ['#/stores/*'],
|
||||
message:
|
||||
'The #/stores package cannot be imported, please use the @core package itself',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
// @core内部组件,不能引入@vben/* 里面的包
|
||||
files: ['packages/@core/**/**'],
|
||||
ignores: restrictedImportIgnores,
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: ['@vben/*'],
|
||||
message:
|
||||
'The @core package cannot import the @vben package, please use the @core package itself',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
// @core/shared内部组件,不能引入@vben/* 或者 @vben-core/* 里面的包
|
||||
files: ['packages/@core/base/**/**'],
|
||||
ignores: restrictedImportIgnores,
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: ['@vben/*', '@vben-core/*'],
|
||||
message:
|
||||
'The @vben-core/shared package cannot import the @vben package, please use the @core/shared package itself',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
// 不能引入@vben/*里面的包
|
||||
files: [
|
||||
'packages/types/**/**',
|
||||
'packages/utils/**/**',
|
||||
'packages/icons/**/**',
|
||||
'packages/constants/**/**',
|
||||
'packages/styles/**/**',
|
||||
'packages/stores/**/**',
|
||||
'packages/preferences/**/**',
|
||||
'packages/locales/**/**',
|
||||
],
|
||||
ignores: restrictedImportIgnores,
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: ['@vben/*'],
|
||||
message:
|
||||
'The @vben package cannot be imported, please use the @core package itself',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// 后端模拟代码,不需要太多规则
|
||||
{
|
||||
files: ['apps/backend-mock/**/**', 'docs/**/**'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/**/playwright.config.ts'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['internal/**/**', 'scripts/**/**'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/@core/base/shared/src/utils/inference.ts'],
|
||||
rules: {
|
||||
'vue/prefer-import-from-vue': 'off',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export { customConfig };
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
import {
|
||||
ignores,
|
||||
javascript,
|
||||
jsonc,
|
||||
node,
|
||||
perfectionist,
|
||||
pnpm,
|
||||
typescript,
|
||||
vue,
|
||||
yaml,
|
||||
} from './configs';
|
||||
import { customConfig } from './custom-config';
|
||||
|
||||
type FlatConfig = Linter.Config;
|
||||
|
||||
type FlatConfigPromise =
|
||||
| FlatConfig
|
||||
| FlatConfig[]
|
||||
| Promise<FlatConfig>
|
||||
| Promise<FlatConfig[]>;
|
||||
|
||||
async function defineConfig(config: FlatConfig[] = []) {
|
||||
const configs: FlatConfigPromise[] = [
|
||||
vue(),
|
||||
javascript(),
|
||||
ignores(),
|
||||
typescript(),
|
||||
jsonc(),
|
||||
node(),
|
||||
perfectionist(),
|
||||
yaml(),
|
||||
pnpm(),
|
||||
...customConfig,
|
||||
...config,
|
||||
];
|
||||
|
||||
const resolved = await Promise.all(configs);
|
||||
|
||||
return resolved.flat();
|
||||
}
|
||||
|
||||
export { defineConfig };
|
||||
@@ -0,0 +1,8 @@
|
||||
export type Awaitable<T> = Promise<T> | T;
|
||||
|
||||
export async function interopDefault<T>(
|
||||
m: Awaitable<T>,
|
||||
): Promise<T extends { default: infer U } ? U : T> {
|
||||
const resolved = await m;
|
||||
return (resolved as any).default || resolved;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/node.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from 'tsdown';
|
||||
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
deps: {
|
||||
skipNodeModulesBundle: true,
|
||||
},
|
||||
dts: {
|
||||
resolver: 'tsc',
|
||||
},
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
outExtensions: () => ({
|
||||
dts: '.d.ts',
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "@vben/oxfmt-config",
|
||||
"version": "5.7.0",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "internal/lint-configs/oxfmt-config"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"stub": "pnpm exec tsdown"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"oxfmt": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import { defineConfig as defineOxfmtConfig } from 'oxfmt';
|
||||
|
||||
type OxfmtConfig = Parameters<typeof defineOxfmtConfig>[0];
|
||||
|
||||
/**
|
||||
* oxfmt 配置文件,详见下方链接
|
||||
* https://oxc.rs/docs/guide/usage/formatter/config-file-reference.html
|
||||
*/
|
||||
const oxfmtConfig: OxfmtConfig = defineOxfmtConfig({
|
||||
/**
|
||||
* 单行长度 oxfmt,适配 prettier 的 80
|
||||
* Default:100
|
||||
*/
|
||||
printWidth: 80,
|
||||
/**
|
||||
* 缩进宽度
|
||||
* Default:2
|
||||
*/
|
||||
tabWidth: 2,
|
||||
/**
|
||||
* Markdown、MDX、YAML 文件格式化包裹
|
||||
* type: always | never | preserve
|
||||
* Default: preserve
|
||||
*/
|
||||
proseWrap: 'never',
|
||||
/**
|
||||
* 结尾添加分号
|
||||
* Default:true
|
||||
*/
|
||||
semi: true,
|
||||
/**
|
||||
* 使用单引号
|
||||
* Default:false
|
||||
*/
|
||||
singleQuote: true,
|
||||
/**
|
||||
* 对象属性添加引号
|
||||
* Default:as-needed
|
||||
*/
|
||||
quoteProps: 'as-needed',
|
||||
/**
|
||||
* 将多行元素的 > 放在最后一行的末尾,而不是单独放在下一行
|
||||
* Default:false
|
||||
*/
|
||||
bracketSameLine: false,
|
||||
/**
|
||||
* 对象字面量的大括号间添加空格
|
||||
* Default:true
|
||||
*/
|
||||
bracketSpacing: true,
|
||||
/**
|
||||
* 箭头函数参数总是使用括号
|
||||
* type: always | avoid
|
||||
* Default:always
|
||||
*/
|
||||
arrowParens: 'always',
|
||||
/**
|
||||
* 配置 package.json 排序,但是 oxfmt 不支持 pnpm-workspace
|
||||
* 现使用 eslint 搭配 eslint-plugin-pnpm eslint-plugin-yml 支持 package.json 和 pnpm-workspace.yaml 但排序风格不太一致
|
||||
* Default:true
|
||||
*/
|
||||
sortPackageJson: false,
|
||||
/**
|
||||
* 配置 import 排序,现在 使用 eslint-plugin-perfectionist,但是 oxfmt 不支持 export 等
|
||||
* 并且 customGroups 不支持 ts-equals-import
|
||||
* Default:false
|
||||
*/
|
||||
sortImports: false,
|
||||
/**
|
||||
* 多行结构中的后置逗号
|
||||
* Default:all
|
||||
*/
|
||||
trailingComma: 'all',
|
||||
/**
|
||||
* 行尾换行符
|
||||
* type: lf | crlf | cr
|
||||
* Default: lf
|
||||
*/
|
||||
endOfLine: 'lf',
|
||||
/**
|
||||
* 在文件最后插入一个换行
|
||||
* Default:true
|
||||
*/
|
||||
insertFinalNewline: true,
|
||||
/**
|
||||
* 控制格式化文件中例如,CSS-in-JS 或 JS-in-Vue 等
|
||||
* Default:auto
|
||||
*/
|
||||
embeddedLanguageFormatting: 'auto',
|
||||
/**
|
||||
* Vue/HTML/Angular/Handlebars 的空白敏感度(oxfmt 现会格式化 <template>)
|
||||
* type: css | strict | ignore
|
||||
* Default:css
|
||||
*/
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
/**
|
||||
* 暂时关闭,改动较多,后续可以考虑开启,支持 vue 但与现有的 eslint-plugin-better-tailwindcss 格式化会冲突
|
||||
* eslint-plugin-better-tailwindcss配置在 oxlint,在 vue文 件暂时不生效,ts等正常
|
||||
* Default:关闭
|
||||
*/
|
||||
// sortTailwindcss: {
|
||||
// functions: ['clsx', 'cn', 'cva', 'tw'],
|
||||
// stylesheet: './internal/tailwind-config/src/theme.css',
|
||||
// preserveWhitespace: true,
|
||||
// },
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'*.json',
|
||||
'*.json5',
|
||||
'*.jsonc',
|
||||
'*.code-workspace',
|
||||
'**/*.json',
|
||||
'**/*.json5',
|
||||
'**/*.jsonc',
|
||||
'**/*.code-workspace',
|
||||
],
|
||||
options: {
|
||||
trailingComma: 'none',
|
||||
quoteProps: 'preserve',
|
||||
singleQuote: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
function defineConfig(config: OxfmtConfig = {}): OxfmtConfig {
|
||||
return defineOxfmtConfig({
|
||||
...oxfmtConfig,
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
export { defineConfig, oxfmtConfig };
|
||||
export type { OxfmtConfig };
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/node.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineConfig } from 'tsdown';
|
||||
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
dts: true,
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
outExtensions: () => ({
|
||||
dts: '.d.ts',
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@vben/oxlint-config",
|
||||
"version": "5.7.0",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "internal/lint-configs/oxlint-config"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"stub": "pnpm exec tsdown"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "catalog:",
|
||||
"eslint-plugin-better-tailwindcss": "catalog:",
|
||||
"eslint-plugin-command": "catalog:",
|
||||
"oxlint": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
const command: OxlintConfig = {
|
||||
jsPlugins: [
|
||||
{
|
||||
name: 'command',
|
||||
specifier: 'eslint-plugin-command',
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
'command/command': 'error',
|
||||
},
|
||||
};
|
||||
|
||||
export { command };
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
const comments: OxlintConfig = {
|
||||
jsPlugins: [
|
||||
{
|
||||
name: 'eslint-comments',
|
||||
specifier: '@eslint-community/eslint-plugin-eslint-comments',
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
'eslint/no-underscore-dangle': 'off',
|
||||
'eslint-comments/no-aggregating-enable': 'error',
|
||||
'eslint-comments/no-duplicate-disable': 'error',
|
||||
'eslint-comments/no-unlimited-disable': 'error',
|
||||
'eslint-comments/no-unused-enable': 'error',
|
||||
},
|
||||
};
|
||||
|
||||
export { comments };
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
const ignores: OxlintConfig = {
|
||||
ignorePatterns: [
|
||||
'**/dist/**',
|
||||
'**/node_modules/**',
|
||||
'docs/**',
|
||||
'playground/public/**',
|
||||
'**/*.json',
|
||||
'**/*.md',
|
||||
'**/*.svg',
|
||||
'**/*.yaml',
|
||||
'**/*.yml',
|
||||
],
|
||||
};
|
||||
|
||||
export { ignores };
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
const importPluginConfig: OxlintConfig = {
|
||||
rules: {
|
||||
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
||||
'import/first': 'error',
|
||||
'import/no-duplicates': 'error',
|
||||
'import/no-mutable-exports': 'error',
|
||||
'import/no-named-as-default': 'off',
|
||||
'import/no-named-as-default-member': 'off',
|
||||
'import/no-named-default': 'error',
|
||||
'import/no-self-import': 'error',
|
||||
'import/no-unassigned-import': 'off',
|
||||
'import/no-webpack-loader-syntax': 'error',
|
||||
},
|
||||
};
|
||||
|
||||
export { importPluginConfig };
|
||||
@@ -0,0 +1,96 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
import { defineConfig as defineOxlintConfig } from 'oxlint';
|
||||
|
||||
import { command } from './command';
|
||||
import { comments } from './comments';
|
||||
import { ignores } from './ignores';
|
||||
import { importPluginConfig } from './import';
|
||||
import { javascript } from './javascript';
|
||||
import { node } from './node';
|
||||
import { overrides } from './overrides';
|
||||
import { plugins } from './plugins';
|
||||
import { tailwindcss } from './tailwindcss';
|
||||
import { test } from './test';
|
||||
import { typescript } from './typescript';
|
||||
import { unicorn } from './unicorn';
|
||||
import { vue } from './vue';
|
||||
|
||||
function mergeOxlintConfigs(...configs: OxlintConfig[]): OxlintConfig {
|
||||
const merged: OxlintConfig = {};
|
||||
|
||||
for (const config of configs) {
|
||||
merged.categories =
|
||||
merged.categories && config.categories
|
||||
? { ...merged.categories, ...config.categories }
|
||||
: (config.categories ?? merged.categories);
|
||||
merged.env =
|
||||
merged.env && config.env
|
||||
? { ...merged.env, ...config.env }
|
||||
: (config.env ?? merged.env);
|
||||
merged.globals =
|
||||
merged.globals && config.globals
|
||||
? { ...merged.globals, ...config.globals }
|
||||
: (config.globals ?? merged.globals);
|
||||
merged.ignorePatterns = [
|
||||
...(merged.ignorePatterns ?? []),
|
||||
...(config.ignorePatterns ?? []),
|
||||
];
|
||||
merged.jsPlugins = [
|
||||
...new Set([...(merged.jsPlugins ?? []), ...(config.jsPlugins ?? [])]),
|
||||
];
|
||||
merged.overrides = [
|
||||
...(merged.overrides ?? []),
|
||||
...(config.overrides ?? []),
|
||||
];
|
||||
merged.plugins = [
|
||||
...new Set([...(merged.plugins ?? []), ...(config.plugins ?? [])]),
|
||||
];
|
||||
merged.rules =
|
||||
merged.rules && config.rules
|
||||
? { ...merged.rules, ...config.rules }
|
||||
: (config.rules ?? merged.rules);
|
||||
merged.settings =
|
||||
merged.settings && config.settings
|
||||
? { ...merged.settings, ...config.settings }
|
||||
: (config.settings ?? merged.settings);
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
const oxlintConfig = defineOxlintConfig(
|
||||
mergeOxlintConfigs(
|
||||
javascript,
|
||||
command,
|
||||
comments,
|
||||
ignores,
|
||||
plugins,
|
||||
importPluginConfig,
|
||||
node,
|
||||
overrides,
|
||||
tailwindcss,
|
||||
test,
|
||||
typescript,
|
||||
unicorn,
|
||||
vue,
|
||||
),
|
||||
);
|
||||
|
||||
export {
|
||||
command,
|
||||
comments,
|
||||
ignores,
|
||||
importPluginConfig,
|
||||
javascript,
|
||||
mergeOxlintConfigs,
|
||||
node,
|
||||
overrides,
|
||||
oxlintConfig,
|
||||
plugins,
|
||||
tailwindcss,
|
||||
test,
|
||||
typescript,
|
||||
unicorn,
|
||||
vue,
|
||||
};
|
||||
@@ -0,0 +1,141 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
const javascript: OxlintConfig = {
|
||||
categories: {
|
||||
correctness: 'error',
|
||||
suspicious: 'warn',
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
globals: {
|
||||
document: 'readonly',
|
||||
navigator: 'readonly',
|
||||
window: 'readonly',
|
||||
},
|
||||
rules: {
|
||||
'accessor-pairs': [
|
||||
'error',
|
||||
{
|
||||
enforceForClassMembers: true,
|
||||
setWithoutGet: true,
|
||||
},
|
||||
],
|
||||
'array-callback-return': 'error',
|
||||
'block-scoped-var': 'error',
|
||||
'default-case-last': 'error',
|
||||
eqeqeq: ['error', 'always'],
|
||||
'eslint/no-unreachable': 'error',
|
||||
// 抛出嵌套三元运算格式错误,禁止使用嵌套三元运算。
|
||||
'no-nested-ternary': 'error',
|
||||
'new-cap': [
|
||||
'error',
|
||||
{
|
||||
capIsNew: false,
|
||||
newIsCap: true,
|
||||
properties: true,
|
||||
},
|
||||
],
|
||||
'no-alert': 'error',
|
||||
'no-array-constructor': 'error',
|
||||
'no-caller': 'error',
|
||||
'no-case-declarations': 'error',
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
'no-control-regex': 'error',
|
||||
'no-debugger': 'error',
|
||||
'no-empty': ['error', { allowEmptyCatch: true }],
|
||||
'no-fallthrough': 'error',
|
||||
'no-new-func': 'error',
|
||||
'no-object-constructor': 'error',
|
||||
'no-new-native-nonconstructor': 'error',
|
||||
'no-labels': ['error', { allowLoop: false, allowSwitch: false }],
|
||||
'no-lone-blocks': 'error',
|
||||
'no-multi-str': 'error',
|
||||
'no-nonoctal-decimal-escape': 'error',
|
||||
'no-proto': 'error',
|
||||
'no-prototype-builtins': 'error',
|
||||
'no-redeclare': ['error', { builtinGlobals: false }],
|
||||
'no-regex-spaces': 'error',
|
||||
'no-self-compare': 'error',
|
||||
'no-sequences': 'error',
|
||||
'no-shadow': 'off',
|
||||
'no-shadow-restricted-names': 'error',
|
||||
'eslint/no-empty-function': [
|
||||
'error',
|
||||
{
|
||||
allow: ['arrowFunctions', 'functions', 'methods'],
|
||||
},
|
||||
],
|
||||
'no-template-curly-in-string': 'error',
|
||||
'no-throw-literal': 'error',
|
||||
'no-unassigned-vars': 'error',
|
||||
'no-unexpected-multiline': 'error',
|
||||
'no-unused-expressions': [
|
||||
'error',
|
||||
{
|
||||
allowShortCircuit: true,
|
||||
allowTaggedTemplates: true,
|
||||
allowTernary: true,
|
||||
},
|
||||
],
|
||||
'eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'no-var': 'error',
|
||||
'no-eval': 'error',
|
||||
'no-iterator': 'error',
|
||||
'no-new-wrappers': 'error',
|
||||
'no-restricted-globals': [
|
||||
'error',
|
||||
{ message: 'Use `globalThis` instead.', name: 'global' },
|
||||
{ message: 'Use `globalThis` instead.', name: 'self' },
|
||||
],
|
||||
'no-useless-call': 'error',
|
||||
'no-useless-computed-key': 'error',
|
||||
'no-useless-constructor': 'error',
|
||||
'no-useless-return': 'error',
|
||||
'prefer-const': [
|
||||
'error',
|
||||
{
|
||||
destructuring: 'all',
|
||||
ignoreReadBeforeAssign: true,
|
||||
},
|
||||
],
|
||||
'prefer-exponentiation-operator': 'error',
|
||||
'prefer-promise-reject-errors': 'error',
|
||||
'prefer-rest-params': 'error',
|
||||
'prefer-spread': 'error',
|
||||
'prefer-template': 'error',
|
||||
'preserve-caught-error': [
|
||||
'error',
|
||||
{
|
||||
requireCatchParameter: false,
|
||||
},
|
||||
],
|
||||
'symbol-description': 'error',
|
||||
'unicode-bom': ['error', 'never'],
|
||||
'use-isnan': [
|
||||
'error',
|
||||
{
|
||||
enforceForIndexOf: true,
|
||||
enforceForSwitchCase: true,
|
||||
},
|
||||
],
|
||||
'valid-typeof': [
|
||||
'error',
|
||||
{
|
||||
requireStringLiterals: true,
|
||||
},
|
||||
],
|
||||
'vars-on-top': 'error',
|
||||
yoda: ['error', 'never'],
|
||||
},
|
||||
};
|
||||
|
||||
export { javascript };
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
const node: OxlintConfig = {
|
||||
rules: {
|
||||
'node/no-exports-assign': 'error',
|
||||
'node/no-new-require': 'error',
|
||||
'node/no-path-concat': 'error',
|
||||
},
|
||||
};
|
||||
|
||||
export { node };
|
||||
@@ -0,0 +1,107 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
const overrides: OxlintConfig = {
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.d.ts', '**/*.d.ts'],
|
||||
rules: {
|
||||
'import/no-unassigned-import': 'off',
|
||||
'typescript/triple-slash-reference': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// 这些 @typescript-eslint 规则此前不作用于 .vue(旧 eslint glob 不含 .vue)。
|
||||
// Vue 组件惯用 `interface Props extends XxxProps {}` 声明 props,保持迁移前行为放行。
|
||||
files: ['*.vue', '**/*.vue'],
|
||||
rules: {
|
||||
'typescript/no-empty-object-type': 'off',
|
||||
'typescript/no-unsafe-function-type': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/**/*.js',
|
||||
'**/__tests__/**/*.cjs',
|
||||
'**/__tests__/**/*.mjs',
|
||||
'**/__tests__/**/*.jsx',
|
||||
'**/__tests__/**/*.ts',
|
||||
'**/__tests__/**/*.cts',
|
||||
'**/__tests__/**/*.mts',
|
||||
'**/__tests__/**/*.tsx',
|
||||
'**/*.spec.js',
|
||||
'**/*.spec.cjs',
|
||||
'**/*.spec.mjs',
|
||||
'**/*.spec.jsx',
|
||||
'**/*.spec.ts',
|
||||
'**/*.spec.cts',
|
||||
'**/*.spec.mts',
|
||||
'**/*.spec.tsx',
|
||||
'**/*.test.js',
|
||||
'**/*.test.cjs',
|
||||
'**/*.test.mjs',
|
||||
'**/*.test.jsx',
|
||||
'**/*.test.ts',
|
||||
'**/*.test.cts',
|
||||
'**/*.test.mts',
|
||||
'**/*.test.tsx',
|
||||
'**/*.bench.js',
|
||||
'**/*.bench.cjs',
|
||||
'**/*.bench.mjs',
|
||||
'**/*.bench.jsx',
|
||||
'**/*.bench.ts',
|
||||
'**/*.bench.cts',
|
||||
'**/*.bench.mts',
|
||||
'**/*.bench.tsx',
|
||||
'**/*.benchmark.js',
|
||||
'**/*.benchmark.cjs',
|
||||
'**/*.benchmark.mjs',
|
||||
'**/*.benchmark.jsx',
|
||||
'**/*.benchmark.ts',
|
||||
'**/*.benchmark.cts',
|
||||
'**/*.benchmark.mts',
|
||||
'**/*.benchmark.tsx',
|
||||
],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/@core/base/shared/src/utils/inference.ts'],
|
||||
rules: {
|
||||
'vue/prefer-import-from-vue': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/@core/ui-kit/menu-ui/src/sub-menu.vue'],
|
||||
rules: {
|
||||
'import/no-self-import': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'scripts/**/*.js',
|
||||
'scripts/**/*.cjs',
|
||||
'scripts/**/*.mjs',
|
||||
'scripts/**/*.jsx',
|
||||
'scripts/**/*.ts',
|
||||
'scripts/**/*.cts',
|
||||
'scripts/**/*.mts',
|
||||
'scripts/**/*.tsx',
|
||||
'internal/**/*.js',
|
||||
'internal/**/*.cjs',
|
||||
'internal/**/*.mjs',
|
||||
'internal/**/*.jsx',
|
||||
'internal/**/*.ts',
|
||||
'internal/**/*.cts',
|
||||
'internal/**/*.mts',
|
||||
'internal/**/*.tsx',
|
||||
],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'unicorn/no-process-exit': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export { overrides };
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
const plugins: OxlintConfig = {
|
||||
/**
|
||||
* oxlint 支持的插件,将默认开启的也显示配置
|
||||
* type: eslint | react | unicorn | typescript | oxc |
|
||||
* import | jsdoc | jest | vitest | jsx-a11y | nextjs |
|
||||
* react-perf | promise | node | vue
|
||||
*
|
||||
* Default: eslint,typescript,unicorn,oxc
|
||||
*/
|
||||
plugins: [
|
||||
'eslint',
|
||||
'import',
|
||||
'node',
|
||||
'oxc',
|
||||
'typescript',
|
||||
'unicorn',
|
||||
'vitest',
|
||||
'vue',
|
||||
],
|
||||
};
|
||||
|
||||
export { plugins };
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import eslintPluginBetterTailwindcss from 'eslint-plugin-better-tailwindcss';
|
||||
import { getDefaultSelectors } from 'eslint-plugin-better-tailwindcss/defaults';
|
||||
import { SelectorKind } from 'eslint-plugin-better-tailwindcss/types';
|
||||
|
||||
const selectors = [
|
||||
...getDefaultSelectors(),
|
||||
{
|
||||
kind: SelectorKind.Attribute,
|
||||
match: [{ type: 'objectValues' }],
|
||||
name: '^classNames$',
|
||||
},
|
||||
];
|
||||
|
||||
const entryPoint = fileURLToPath(
|
||||
new URL('../../../../tailwind-config/src/theme.css', import.meta.url),
|
||||
);
|
||||
|
||||
const settings = {
|
||||
entryPoint,
|
||||
selectors,
|
||||
};
|
||||
|
||||
const tailwindcss: OxlintConfig = {
|
||||
// Generated shadcn-ui internals are intentionally left unmanaged.
|
||||
ignorePatterns: ['packages/@core/ui-kit/shadcn-ui/**/*'],
|
||||
jsPlugins: [
|
||||
{
|
||||
name: 'better-tailwindcss',
|
||||
specifier: 'eslint-plugin-better-tailwindcss',
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
...eslintPluginBetterTailwindcss.configs.recommended.rules,
|
||||
'better-tailwindcss/enforce-consistent-class-order': [
|
||||
'error',
|
||||
{
|
||||
detectComponentClasses: true,
|
||||
unknownClassOrder: 'asc',
|
||||
unknownClassPosition: 'start',
|
||||
},
|
||||
],
|
||||
// Let Prettier own wrapping decisions to avoid ping-pong formatting.
|
||||
'better-tailwindcss/enforce-consistent-line-wrapping': 'off',
|
||||
'better-tailwindcss/no-unknown-classes': 'off',
|
||||
},
|
||||
settings: {
|
||||
'better-tailwindcss': settings,
|
||||
'eslint-plugin-better-tailwindcss': settings,
|
||||
},
|
||||
};
|
||||
|
||||
export { tailwindcss };
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
const test: OxlintConfig = {
|
||||
rules: {
|
||||
'jest/no-conditional-expect': 'off',
|
||||
'jest/require-to-throw-message': 'off',
|
||||
'vitest/consistent-test-it': [
|
||||
'error',
|
||||
{
|
||||
fn: 'it',
|
||||
withinDescribe: 'it',
|
||||
},
|
||||
],
|
||||
'vitest/hoisted-apis-on-top': 'off',
|
||||
'vitest/no-focused-tests': 'error',
|
||||
'vitest/no-identical-title': 'error',
|
||||
'vitest/no-import-node-test': 'error',
|
||||
'vitest/prefer-hooks-in-order': 'error',
|
||||
'vitest/prefer-lowercase-title': 'error',
|
||||
'vitest/require-mock-type-parameters': 'off',
|
||||
},
|
||||
};
|
||||
|
||||
export { test };
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
const typescript: OxlintConfig = {
|
||||
rules: {
|
||||
// —— 从 @typescript-eslint strict 预设迁移而来(非类型感知,oxlint 原生支持)——
|
||||
// no-array-constructor / no-useless-constructor 已由 javascript 配置以核心规则覆盖。
|
||||
'typescript/ban-ts-comment': 'error',
|
||||
'typescript/no-duplicate-enum-values': 'error',
|
||||
'typescript/no-dynamic-delete': 'error',
|
||||
'typescript/no-empty-object-type': 'error',
|
||||
'typescript/no-extra-non-null-assertion': 'error',
|
||||
'typescript/no-extraneous-class': 'error',
|
||||
'typescript/no-invalid-void-type': 'error',
|
||||
'typescript/no-misused-new': 'error',
|
||||
'typescript/no-non-null-asserted-nullish-coalescing': 'error',
|
||||
'typescript/no-non-null-asserted-optional-chain': 'error',
|
||||
'typescript/no-non-null-assertion': 'error',
|
||||
'typescript/no-require-imports': 'error',
|
||||
'typescript/no-this-alias': 'error',
|
||||
'typescript/no-unnecessary-type-constraint': 'error',
|
||||
'typescript/no-unsafe-declaration-merging': 'error',
|
||||
'typescript/no-unsafe-function-type': 'error',
|
||||
'typescript/no-var-requires': 'error',
|
||||
'typescript/no-wrapper-object-types': 'error',
|
||||
'typescript/prefer-as-const': 'error',
|
||||
'typescript/prefer-literal-enum-member': 'error',
|
||||
'typescript/prefer-namespace-keyword': 'error',
|
||||
'typescript/triple-slash-reference': 'error',
|
||||
'typescript/unified-signatures': 'error',
|
||||
|
||||
'typescript/await-thenable': 'off',
|
||||
'typescript/consistent-return': 'off',
|
||||
'typescript/no-base-to-string': 'off',
|
||||
'typescript/no-duplicate-type-constituents': 'off',
|
||||
'typescript/no-floating-promises': 'off',
|
||||
'typescript/no-misused-spread': 'off',
|
||||
'typescript/no-redundant-type-constituents': 'off',
|
||||
'typescript/no-unnecessary-boolean-literal-compare': 'off',
|
||||
'typescript/no-unnecessary-template-expression': 'off',
|
||||
'typescript/no-unnecessary-type-arguments': 'off',
|
||||
'typescript/no-unnecessary-type-assertion': 'off',
|
||||
'typescript/no-unnecessary-type-conversion': 'off',
|
||||
'typescript/no-unnecessary-type-parameters': 'off',
|
||||
'typescript/no-unsafe-enum-comparison': 'off',
|
||||
'typescript/no-unsafe-type-assertion': 'off',
|
||||
'typescript/no-useless-default-assignment': 'off',
|
||||
'typescript/restrict-template-expressions': 'off',
|
||||
'typescript/unbound-method': 'off',
|
||||
},
|
||||
};
|
||||
|
||||
export { typescript };
|
||||
@@ -0,0 +1,132 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
const unicorn: OxlintConfig = {
|
||||
rules: {
|
||||
'unicorn/consistent-function-scoping': 'off',
|
||||
'unicorn/no-process-exit': 'error',
|
||||
'unicorn/no-single-promise-in-promise-methods': 'off',
|
||||
'unicorn/no-useless-spread': 'off',
|
||||
'unicorn/prefer-global-this': 'off',
|
||||
'unicorn/prefer-module': 'error',
|
||||
|
||||
'unicorn/catch-error-name': 'error',
|
||||
'unicorn/consistent-assert': 'error',
|
||||
'unicorn/consistent-date-clone': 'error',
|
||||
'unicorn/consistent-empty-array-spread': 'error',
|
||||
'unicorn/consistent-existence-index-check': 'error',
|
||||
'unicorn/consistent-template-literal-escape': 'error',
|
||||
'unicorn/empty-brace-spaces': 'error',
|
||||
'unicorn/error-message': 'error',
|
||||
'unicorn/escape-case': 'error',
|
||||
'unicorn/explicit-length-check': 'error',
|
||||
'unicorn/new-for-builtins': 'error',
|
||||
'unicorn/no-abusive-eslint-disable': 'error',
|
||||
'unicorn/no-accessor-recursion': 'error',
|
||||
'unicorn/no-anonymous-default-export': 'error',
|
||||
'unicorn/no-array-callback-reference': 'error',
|
||||
'unicorn/no-array-method-this-argument': 'error',
|
||||
'unicorn/no-array-reduce': 'error',
|
||||
'unicorn/no-array-reverse': 'error',
|
||||
'unicorn/no-array-sort': 'error',
|
||||
'unicorn/no-await-expression-member': 'error',
|
||||
'unicorn/no-await-in-promise-methods': 'error',
|
||||
'unicorn/no-console-spaces': 'error',
|
||||
'unicorn/no-document-cookie': 'error',
|
||||
'unicorn/no-empty-file': 'error',
|
||||
'unicorn/no-hex-escape': 'error',
|
||||
// oxlint 实现较 eslint 更严格,会误报既有代码,暂关闭
|
||||
'unicorn/no-immediate-mutation': 'off',
|
||||
'unicorn/no-instanceof-builtins': 'error',
|
||||
'unicorn/no-invalid-fetch-options': 'error',
|
||||
'unicorn/no-invalid-remove-event-listener': 'error',
|
||||
'unicorn/no-lonely-if': 'error',
|
||||
'unicorn/no-magic-array-flat-depth': 'error',
|
||||
'unicorn/no-negated-condition': 'error',
|
||||
'unicorn/no-negation-in-equality-check': 'error',
|
||||
// 禁止通过“添加括号”自动修复嵌套三元运算
|
||||
'unicorn/no-nested-ternary': 'off',
|
||||
'unicorn/no-new-array': 'error',
|
||||
'unicorn/no-new-buffer': 'error',
|
||||
'unicorn/no-object-as-default-parameter': 'error',
|
||||
'unicorn/no-static-only-class': 'error',
|
||||
'unicorn/no-thenable': 'error',
|
||||
'unicorn/no-this-assignment': 'error',
|
||||
'unicorn/no-typeof-undefined': 'error',
|
||||
'unicorn/no-unnecessary-array-flat-depth': 'error',
|
||||
'unicorn/no-unnecessary-array-splice-count': 'error',
|
||||
'unicorn/no-unnecessary-await': 'error',
|
||||
'unicorn/no-unnecessary-slice-end': 'error',
|
||||
'unicorn/no-unreadable-array-destructuring': 'error',
|
||||
'unicorn/no-unreadable-iife': 'error',
|
||||
'unicorn/no-useless-collection-argument': 'error',
|
||||
'unicorn/no-useless-error-capture-stack-trace': 'error',
|
||||
'unicorn/no-useless-fallback-in-spread': 'error',
|
||||
'unicorn/no-useless-iterator-to-array': 'error',
|
||||
'unicorn/no-useless-length-check': 'error',
|
||||
'unicorn/no-useless-promise-resolve-reject': 'error',
|
||||
'unicorn/no-useless-switch-case': 'error',
|
||||
'unicorn/no-zero-fractions': 'error',
|
||||
'unicorn/number-literal-case': 'error',
|
||||
'unicorn/numeric-separators-style': 'error',
|
||||
'unicorn/prefer-add-event-listener': 'error',
|
||||
'unicorn/prefer-array-find': 'error',
|
||||
'unicorn/prefer-array-flat': 'error',
|
||||
'unicorn/prefer-array-flat-map': 'error',
|
||||
'unicorn/prefer-array-index-of': 'error',
|
||||
'unicorn/prefer-array-some': 'error',
|
||||
'unicorn/prefer-bigint-literals': 'error',
|
||||
'unicorn/prefer-blob-reading-methods': 'error',
|
||||
'unicorn/prefer-class-fields': 'error',
|
||||
'unicorn/prefer-classlist-toggle': 'error',
|
||||
'unicorn/prefer-code-point': 'error',
|
||||
'unicorn/prefer-date-now': 'error',
|
||||
'unicorn/prefer-default-parameters': 'error',
|
||||
'unicorn/prefer-dom-node-append': 'error',
|
||||
'unicorn/prefer-dom-node-dataset': 'error',
|
||||
'unicorn/prefer-dom-node-remove': 'error',
|
||||
'unicorn/prefer-event-target': 'error',
|
||||
'unicorn/prefer-export-from': ['error', { checkUsedVariables: false }],
|
||||
'unicorn/prefer-includes': 'error',
|
||||
'unicorn/prefer-keyboard-event-key': 'error',
|
||||
'unicorn/prefer-logical-operator-over-ternary': 'error',
|
||||
'unicorn/prefer-math-min-max': 'error',
|
||||
'unicorn/prefer-math-trunc': 'error',
|
||||
'unicorn/prefer-modern-dom-apis': 'error',
|
||||
'unicorn/prefer-modern-math-apis': 'error',
|
||||
'unicorn/prefer-native-coercion-functions': 'error',
|
||||
'unicorn/prefer-negative-index': 'error',
|
||||
'unicorn/prefer-node-protocol': 'error',
|
||||
'unicorn/prefer-number-properties': 'error',
|
||||
'unicorn/prefer-object-from-entries': 'error',
|
||||
'unicorn/prefer-optional-catch-binding': 'error',
|
||||
'unicorn/prefer-prototype-methods': 'error',
|
||||
'unicorn/prefer-query-selector': 'error',
|
||||
'unicorn/prefer-reflect-apply': 'error',
|
||||
'unicorn/prefer-regexp-test': 'error',
|
||||
'unicorn/prefer-response-static-json': 'error',
|
||||
'unicorn/prefer-set-has': 'error',
|
||||
'unicorn/prefer-set-size': 'error',
|
||||
'unicorn/prefer-single-call': 'error',
|
||||
'unicorn/prefer-spread': 'error',
|
||||
'unicorn/prefer-string-raw': 'error',
|
||||
'unicorn/prefer-string-replace-all': 'error',
|
||||
'unicorn/prefer-string-slice': 'error',
|
||||
'unicorn/prefer-string-starts-ends-with': 'error',
|
||||
'unicorn/prefer-string-trim-start-end': 'error',
|
||||
// oxlint 实现较 eslint 更严格(含 cloneDeep),暂关闭以保持迁移前行为
|
||||
'unicorn/prefer-structured-clone': 'off',
|
||||
'unicorn/prefer-ternary': 'error',
|
||||
'unicorn/prefer-type-error': 'error',
|
||||
'unicorn/relative-url-style': 'error',
|
||||
'unicorn/require-array-join-separator': 'error',
|
||||
'unicorn/require-module-attributes': 'error',
|
||||
'unicorn/require-module-specifiers': 'error',
|
||||
'unicorn/require-number-to-fixed-digits-argument': 'error',
|
||||
'unicorn/switch-case-braces': 'error',
|
||||
'unicorn/switch-case-break-position': 'error',
|
||||
'unicorn/text-encoding-identifier-case': 'error',
|
||||
'unicorn/throw-new-error': 'error',
|
||||
},
|
||||
};
|
||||
|
||||
export { unicorn };
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
const vue: OxlintConfig = {
|
||||
rules: {
|
||||
'vue/no-reserved-component-names': 'off',
|
||||
'vue/prefer-import-from-vue': 'error',
|
||||
},
|
||||
};
|
||||
|
||||
export { vue };
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { OxlintConfig } from 'oxlint';
|
||||
|
||||
import { defineConfig as defineOxlintConfig } from 'oxlint';
|
||||
|
||||
import { mergeOxlintConfigs, oxlintConfig } from './configs';
|
||||
|
||||
type VbenOxlintConfig = Omit<OxlintConfig, 'extends'> & {
|
||||
extends?: OxlintConfig[];
|
||||
};
|
||||
|
||||
function defineConfig(config: VbenOxlintConfig = {}) {
|
||||
const { extends: extendedConfigs = [], ...restConfig } = config;
|
||||
|
||||
return defineOxlintConfig(
|
||||
mergeOxlintConfigs(oxlintConfig, ...extendedConfigs, restConfig),
|
||||
);
|
||||
}
|
||||
|
||||
export { defineConfig, oxlintConfig };
|
||||
export * from './configs';
|
||||
export type { OxlintConfig, VbenOxlintConfig };
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/node.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineConfig } from 'tsdown';
|
||||
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
dts: true,
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
outExtensions: () => ({
|
||||
dts: '.d.ts',
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,149 @@
|
||||
export default {
|
||||
extends: ['stylelint-config-standard', 'stylelint-config-recess-order'],
|
||||
ignoreFiles: [
|
||||
'**/*.js',
|
||||
'**/*.jsx',
|
||||
'**/*.tsx',
|
||||
'**/*.ts',
|
||||
'**/*.json',
|
||||
'**/*.md',
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
customSyntax: 'postcss-html',
|
||||
files: ['*.(html|vue)', '**/*.(html|vue)'],
|
||||
rules: {
|
||||
'selector-pseudo-class-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignorePseudoClasses: ['global', 'deep'],
|
||||
},
|
||||
],
|
||||
'selector-pseudo-element-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
customSyntax: 'postcss-scss',
|
||||
extends: [
|
||||
'stylelint-config-recommended-scss',
|
||||
'stylelint-config-recommended-vue/scss',
|
||||
],
|
||||
files: ['*.scss', '**/*.scss'],
|
||||
},
|
||||
],
|
||||
plugins: ['stylelint-order', '@stylistic/stylelint-plugin', 'stylelint-scss'],
|
||||
rules: {
|
||||
'at-rule-no-deprecated': null,
|
||||
'at-rule-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreAtRules: [
|
||||
'extends',
|
||||
'ignores',
|
||||
'include',
|
||||
'mixin',
|
||||
'if',
|
||||
'else',
|
||||
'media',
|
||||
'for',
|
||||
'at-root',
|
||||
'tailwind',
|
||||
'apply',
|
||||
'variants',
|
||||
'responsive',
|
||||
'screen',
|
||||
'function',
|
||||
'each',
|
||||
'use',
|
||||
'forward',
|
||||
'return',
|
||||
'reference',
|
||||
'plugin',
|
||||
'source',
|
||||
'theme',
|
||||
'utility',
|
||||
'custom-variant',
|
||||
],
|
||||
},
|
||||
],
|
||||
'font-family-no-missing-generic-family-keyword': null,
|
||||
'function-no-unknown': null,
|
||||
'import-notation': null,
|
||||
'media-feature-range-notation': null,
|
||||
'named-grid-areas-no-invalid': null,
|
||||
'nesting-selector-no-missing-scoping-root': null,
|
||||
'no-descending-specificity': null,
|
||||
'no-empty-source': null,
|
||||
'no-invalid-position-declaration': null,
|
||||
'order/order': [
|
||||
[
|
||||
'dollar-variables',
|
||||
'custom-properties',
|
||||
'at-rules',
|
||||
'declarations',
|
||||
{
|
||||
name: 'supports',
|
||||
type: 'at-rule',
|
||||
},
|
||||
{
|
||||
name: 'media',
|
||||
type: 'at-rule',
|
||||
},
|
||||
{
|
||||
name: 'include',
|
||||
type: 'at-rule',
|
||||
},
|
||||
'rules',
|
||||
],
|
||||
{ severity: 'error' },
|
||||
],
|
||||
'rule-empty-line-before': [
|
||||
'always',
|
||||
{
|
||||
ignore: ['after-comment', 'first-nested'],
|
||||
},
|
||||
],
|
||||
'scss/at-rule-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreAtRules: [
|
||||
'extends',
|
||||
'ignores',
|
||||
'include',
|
||||
'mixin',
|
||||
'if',
|
||||
'else',
|
||||
'media',
|
||||
'for',
|
||||
'at-root',
|
||||
'tailwind',
|
||||
'apply',
|
||||
'variants',
|
||||
'responsive',
|
||||
'screen',
|
||||
'function',
|
||||
'each',
|
||||
'use',
|
||||
'forward',
|
||||
'return',
|
||||
'reference',
|
||||
'plugin',
|
||||
'source',
|
||||
'theme',
|
||||
'utility',
|
||||
'custom-variant',
|
||||
],
|
||||
},
|
||||
],
|
||||
'scss/operator-no-newline-after': null,
|
||||
'selector-class-pattern':
|
||||
'^-?(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:[.+])?$',
|
||||
|
||||
'selector-not-notation': null,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@vben/stylelint-config",
|
||||
"version": "5.7.0",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "internal/lint-configs/stylelint-config"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./index.mjs",
|
||||
"module": "./index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.mjs",
|
||||
"default": "./index.mjs"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@stylistic/stylelint-plugin": "catalog:",
|
||||
"stylelint-config-recess-order": "catalog:",
|
||||
"stylelint-scss": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"postcss": "catalog:",
|
||||
"postcss-html": "catalog:",
|
||||
"postcss-scss": "catalog:",
|
||||
"stylelint": "catalog:",
|
||||
"stylelint-config-recommended": "catalog:",
|
||||
"stylelint-config-recommended-scss": "catalog:",
|
||||
"stylelint-config-recommended-vue": "catalog:",
|
||||
"stylelint-config-standard": "catalog:",
|
||||
"stylelint-order": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "@vben/node-utils",
|
||||
"version": "5.7.0",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "internal/node-utils"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"stub": "node ./scripts/build.mjs"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@changesets/git": "catalog:",
|
||||
"@manypkg/get-packages": "catalog:",
|
||||
"chalk": "catalog:",
|
||||
"consola": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"execa": "catalog:",
|
||||
"find-up": "catalog:",
|
||||
"ora": "catalog:",
|
||||
"pkg-types": "catalog:",
|
||||
"rimraf": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
const pnpmCommand =
|
||||
process.env.npm_execpath && process.env.npm_execpath.endsWith('.cjs')
|
||||
? [process.execPath, process.env.npm_execpath]
|
||||
: ['pnpm'];
|
||||
|
||||
const steps = [
|
||||
['exec', 'tsdown', '--no-dts'],
|
||||
[
|
||||
'exec',
|
||||
'tsc',
|
||||
'-p',
|
||||
'tsconfig.build.json',
|
||||
'--emitDeclarationOnly',
|
||||
'--declaration',
|
||||
'--outDir',
|
||||
'dist',
|
||||
],
|
||||
];
|
||||
|
||||
for (const args of steps) {
|
||||
const [command, ...commandArgs] = pnpmCommand;
|
||||
let cmd = command;
|
||||
if (cmd.includes(' ')) {
|
||||
cmd = `"${command}"`;
|
||||
}
|
||||
const result = spawnSync(cmd, [...commandArgs, ...args], {
|
||||
shell: true,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { generatorContentHash } from '../hash';
|
||||
|
||||
describe('generatorContentHash', () => {
|
||||
it('should generate an MD5 hash for the content', () => {
|
||||
const content = 'example content';
|
||||
const expectedHash = createHash('md5')
|
||||
.update(content, 'utf8')
|
||||
.digest('hex');
|
||||
const actualHash = generatorContentHash(content);
|
||||
expect(actualHash).toBe(expectedHash);
|
||||
});
|
||||
|
||||
it('should generate an MD5 hash with specified length', () => {
|
||||
const content = 'example content';
|
||||
const hashLength = 10;
|
||||
const generatedHash = generatorContentHash(content, hashLength);
|
||||
expect(generatedHash).toHaveLength(hashLength);
|
||||
});
|
||||
|
||||
it('should correctly generate the hash with specified length', () => {
|
||||
const content = 'example content';
|
||||
const hashLength = 8;
|
||||
const expectedHash = createHash('md5')
|
||||
.update(content, 'utf8')
|
||||
.digest('hex')
|
||||
.slice(0, hashLength);
|
||||
const generatedHash = generatorContentHash(content, hashLength);
|
||||
expect(generatedHash).toBe(expectedHash);
|
||||
});
|
||||
|
||||
it('should return full hash if hash length parameter is not provided', () => {
|
||||
const content = 'example content';
|
||||
const expectedHash = createHash('md5')
|
||||
.update(content, 'utf8')
|
||||
.digest('hex');
|
||||
const actualHash = generatorContentHash(content);
|
||||
expect(actualHash).toBe(expectedHash);
|
||||
});
|
||||
|
||||
it('should handle empty content', () => {
|
||||
const content = '';
|
||||
const expectedHash = createHash('md5')
|
||||
.update(content, 'utf8')
|
||||
.digest('hex');
|
||||
const actualHash = generatorContentHash(content);
|
||||
expect(actualHash).toBe(expectedHash);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
// pathUtils.test.ts
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { toPosixPath } from '../path';
|
||||
|
||||
describe('toPosixPath', () => {
|
||||
// 测试 Windows 风格路径到 POSIX 风格路径的转换
|
||||
it('converts Windows-style paths to POSIX paths', () => {
|
||||
const windowsPath = String.raw`C:\Users\Example\file.txt`;
|
||||
const expectedPosixPath = 'C:/Users/Example/file.txt';
|
||||
expect(toPosixPath(windowsPath)).toBe(expectedPosixPath);
|
||||
});
|
||||
|
||||
// 确认 POSIX 风格路径不会被改变
|
||||
it('leaves POSIX-style paths unchanged', () => {
|
||||
const posixPath = '/home/user/file.txt';
|
||||
expect(toPosixPath(posixPath)).toBe(posixPath);
|
||||
});
|
||||
|
||||
// 测试带有多个分隔符的路径
|
||||
it('converts paths with mixed separators', () => {
|
||||
const mixedPath = String.raw`C:/Users\Example\file.txt`;
|
||||
const expectedPosixPath = 'C:/Users/Example/file.txt';
|
||||
expect(toPosixPath(mixedPath)).toBe(expectedPosixPath);
|
||||
});
|
||||
|
||||
// 测试空字符串
|
||||
it('handles empty strings', () => {
|
||||
const emptyPath = '';
|
||||
expect(toPosixPath(emptyPath)).toBe('');
|
||||
});
|
||||
|
||||
// 测试仅包含分隔符的路径
|
||||
it('handles path with only separators', () => {
|
||||
const separatorsPath = '\\\\\\';
|
||||
const expectedPosixPath = '///';
|
||||
expect(toPosixPath(separatorsPath)).toBe(expectedPosixPath);
|
||||
});
|
||||
|
||||
// 测试不包含任何分隔符的路径
|
||||
it('handles path without separators', () => {
|
||||
const noSeparatorPath = 'file.txt';
|
||||
expect(toPosixPath(noSeparatorPath)).toBe('file.txt');
|
||||
});
|
||||
|
||||
// 测试以分隔符结尾的路径
|
||||
it('handles path ending with a separator', () => {
|
||||
const endingSeparatorPath = 'C:\\Users\\Example\\';
|
||||
const expectedPosixPath = 'C:/Users/Example/';
|
||||
expect(toPosixPath(endingSeparatorPath)).toBe(expectedPosixPath);
|
||||
});
|
||||
|
||||
// 测试以分隔符开头的路径
|
||||
it('handles path starting with a separator', () => {
|
||||
const startingSeparatorPath = String.raw`\Users\Example`;
|
||||
const expectedPosixPath = '/Users/Example';
|
||||
expect(toPosixPath(startingSeparatorPath)).toBe(expectedPosixPath);
|
||||
});
|
||||
|
||||
// 测试包含非法字符的路径
|
||||
it('handles path with invalid characters', () => {
|
||||
const invalidCharsPath = String.raw`C:\Us*?ers\Ex<ample>|file.txt`;
|
||||
const expectedPosixPath = 'C:/Us*?ers/Ex<ample>|file.txt';
|
||||
expect(toPosixPath(invalidCharsPath)).toBe(expectedPosixPath);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
enum UNICODE {
|
||||
FAILURE = '\u2716', // ✖
|
||||
SUCCESS = '\u2714', // ✔
|
||||
}
|
||||
|
||||
export { UNICODE };
|
||||
@@ -0,0 +1,12 @@
|
||||
import dayjs from 'dayjs';
|
||||
import timezone from 'dayjs/plugin/timezone.js';
|
||||
import utc from 'dayjs/plugin/utc.js';
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
dayjs.tz.setDefault('Asia/Shanghai');
|
||||
|
||||
const dateUtil = dayjs;
|
||||
|
||||
export { dateUtil };
|
||||
@@ -0,0 +1,13 @@
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
import { execa } from 'execa';
|
||||
|
||||
async function formatFile(filepath: string) {
|
||||
await execa('oxfmt', [filepath], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
return await fs.readFile(filepath, 'utf8');
|
||||
}
|
||||
|
||||
export { formatFile };
|
||||
@@ -0,0 +1,39 @@
|
||||
import { promises as fs } from 'node:fs';
|
||||
import { dirname } from 'node:path';
|
||||
|
||||
export async function outputJSON(
|
||||
filePath: string,
|
||||
data: any,
|
||||
spaces: number = 2,
|
||||
) {
|
||||
try {
|
||||
const dir = dirname(filePath);
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
const jsonData = JSON.stringify(data, null, spaces);
|
||||
await fs.writeFile(filePath, jsonData, 'utf8');
|
||||
} catch (error) {
|
||||
console.error('Error writing JSON file:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureFile(filePath: string) {
|
||||
try {
|
||||
const dir = dirname(filePath);
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
await fs.writeFile(filePath, '', { flag: 'a' });
|
||||
} catch (error) {
|
||||
console.error('Error ensuring file:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function readJSON(filePath: string) {
|
||||
try {
|
||||
const data = await fs.readFile(filePath, 'utf8');
|
||||
return JSON.parse(data);
|
||||
} catch (error) {
|
||||
console.error('Error reading JSON file:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import path from 'node:path';
|
||||
|
||||
import { execa } from 'execa';
|
||||
|
||||
export * from '@changesets/git';
|
||||
|
||||
/**
|
||||
* 获取暂存区文件
|
||||
*/
|
||||
async function getStagedFiles(): Promise<string[]> {
|
||||
try {
|
||||
const { stdout } = await execa('git', [
|
||||
'-c',
|
||||
'submodule.recurse=false',
|
||||
'diff',
|
||||
'--staged',
|
||||
'--diff-filter=ACMR',
|
||||
'--name-only',
|
||||
'--ignore-submodules',
|
||||
'-z',
|
||||
]);
|
||||
|
||||
const nullSeparator = '\u0000';
|
||||
const normalizedStdout = stdout.endsWith(nullSeparator)
|
||||
? stdout.slice(0, -1)
|
||||
: stdout;
|
||||
let changedList = normalizedStdout
|
||||
? normalizedStdout.split(nullSeparator)
|
||||
: [];
|
||||
changedList = changedList.map((item) => path.resolve(process.cwd(), item));
|
||||
const changedSet = new Set(changedList);
|
||||
changedSet.delete('');
|
||||
return [...changedSet];
|
||||
} catch (error) {
|
||||
console.error('Failed to get staged files:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export { getStagedFiles };
|
||||
@@ -0,0 +1,18 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
|
||||
/**
|
||||
* 生产基于内容的 hash,可自定义长度
|
||||
* @param content
|
||||
* @param hashLSize
|
||||
*/
|
||||
function generatorContentHash(content: string, hashLSize?: number) {
|
||||
const hash = createHash('md5').update(content, 'utf8').digest('hex');
|
||||
|
||||
if (hashLSize) {
|
||||
return hash.slice(0, hashLSize);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
export { generatorContentHash };
|
||||
@@ -0,0 +1,19 @@
|
||||
export * from './constants';
|
||||
export * from './date';
|
||||
export { formatFile } from './formatter';
|
||||
export * from './fs';
|
||||
export * from './git';
|
||||
export { getStagedFiles, add as gitAdd } from './git';
|
||||
export { generatorContentHash } from './hash';
|
||||
export * from './monorepo';
|
||||
export { toPosixPath } from './path';
|
||||
export * from './spinner';
|
||||
export type { Package } from '@manypkg/get-packages';
|
||||
export { default as colors } from 'chalk';
|
||||
export { consola } from 'consola';
|
||||
export * from 'execa';
|
||||
|
||||
export { default as fs } from 'node:fs/promises';
|
||||
|
||||
export { type PackageJson, readPackageJSON } from 'pkg-types';
|
||||
export { rimraf } from 'rimraf';
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { Package } from '@manypkg/get-packages';
|
||||
|
||||
import { existsSync } from 'node:fs';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
|
||||
import * as manypkg from '@manypkg/get-packages';
|
||||
const { getPackages: getPackagesFunc, getPackagesSync: getPackagesSyncFunc } =
|
||||
manypkg;
|
||||
|
||||
/**
|
||||
* 查找大仓的根目录
|
||||
* @param cwd
|
||||
*/
|
||||
function findMonorepoRoot(cwd: string = process.cwd()) {
|
||||
let currentDir = resolve(cwd);
|
||||
|
||||
while (true) {
|
||||
if (existsSync(join(currentDir, 'pnpm-lock.yaml'))) {
|
||||
return currentDir;
|
||||
}
|
||||
|
||||
const parentDir = dirname(currentDir);
|
||||
if (parentDir === currentDir) {
|
||||
return '';
|
||||
}
|
||||
|
||||
currentDir = parentDir;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取大仓的所有包
|
||||
*/
|
||||
function getPackagesSync() {
|
||||
const root = findMonorepoRoot();
|
||||
return getPackagesSyncFunc(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取大仓的所有包
|
||||
*/
|
||||
async function getPackages() {
|
||||
const root = findMonorepoRoot();
|
||||
|
||||
return await getPackagesFunc(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取大仓指定的包
|
||||
*/
|
||||
async function getPackage(pkgName: string) {
|
||||
const { packages } = await getPackages();
|
||||
return packages.find((pkg: Package) => pkg.packageJson.name === pkgName);
|
||||
}
|
||||
|
||||
export { findMonorepoRoot, getPackage, getPackages, getPackagesSync };
|
||||
@@ -0,0 +1,11 @@
|
||||
import { posix } from 'node:path';
|
||||
|
||||
/**
|
||||
* 将给定的文件路径转换为 POSIX 风格。
|
||||
* @param {string} pathname - 原始文件路径。
|
||||
*/
|
||||
function toPosixPath(pathname: string) {
|
||||
return pathname.split(`\\`).join(posix.sep);
|
||||
}
|
||||
|
||||
export { toPosixPath };
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { Ora } from 'ora';
|
||||
|
||||
import ora from 'ora';
|
||||
|
||||
interface SpinnerOptions {
|
||||
failedText?: string;
|
||||
successText?: string;
|
||||
title: string;
|
||||
}
|
||||
export async function spinner<T>(
|
||||
{ failedText, successText, title }: SpinnerOptions,
|
||||
callback: () => Promise<T>,
|
||||
): Promise<T> {
|
||||
const loading: Ora = ora(title).start();
|
||||
|
||||
try {
|
||||
const result = await callback();
|
||||
loading.succeed(successText || 'Success!');
|
||||
return result;
|
||||
} catch (error) {
|
||||
loading.fail(failedText || 'Failed!');
|
||||
throw error;
|
||||
} finally {
|
||||
loading.stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"noEmit": false
|
||||
},
|
||||
"exclude": ["node_modules", "src/__tests__"]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/node.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'tsdown';
|
||||
|
||||
export default defineConfig({
|
||||
clean: false,
|
||||
deps: {
|
||||
skipNodeModulesBundle: true,
|
||||
},
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "@vben/tailwind-config",
|
||||
"version": "5.7.0",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "internal/tailwind-config"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"sideEffects": [
|
||||
"**/*.css"
|
||||
],
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
},
|
||||
"./theme": {
|
||||
"default": "./src/theme.css"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/tailwind4": "catalog:",
|
||||
"@tailwindcss/typography": "catalog:",
|
||||
"tailwindcss": "catalog:",
|
||||
"tw-animate-css": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import './theme.css';
|
||||
@@ -0,0 +1,597 @@
|
||||
/*
|
||||
* 级联层顺序声明(必须先于 @import 'tailwindcss' 首次出现):
|
||||
* properties(Tailwind 的 --tw-* 回退变量层) < preflight(base) <
|
||||
* UI 库组件样式(antd / el / td) < Tailwind 工具类,
|
||||
* 使组件库样式(antdv-next 经 StyleProvider layer 注入 @layer antd;
|
||||
* element-plus / tdesign 经各自应用的 vite css-layer 插件包入对应层)
|
||||
* 能被工具类覆盖。
|
||||
* 与 internal/vite-config/src/plugins/css-layer.ts 中的 LAYER_ORDER_STATEMENT
|
||||
* 保持一致。
|
||||
*/
|
||||
@layer properties, theme, base, ant, antd, el, td, components, utilities;
|
||||
|
||||
@import 'tailwindcss';
|
||||
@import 'tw-animate-css';
|
||||
|
||||
@plugin '@tailwindcss/typography';
|
||||
@plugin '@iconify/tailwind4';
|
||||
|
||||
/* Monorepo source detection: scan all packages and apps for utility classes */
|
||||
@source '../../../packages/';
|
||||
@source '../../../apps/';
|
||||
@source '../../../docs/';
|
||||
@source '../../../playground/';
|
||||
|
||||
/* Dark mode uses .dark class selector, not prefers-color-scheme */
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
/* Explicitly pin Tailwind v4 dynamic spacing for classes like w-150/h-55. */
|
||||
@theme {
|
||||
--spacing: 0.25rem;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
/* Font */
|
||||
--font-sans: var(--font-family);
|
||||
|
||||
/* Border Radius */
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
|
||||
/* Box Shadow */
|
||||
--shadow-float:
|
||||
0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%),
|
||||
0 9px 28px 8px rgb(0 0 0 / 5%);
|
||||
|
||||
/* Animations */
|
||||
--animate-accordion-down: accordion-down 0.2s ease-out;
|
||||
--animate-accordion-up: accordion-up 0.2s ease-out;
|
||||
--animate-collapsible-down: collapsible-down 0.2s ease-in-out;
|
||||
--animate-collapsible-up: collapsible-up 0.2s ease-in-out;
|
||||
--animate-float: float 5s linear 0ms infinite;
|
||||
|
||||
/* ===== Semantic Colors (shadcn-ui) ===== */
|
||||
|
||||
--color-background: hsl(var(--background));
|
||||
--color-background-deep: hsl(var(--background-deep));
|
||||
--color-foreground: hsl(var(--foreground));
|
||||
--color-card: hsl(var(--card));
|
||||
--color-card-foreground: hsl(var(--card-foreground));
|
||||
--color-popover: hsl(var(--popover));
|
||||
--color-popover-foreground: hsl(var(--popover-foreground));
|
||||
--color-muted: hsl(var(--muted));
|
||||
--color-muted-foreground: hsl(var(--muted-foreground));
|
||||
--color-accent: hsl(var(--accent));
|
||||
--color-accent-foreground: hsl(var(--accent-foreground));
|
||||
--color-accent-hover: hsl(var(--accent-hover));
|
||||
--color-accent-lighter: hsl(var(--accent-lighter));
|
||||
--color-border: hsl(var(--border));
|
||||
--color-input: hsl(var(--input));
|
||||
--color-input-background: hsl(var(--input-background));
|
||||
--color-ring: hsl(var(--ring));
|
||||
--color-secondary: hsl(var(--secondary));
|
||||
--color-secondary-desc: hsl(var(--secondary-desc));
|
||||
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
||||
|
||||
/* ===== Custom Semantic Colors ===== */
|
||||
|
||||
--color-header: hsl(var(--header));
|
||||
--color-heavy: hsl(var(--heavy));
|
||||
--color-heavy-foreground: hsl(var(--heavy-foreground));
|
||||
--color-main: hsl(var(--main));
|
||||
--color-overlay: hsl(var(--overlay));
|
||||
--color-overlay-content: hsl(var(--overlay-content));
|
||||
--color-sidebar: hsl(var(--sidebar));
|
||||
--color-sidebar-deep: hsl(var(--sidebar-deep));
|
||||
|
||||
/* ===== Primary Palette ===== */
|
||||
|
||||
--color-primary: hsl(var(--primary));
|
||||
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||
--color-primary-50: hsl(var(--primary-50));
|
||||
--color-primary-100: hsl(var(--primary-100));
|
||||
--color-primary-200: hsl(var(--primary-200));
|
||||
--color-primary-300: hsl(var(--primary-300));
|
||||
--color-primary-400: hsl(var(--primary-400));
|
||||
--color-primary-500: hsl(var(--primary-500));
|
||||
--color-primary-600: hsl(var(--primary-600));
|
||||
--color-primary-700: hsl(var(--primary-700));
|
||||
--color-primary-active: hsl(var(--primary-700));
|
||||
--color-primary-background-light: hsl(var(--primary-200));
|
||||
--color-primary-background-lighter: hsl(var(--primary-100));
|
||||
--color-primary-background-lightest: hsl(var(--primary-50));
|
||||
--color-primary-border: hsl(var(--primary-400));
|
||||
--color-primary-border-light: hsl(var(--primary-300));
|
||||
--color-primary-hover: hsl(var(--primary-600));
|
||||
--color-primary-text: hsl(var(--primary-500));
|
||||
--color-primary-text-active: hsl(var(--primary-700));
|
||||
--color-primary-text-hover: hsl(var(--primary-600));
|
||||
|
||||
/* ===== Destructive Palette ===== */
|
||||
|
||||
--color-destructive: hsl(var(--destructive));
|
||||
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
||||
--color-destructive-50: hsl(var(--destructive-50));
|
||||
--color-destructive-100: hsl(var(--destructive-100));
|
||||
--color-destructive-200: hsl(var(--destructive-200));
|
||||
--color-destructive-300: hsl(var(--destructive-300));
|
||||
--color-destructive-400: hsl(var(--destructive-400));
|
||||
--color-destructive-500: hsl(var(--destructive-500));
|
||||
--color-destructive-600: hsl(var(--destructive-600));
|
||||
--color-destructive-700: hsl(var(--destructive-700));
|
||||
--color-destructive-active: hsl(var(--destructive-700));
|
||||
--color-destructive-background-light: hsl(var(--destructive-200));
|
||||
--color-destructive-background-lighter: hsl(var(--destructive-100));
|
||||
--color-destructive-background-lightest: hsl(var(--destructive-50));
|
||||
--color-destructive-border: hsl(var(--destructive-400));
|
||||
--color-destructive-border-light: hsl(var(--destructive-300));
|
||||
--color-destructive-hover: hsl(var(--destructive-600));
|
||||
--color-destructive-text: hsl(var(--destructive-500));
|
||||
--color-destructive-text-active: hsl(var(--destructive-700));
|
||||
--color-destructive-text-hover: hsl(var(--destructive-600));
|
||||
|
||||
/* ===== Success Palette ===== */
|
||||
|
||||
--color-success: hsl(var(--success));
|
||||
--color-success-foreground: hsl(var(--success-foreground));
|
||||
--color-success-50: hsl(var(--success-50));
|
||||
--color-success-100: hsl(var(--success-100));
|
||||
--color-success-200: hsl(var(--success-200));
|
||||
--color-success-300: hsl(var(--success-300));
|
||||
--color-success-400: hsl(var(--success-400));
|
||||
--color-success-500: hsl(var(--success-500));
|
||||
--color-success-600: hsl(var(--success-600));
|
||||
--color-success-700: hsl(var(--success-700));
|
||||
--color-success-active: hsl(var(--success-700));
|
||||
--color-success-background-light: hsl(var(--success-200));
|
||||
--color-success-background-lighter: hsl(var(--success-100));
|
||||
--color-success-background-lightest: hsl(var(--success-50));
|
||||
--color-success-border: hsl(var(--success-400));
|
||||
--color-success-border-light: hsl(var(--success-300));
|
||||
--color-success-hover: hsl(var(--success-600));
|
||||
--color-success-text: hsl(var(--success-500));
|
||||
--color-success-text-active: hsl(var(--success-700));
|
||||
--color-success-text-hover: hsl(var(--success-600));
|
||||
|
||||
/* ===== Warning Palette ===== */
|
||||
|
||||
--color-warning: hsl(var(--warning));
|
||||
--color-warning-foreground: hsl(var(--warning-foreground));
|
||||
--color-warning-50: hsl(var(--warning-50));
|
||||
--color-warning-100: hsl(var(--warning-100));
|
||||
--color-warning-200: hsl(var(--warning-200));
|
||||
--color-warning-300: hsl(var(--warning-300));
|
||||
--color-warning-400: hsl(var(--warning-400));
|
||||
--color-warning-500: hsl(var(--warning-500));
|
||||
--color-warning-600: hsl(var(--warning-600));
|
||||
--color-warning-700: hsl(var(--warning-700));
|
||||
--color-warning-active: hsl(var(--warning-700));
|
||||
--color-warning-background-light: hsl(var(--warning-200));
|
||||
--color-warning-background-lighter: hsl(var(--warning-100));
|
||||
--color-warning-background-lightest: hsl(var(--warning-50));
|
||||
--color-warning-border: hsl(var(--warning-400));
|
||||
--color-warning-border-light: hsl(var(--warning-300));
|
||||
--color-warning-hover: hsl(var(--warning-600));
|
||||
--color-warning-text: hsl(var(--warning-500));
|
||||
--color-warning-text-active: hsl(var(--warning-700));
|
||||
--color-warning-text-hover: hsl(var(--warning-600));
|
||||
|
||||
/* ===== Green Palette (alias for success shades) ===== */
|
||||
|
||||
--color-green-50: hsl(var(--green-50));
|
||||
--color-green-100: hsl(var(--green-100));
|
||||
--color-green-200: hsl(var(--green-200));
|
||||
--color-green-300: hsl(var(--green-300));
|
||||
--color-green-400: hsl(var(--green-400));
|
||||
--color-green-500: hsl(var(--green-500));
|
||||
--color-green-600: hsl(var(--green-600));
|
||||
--color-green-700: hsl(var(--green-700));
|
||||
--color-green-active: hsl(var(--green-700));
|
||||
--color-green-background-light: hsl(var(--green-200));
|
||||
--color-green-background-lighter: hsl(var(--green-100));
|
||||
--color-green-background-lightest: hsl(var(--green-50));
|
||||
--color-green-border: hsl(var(--green-400));
|
||||
--color-green-border-light: hsl(var(--green-300));
|
||||
--color-green-foreground: hsl(var(--success-foreground));
|
||||
--color-green-hover: hsl(var(--green-600));
|
||||
--color-green-text: hsl(var(--green-500));
|
||||
--color-green-text-active: hsl(var(--green-700));
|
||||
--color-green-text-hover: hsl(var(--green-600));
|
||||
|
||||
/* ===== Red Palette (alias for destructive shades) ===== */
|
||||
|
||||
--color-red-50: hsl(var(--red-50));
|
||||
--color-red-100: hsl(var(--red-100));
|
||||
--color-red-200: hsl(var(--red-200));
|
||||
--color-red-300: hsl(var(--red-300));
|
||||
--color-red-400: hsl(var(--red-400));
|
||||
--color-red-500: hsl(var(--red-500));
|
||||
--color-red-600: hsl(var(--red-600));
|
||||
--color-red-700: hsl(var(--red-700));
|
||||
--color-red-active: hsl(var(--red-700));
|
||||
--color-red-background-light: hsl(var(--red-200));
|
||||
--color-red-background-lighter: hsl(var(--red-100));
|
||||
--color-red-background-lightest: hsl(var(--red-50));
|
||||
--color-red-border: hsl(var(--red-400));
|
||||
--color-red-border-light: hsl(var(--red-300));
|
||||
--color-red-foreground: hsl(var(--destructive-foreground));
|
||||
--color-red-hover: hsl(var(--red-600));
|
||||
--color-red-text: hsl(var(--red-500));
|
||||
--color-red-text-active: hsl(var(--red-700));
|
||||
--color-red-text-hover: hsl(var(--red-600));
|
||||
|
||||
/* ===== Yellow Palette (alias for warning shades) ===== */
|
||||
|
||||
--color-yellow-50: hsl(var(--yellow-50));
|
||||
--color-yellow-100: hsl(var(--yellow-100));
|
||||
--color-yellow-200: hsl(var(--yellow-200));
|
||||
--color-yellow-300: hsl(var(--yellow-300));
|
||||
--color-yellow-400: hsl(var(--yellow-400));
|
||||
--color-yellow-500: hsl(var(--yellow-500));
|
||||
--color-yellow-600: hsl(var(--yellow-600));
|
||||
--color-yellow-700: hsl(var(--yellow-700));
|
||||
--color-yellow-active: hsl(var(--yellow-700));
|
||||
--color-yellow-background-light: hsl(var(--yellow-200));
|
||||
--color-yellow-background-lighter: hsl(var(--yellow-100));
|
||||
--color-yellow-background-lightest: hsl(var(--yellow-50));
|
||||
--color-yellow-border: hsl(var(--yellow-400));
|
||||
--color-yellow-border-light: hsl(var(--yellow-300));
|
||||
--color-yellow-foreground: hsl(var(--warning-foreground));
|
||||
--color-yellow-hover: hsl(var(--yellow-600));
|
||||
--color-yellow-text: hsl(var(--yellow-500));
|
||||
--color-yellow-text-active: hsl(var(--yellow-700));
|
||||
--color-yellow-text-hover: hsl(var(--yellow-600));
|
||||
}
|
||||
|
||||
/* Keyframes */
|
||||
@keyframes accordion-down {
|
||||
from {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
height: var(--reka-accordion-content-height);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes accordion-up {
|
||||
from {
|
||||
height: var(--reka-accordion-content-height);
|
||||
}
|
||||
|
||||
to {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes collapsible-down {
|
||||
from {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
height: var(--reka-collapsible-content-height);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes collapsible-up {
|
||||
from {
|
||||
height: var(--reka-collapsible-content-height);
|
||||
}
|
||||
|
||||
to {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Base styles */
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before {
|
||||
@apply border-border outline-ring/50;
|
||||
|
||||
box-sizing: border-box;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
--vben-viewport-height: 100vh;
|
||||
|
||||
@apply bg-background font-sans text-foreground;
|
||||
|
||||
scroll-behavior: smooth;
|
||||
font-size: var(--font-size-base, 16px);
|
||||
font-variation-settings: normal;
|
||||
font-synthesis-weight: none;
|
||||
line-height: 1.15;
|
||||
text-rendering: optimizelegibility;
|
||||
text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
@supports (height: 1dvh) {
|
||||
--vben-viewport-height: 100dvh;
|
||||
}
|
||||
}
|
||||
|
||||
#app,
|
||||
body,
|
||||
html {
|
||||
@apply size-full;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
a,
|
||||
a:active,
|
||||
a:hover,
|
||||
a:link,
|
||||
a:visited {
|
||||
@apply no-underline;
|
||||
}
|
||||
|
||||
::view-transition-new(root),
|
||||
::view-transition-old(root) {
|
||||
@apply animate-none mix-blend-normal;
|
||||
}
|
||||
|
||||
::view-transition-old(root) {
|
||||
@apply z-1;
|
||||
}
|
||||
|
||||
::view-transition-new(root) {
|
||||
@apply z-2147483646;
|
||||
}
|
||||
|
||||
html.dark::view-transition-old(root) {
|
||||
@apply z-2147483646;
|
||||
}
|
||||
|
||||
html.dark::view-transition-new(root) {
|
||||
@apply z-1;
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
input[type='number']::-webkit-inner-spin-button,
|
||||
input[type='number']::-webkit-outer-spin-button {
|
||||
@apply m-0 appearance-none;
|
||||
}
|
||||
|
||||
/* Only adjust scrollbar for non-macOS */
|
||||
html:not([data-platform='macOs']) {
|
||||
::-webkit-scrollbar {
|
||||
@apply h-2.5 w-2.5;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply rounded-sm border-none bg-border;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@apply rounded-sm border-none bg-transparent shadow-none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tailwind v4 Preflight 不再为 button 默认设置 pointer;见官方升级说明:
|
||||
* https://tailwindcss.com/docs/upgrade-guide#buttons-use-the-default-cursor */
|
||||
button:not(:disabled),
|
||||
[role='button']:not(:disabled) {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom utilities (v4 @utility syntax) */
|
||||
@utility flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@utility flex-col-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Tailwind v4 的 utilities 在 @layer 内;组件样式若留在 layer 外,会按层叠规则压过 py-4 等工具类。
|
||||
* 见:https://tailwindcss.com/docs/adding-custom-styles#using-css-and-layering */
|
||||
@layer components {
|
||||
.outline-box {
|
||||
@apply relative cursor-pointer rounded-md p-1 outline-1 outline-border;
|
||||
}
|
||||
|
||||
.outline-box::after {
|
||||
@apply absolute top-1/2 left-1/2 z-20 h-0 w-px rounded-sm opacity-0 outline-2 outline-transparent transition-all duration-300 content-[""];
|
||||
}
|
||||
|
||||
.outline-box.outline-box-active {
|
||||
@apply outline-2 outline-primary;
|
||||
}
|
||||
|
||||
.outline-box.outline-box-active::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.outline-box:not(.outline-box-active):hover::after {
|
||||
@apply top-0 left-0 h-full w-full p-1 opacity-100 outline-primary;
|
||||
}
|
||||
|
||||
.vben-link {
|
||||
@apply cursor-pointer text-primary hover:text-primary-hover active:text-primary-active;
|
||||
}
|
||||
|
||||
.card-box {
|
||||
@apply rounded-xl border border-border bg-card text-card-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* Enter animations (converted from enterAnimationPlugin) */
|
||||
@keyframes enter-x-animation {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes enter-y-animation {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.enter-x:nth-child(1) {
|
||||
opacity: 0;
|
||||
transform: translateX(50px);
|
||||
animation: enter-x-animation 0.3s ease-in-out 0.1s forwards;
|
||||
}
|
||||
|
||||
.enter-x:nth-child(2) {
|
||||
opacity: 0;
|
||||
transform: translateX(50px);
|
||||
animation: enter-x-animation 0.3s ease-in-out 0.2s forwards;
|
||||
}
|
||||
|
||||
.enter-x:nth-child(3) {
|
||||
opacity: 0;
|
||||
transform: translateX(50px);
|
||||
animation: enter-x-animation 0.3s ease-in-out 0.3s forwards;
|
||||
}
|
||||
|
||||
.enter-x:nth-child(4) {
|
||||
opacity: 0;
|
||||
transform: translateX(50px);
|
||||
animation: enter-x-animation 0.3s ease-in-out 0.4s forwards;
|
||||
}
|
||||
|
||||
.enter-x:nth-child(5) {
|
||||
opacity: 0;
|
||||
transform: translateX(50px);
|
||||
animation: enter-x-animation 0.3s ease-in-out 0.5s forwards;
|
||||
}
|
||||
|
||||
.enter-y:nth-child(1) {
|
||||
opacity: 0;
|
||||
transform: translateY(50px);
|
||||
animation: enter-y-animation 0.3s ease-in-out 0.1s forwards;
|
||||
}
|
||||
|
||||
.enter-y:nth-child(2) {
|
||||
opacity: 0;
|
||||
transform: translateY(50px);
|
||||
animation: enter-y-animation 0.3s ease-in-out 0.2s forwards;
|
||||
}
|
||||
|
||||
.enter-y:nth-child(3) {
|
||||
opacity: 0;
|
||||
transform: translateY(50px);
|
||||
animation: enter-y-animation 0.3s ease-in-out 0.3s forwards;
|
||||
}
|
||||
|
||||
.enter-y:nth-child(4) {
|
||||
opacity: 0;
|
||||
transform: translateY(50px);
|
||||
animation: enter-y-animation 0.3s ease-in-out 0.4s forwards;
|
||||
}
|
||||
|
||||
.enter-y:nth-child(5) {
|
||||
opacity: 0;
|
||||
transform: translateY(50px);
|
||||
animation: enter-y-animation 0.3s ease-in-out 0.5s forwards;
|
||||
}
|
||||
|
||||
.-enter-x:nth-child(1) {
|
||||
opacity: 0;
|
||||
transform: translateX(-50px);
|
||||
animation: enter-x-animation 0.3s ease-in-out 0.1s forwards;
|
||||
}
|
||||
|
||||
.-enter-x:nth-child(2) {
|
||||
opacity: 0;
|
||||
transform: translateX(-50px);
|
||||
animation: enter-x-animation 0.3s ease-in-out 0.2s forwards;
|
||||
}
|
||||
|
||||
.-enter-x:nth-child(3) {
|
||||
opacity: 0;
|
||||
transform: translateX(-50px);
|
||||
animation: enter-x-animation 0.3s ease-in-out 0.3s forwards;
|
||||
}
|
||||
|
||||
.-enter-x:nth-child(4) {
|
||||
opacity: 0;
|
||||
transform: translateX(-50px);
|
||||
animation: enter-x-animation 0.3s ease-in-out 0.4s forwards;
|
||||
}
|
||||
|
||||
.-enter-x:nth-child(5) {
|
||||
opacity: 0;
|
||||
transform: translateX(-50px);
|
||||
animation: enter-x-animation 0.3s ease-in-out 0.5s forwards;
|
||||
}
|
||||
|
||||
.-enter-y:nth-child(1) {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px);
|
||||
animation: enter-y-animation 0.3s ease-in-out 0.1s forwards;
|
||||
}
|
||||
|
||||
.-enter-y:nth-child(2) {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px);
|
||||
animation: enter-y-animation 0.3s ease-in-out 0.2s forwards;
|
||||
}
|
||||
|
||||
.-enter-y:nth-child(3) {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px);
|
||||
animation: enter-y-animation 0.3s ease-in-out 0.3s forwards;
|
||||
}
|
||||
|
||||
.-enter-y:nth-child(4) {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px);
|
||||
animation: enter-y-animation 0.3s ease-in-out 0.4s forwards;
|
||||
}
|
||||
|
||||
.-enter-y:nth-child(5) {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px);
|
||||
animation: enter-y-animation 0.3s ease-in-out 0.5s forwards;
|
||||
}
|
||||
|
||||
html.invert-mode {
|
||||
@apply invert;
|
||||
}
|
||||
|
||||
html.grayscale-mode {
|
||||
@apply grayscale;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/web.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Base",
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"target": "ESNext",
|
||||
|
||||
"moduleDetection": "force",
|
||||
"experimentalDecorators": true,
|
||||
|
||||
"module": "ESNext",
|
||||
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitThis": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
|
||||
"inlineSources": false,
|
||||
"noEmit": true,
|
||||
"removeComments": true,
|
||||
"sourceMap": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"skipLibCheck": true,
|
||||
"preserveWatchOutput": true
|
||||
},
|
||||
"exclude": ["**/node_modules/**", "**/dist/**", "**/.turbo/**"]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Web Application",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"useDefineForClassFields": true,
|
||||
"moduleResolution": "bundler",
|
||||
"declaration": true,
|
||||
"noEmit": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Node Config",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"lib": ["ESNext"],
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["node"],
|
||||
"noImplicitAny": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@vben/tsconfig",
|
||||
"version": "5.7.0",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "internal/tsconfig"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"base.json",
|
||||
"library.json",
|
||||
"node.json",
|
||||
"web-app.json",
|
||||
"web.json"
|
||||
],
|
||||
"dependencies": {
|
||||
"@vben/types": "workspace:*",
|
||||
"vite": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Web Application",
|
||||
"extends": "./web.json",
|
||||
"compilerOptions": {
|
||||
"types": ["vite/client", "@vben/types/global"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Web Package",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "vue",
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"useDefineForClassFields": true,
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["vite/client"],
|
||||
"declaration": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@vben/vite-config",
|
||||
"version": "5.7.0",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "internal/vite-config"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"stub": "pnpm exec tsdown"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "catalog:",
|
||||
"@jspm/generator": "catalog:",
|
||||
"@tailwindcss/vite": "catalog:",
|
||||
"@vben/node-utils": "workspace:*",
|
||||
"archiver": "catalog:",
|
||||
"cheerio": "catalog:",
|
||||
"get-port": "catalog:",
|
||||
"html-minifier-terser": "catalog:",
|
||||
"nitropack": "catalog:",
|
||||
"resolve.exports": "catalog:",
|
||||
"vite-plugin-pwa": "catalog:",
|
||||
"vite-plugin-vue-devtools": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pnpm/workspace.read-manifest": "catalog:",
|
||||
"@types/archiver": "catalog:",
|
||||
"@types/html-minifier-terser": "catalog:",
|
||||
"@vitejs/plugin-vue": "catalog:",
|
||||
"@vitejs/plugin-vue-jsx": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"dotenv": "catalog:",
|
||||
"rollup-plugin-visualizer": "catalog:",
|
||||
"sass": "catalog:",
|
||||
"sass-embedded": "catalog:",
|
||||
"unplugin-dts": "catalog:",
|
||||
"vite": "catalog:",
|
||||
"vite-plugin-compression": "catalog:",
|
||||
"vite-plugin-lazy-import": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import type { CSSOptions, UserConfig } from 'vite';
|
||||
|
||||
import type { DefineApplicationOptions } from '../typing';
|
||||
|
||||
import path, { relative } from 'node:path';
|
||||
|
||||
import { findMonorepoRoot } from '@vben/node-utils';
|
||||
|
||||
import { NodePackageImporter } from 'sass-embedded';
|
||||
import { defineConfig, loadEnv, mergeConfig } from 'vite';
|
||||
|
||||
import { defaultImportmapOptions, getDefaultPwaOptions } from '../options';
|
||||
import { loadApplicationPlugins } from '../plugins';
|
||||
import { loadAndConvertEnv } from '../utils/env';
|
||||
import { getCommonConfig } from './common';
|
||||
|
||||
function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) {
|
||||
return defineConfig(async (config) => {
|
||||
const options = await userConfigPromise?.(config);
|
||||
const { appTitle, base, port, ...envConfig } = await loadAndConvertEnv();
|
||||
const { command, mode } = config;
|
||||
const { application = {}, vite = {} } = options || {};
|
||||
const root = process.cwd();
|
||||
const isBuild = command === 'build';
|
||||
const env = loadEnv(mode, root);
|
||||
|
||||
const plugins = await loadApplicationPlugins({
|
||||
archiver: true,
|
||||
archiverPluginOptions: {},
|
||||
compress: false,
|
||||
compressTypes: ['brotli', 'gzip'],
|
||||
devtools: true,
|
||||
env,
|
||||
extraAppConfig: true,
|
||||
html: true,
|
||||
i18n: true,
|
||||
importmapOptions: defaultImportmapOptions,
|
||||
injectAppLoading: true,
|
||||
injectMetadata: true,
|
||||
isBuild,
|
||||
license: true,
|
||||
mode,
|
||||
nitroMock: !isBuild,
|
||||
nitroMockOptions: {},
|
||||
print: !isBuild,
|
||||
printInfoMap: {
|
||||
'Vben Admin Docs': 'https://doc.vben.pro',
|
||||
},
|
||||
pwa: true,
|
||||
pwaOptions: getDefaultPwaOptions(appTitle),
|
||||
vxeTableLazyImport: true,
|
||||
...envConfig,
|
||||
...application,
|
||||
});
|
||||
|
||||
const { injectGlobalScss = true } = application;
|
||||
|
||||
const applicationConfig: UserConfig = {
|
||||
base,
|
||||
build: {
|
||||
rolldownOptions: {
|
||||
output: {
|
||||
assetFileNames: '[ext]/[name]-[hash].[ext]',
|
||||
chunkFileNames: 'js/[name]-[hash].js',
|
||||
entryFileNames: 'jse/index-[name]-[hash].js',
|
||||
minify: isBuild
|
||||
? {
|
||||
compress: {
|
||||
dropDebugger: true,
|
||||
},
|
||||
}
|
||||
: false,
|
||||
},
|
||||
},
|
||||
target: 'es2015',
|
||||
},
|
||||
css: createCssOptions(injectGlobalScss),
|
||||
plugins,
|
||||
server: {
|
||||
host: true,
|
||||
port,
|
||||
warmup: {
|
||||
// 预热文件
|
||||
clientFiles: [
|
||||
'./index.html',
|
||||
'./src/bootstrap.ts',
|
||||
'./src/{views,layouts,router,store,api,adapter}/*',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mergedCommonConfig = mergeConfig(
|
||||
await getCommonConfig(),
|
||||
applicationConfig,
|
||||
);
|
||||
return mergeConfig(mergedCommonConfig, vite);
|
||||
});
|
||||
}
|
||||
|
||||
function createCssOptions(injectGlobalScss = true): CSSOptions {
|
||||
const root = findMonorepoRoot();
|
||||
return {
|
||||
preprocessorOptions: injectGlobalScss
|
||||
? {
|
||||
scss: {
|
||||
additionalData: (content: string, filepath: string) => {
|
||||
const relativePath = relative(root, filepath);
|
||||
// apps下的包注入全局样式
|
||||
if (relativePath.startsWith(`apps${path.sep}`)) {
|
||||
return `@use "@vben/styles/global" as *;\n${content}`;
|
||||
}
|
||||
return content;
|
||||
},
|
||||
// api: 'modern',
|
||||
importers: [new NodePackageImporter()],
|
||||
},
|
||||
}
|
||||
: {},
|
||||
};
|
||||
}
|
||||
|
||||
export { defineApplicationConfig };
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { UserConfig } from 'vite';
|
||||
|
||||
async function getCommonConfig(): Promise<UserConfig> {
|
||||
return {
|
||||
build: {
|
||||
chunkSizeWarningLimit: 2000,
|
||||
reportCompressedSize: false,
|
||||
sourcemap: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { getCommonConfig };
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { DefineConfig, VbenViteConfig } from '../typing';
|
||||
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { defineApplicationConfig } from './application';
|
||||
import { defineLibraryConfig } from './library';
|
||||
|
||||
export * from './application';
|
||||
export * from './library';
|
||||
|
||||
function defineConfig(
|
||||
userConfigPromise?: DefineConfig,
|
||||
type: 'application' | 'auto' | 'library' = 'auto',
|
||||
): VbenViteConfig {
|
||||
let projectType = type;
|
||||
|
||||
// 根据包是否存在 index.html,自动判断类型
|
||||
if (projectType === 'auto') {
|
||||
const htmlPath = join(process.cwd(), 'index.html');
|
||||
projectType = existsSync(htmlPath) ? 'application' : 'library';
|
||||
}
|
||||
|
||||
switch (projectType) {
|
||||
case 'application': {
|
||||
return defineApplicationConfig(userConfigPromise);
|
||||
}
|
||||
case 'library': {
|
||||
return defineLibraryConfig(userConfigPromise);
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unsupported project type: ${projectType}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { defineConfig };
|
||||
@@ -0,0 +1,59 @@
|
||||
import type { ConfigEnv, UserConfig } from 'vite';
|
||||
|
||||
import type { DefineLibraryOptions } from '../typing';
|
||||
|
||||
import { readPackageJSON } from '@vben/node-utils';
|
||||
|
||||
import { defineConfig, mergeConfig } from 'vite';
|
||||
|
||||
import { loadLibraryPlugins } from '../plugins';
|
||||
import { getCommonConfig } from './common';
|
||||
|
||||
function defineLibraryConfig(userConfigPromise?: DefineLibraryOptions) {
|
||||
return defineConfig(async (config: ConfigEnv) => {
|
||||
const options = await userConfigPromise?.(config);
|
||||
const { command, mode } = config;
|
||||
const { library = {}, vite = {} } = options || {};
|
||||
const root = process.cwd();
|
||||
const isBuild = command === 'build';
|
||||
|
||||
const plugins = await loadLibraryPlugins({
|
||||
dts: false,
|
||||
injectMetadata: true,
|
||||
isBuild,
|
||||
mode,
|
||||
...library,
|
||||
});
|
||||
|
||||
const { dependencies = {}, peerDependencies = {} } =
|
||||
await readPackageJSON(root);
|
||||
|
||||
const externalPackages = [
|
||||
...Object.keys(dependencies),
|
||||
...Object.keys(peerDependencies),
|
||||
];
|
||||
|
||||
const packageConfig: UserConfig = {
|
||||
build: {
|
||||
lib: {
|
||||
entry: 'src/index.ts',
|
||||
fileName: () => 'index.mjs',
|
||||
formats: ['es'],
|
||||
},
|
||||
rolldownOptions: {
|
||||
external: (id) => {
|
||||
return externalPackages.some(
|
||||
(pkg) => id === pkg || id.startsWith(`${pkg}/`),
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins,
|
||||
};
|
||||
const commonConfig = await getCommonConfig();
|
||||
const mergedConmonConfig = mergeConfig(commonConfig, packageConfig);
|
||||
return mergeConfig(mergedConmonConfig, vite);
|
||||
});
|
||||
}
|
||||
|
||||
export { defineLibraryConfig };
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from './config';
|
||||
export * from './options';
|
||||
export * from './plugins';
|
||||
export type * from './typing';
|
||||
export { loadAndConvertEnv } from './utils/env';
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Options as PwaPluginOptions } from 'vite-plugin-pwa';
|
||||
|
||||
import type { ImportmapPluginOptions } from './typing';
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
|
||||
const getDefaultPwaOptions = (name: string): Partial<PwaPluginOptions> => ({
|
||||
manifest: {
|
||||
description:
|
||||
'Vben Admin is a modern admin dashboard template based on Vue 3. ',
|
||||
icons: [
|
||||
{
|
||||
sizes: '192x192',
|
||||
src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-192.png',
|
||||
type: 'image/png',
|
||||
},
|
||||
{
|
||||
sizes: '512x512',
|
||||
src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-512.png',
|
||||
type: 'image/png',
|
||||
},
|
||||
],
|
||||
name: `${name}${isDevelopment ? ' dev' : ''}`,
|
||||
short_name: `${name}${isDevelopment ? ' dev' : ''}`,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* importmap CDN 暂时不开启,因为有些包不支持,且网络不稳定
|
||||
*/
|
||||
const defaultImportmapOptions: ImportmapPluginOptions = {
|
||||
// 通过 Importmap CDN 方式引入,
|
||||
// 目前只有esm.sh源兼容性好一点,jspm.io对于 esm 入口要求高
|
||||
defaultProvider: 'esm.sh',
|
||||
importmap: [
|
||||
{ name: 'vue' },
|
||||
{ name: 'pinia' },
|
||||
{ name: 'vue-router' },
|
||||
// { name: 'vue-i18n' },
|
||||
{ name: 'dayjs' },
|
||||
{ name: 'vue-demi' },
|
||||
],
|
||||
};
|
||||
|
||||
export { defaultImportmapOptions, getDefaultPwaOptions };
|
||||
@@ -0,0 +1,76 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import type { ArchiverPluginOptions } from '../typing';
|
||||
|
||||
import fs from 'node:fs';
|
||||
import fsp from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { ZipArchive } from 'archiver';
|
||||
|
||||
export const viteArchiverPlugin = (
|
||||
options: ArchiverPluginOptions = {},
|
||||
): PluginOption => {
|
||||
return {
|
||||
apply: 'build',
|
||||
closeBundle: {
|
||||
handler() {
|
||||
const { name = 'dist', outputDir = '.' } = options;
|
||||
|
||||
setTimeout(async () => {
|
||||
const folderToZip = 'dist';
|
||||
|
||||
const zipOutputDir = join(process.cwd(), outputDir);
|
||||
const zipOutputPath = join(zipOutputDir, `${name}.zip`);
|
||||
try {
|
||||
await fsp.mkdir(zipOutputDir, { recursive: true });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
await zipFolder(folderToZip, zipOutputPath);
|
||||
console.log(`Folder has been zipped to: ${zipOutputPath}`);
|
||||
} catch (error) {
|
||||
console.error('Error zipping folder:', error);
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
order: 'post',
|
||||
},
|
||||
enforce: 'post',
|
||||
name: 'vite:archiver',
|
||||
};
|
||||
};
|
||||
|
||||
async function zipFolder(
|
||||
folderPath: string,
|
||||
outputPath: string,
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const output = fs.createWriteStream(outputPath);
|
||||
|
||||
const archive = new ZipArchive({
|
||||
zlib: { level: 9 }, // 设置压缩级别为 9 以实现最高压缩率
|
||||
});
|
||||
|
||||
output.on('close', () => {
|
||||
console.log(
|
||||
`ZIP file created: ${outputPath} (${archive.pointer()} total bytes)`,
|
||||
);
|
||||
resolve();
|
||||
});
|
||||
|
||||
archive.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
archive.pipe(output);
|
||||
|
||||
// 使用 directory 方法以流的方式压缩文件夹,减少内存消耗
|
||||
archive.directory(folderPath, false);
|
||||
|
||||
// 流式处理完成
|
||||
archive.finalize();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { viteCssLayerPlugin } from './css-layer';
|
||||
|
||||
interface FakeAsset {
|
||||
fileName: string;
|
||||
source: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
function createBundle(assets: FakeAsset[]) {
|
||||
return Object.fromEntries(assets.map((asset) => [asset.fileName, asset]));
|
||||
}
|
||||
|
||||
const LAYER_ORDER_STATEMENT =
|
||||
'@layer properties, theme, base, ant, antd, el, td, components, utilities;';
|
||||
|
||||
describe('viteCssLayerPlugin', () => {
|
||||
it('wraps matching css modules in the target layer', () => {
|
||||
const plugin = viteCssLayerPlugin({
|
||||
layerName: 'el',
|
||||
packageName: 'element-plus',
|
||||
});
|
||||
const transform = plugin.transform;
|
||||
|
||||
if (typeof transform !== 'function') return;
|
||||
|
||||
const result = transform.call(
|
||||
undefined as never,
|
||||
'.el-button { color: red; }',
|
||||
'/node_modules/element-plus/es/components/button/style/css/index.css?used',
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
code: '@layer el {\n.el-button { color: red; }\n}',
|
||||
map: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not wrap css modules outside the target package', () => {
|
||||
const plugin = viteCssLayerPlugin({
|
||||
layerName: 'el',
|
||||
packageName: 'element-plus',
|
||||
});
|
||||
const transform = plugin.transform;
|
||||
|
||||
if (typeof transform !== 'function') return;
|
||||
|
||||
const result = transform.call(
|
||||
undefined as never,
|
||||
'.local { color: red; }',
|
||||
'/src/views/home/index.css',
|
||||
);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('injects the layer order statement into matching css assets', () => {
|
||||
const plugin = viteCssLayerPlugin({
|
||||
layerName: 'el',
|
||||
packageName: 'element-plus',
|
||||
});
|
||||
const generateBundle = plugin.generateBundle;
|
||||
|
||||
if (typeof generateBundle !== 'function') return;
|
||||
|
||||
const bundle = createBundle([
|
||||
{
|
||||
fileName: 'element-x.css',
|
||||
source: '@layer el{.el-button{color:red}}',
|
||||
type: 'asset',
|
||||
},
|
||||
{
|
||||
fileName: 'theme-x.css',
|
||||
source: '.local { color: blue; }',
|
||||
type: 'asset',
|
||||
},
|
||||
]);
|
||||
|
||||
generateBundle.call(undefined as never, {} as never, bundle);
|
||||
|
||||
expect(bundle['element-x.css'].source).toBe(
|
||||
`${LAYER_ORDER_STATEMENT}\n@layer el{.el-button{color:red}}`,
|
||||
);
|
||||
expect(bundle['theme-x.css'].source).toBe('.local { color: blue; }');
|
||||
});
|
||||
|
||||
it('does not inject the statement twice when already present', () => {
|
||||
const plugin = viteCssLayerPlugin({
|
||||
layerName: 'el',
|
||||
packageName: 'element-plus',
|
||||
});
|
||||
const generateBundle = plugin.generateBundle;
|
||||
|
||||
if (typeof generateBundle !== 'function') return;
|
||||
|
||||
const bundle = createBundle([
|
||||
{
|
||||
fileName: 'element-x.css',
|
||||
source: `${LAYER_ORDER_STATEMENT}\n@layer el{.el-button{color:red}}`,
|
||||
type: 'asset',
|
||||
},
|
||||
]);
|
||||
|
||||
generateBundle.call(undefined as never, {} as never, bundle);
|
||||
|
||||
expect(bundle['element-x.css'].source).toBe(
|
||||
`${LAYER_ORDER_STATEMENT}\n@layer el{.el-button{color:red}}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('injects the statement even when the asset starts with a BOM', () => {
|
||||
const plugin = viteCssLayerPlugin({
|
||||
layerName: 'el',
|
||||
packageName: 'element-plus',
|
||||
});
|
||||
const generateBundle = plugin.generateBundle;
|
||||
|
||||
if (typeof generateBundle !== 'function') return;
|
||||
|
||||
const bundle = createBundle([
|
||||
{
|
||||
fileName: 'element-x.css',
|
||||
source: `\uFEFF@layer el{.el-button{color:red}}`,
|
||||
type: 'asset',
|
||||
},
|
||||
]);
|
||||
|
||||
generateBundle.call(undefined as never, {} as never, bundle);
|
||||
|
||||
expect(bundle['element-x.css'].source).toBe(
|
||||
`${LAYER_ORDER_STATEMENT}\n\uFEFF@layer el{.el-button{color:red}}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores non-css assets', () => {
|
||||
const plugin = viteCssLayerPlugin({
|
||||
layerName: 'el',
|
||||
packageName: 'element-plus',
|
||||
});
|
||||
const generateBundle = plugin.generateBundle;
|
||||
|
||||
if (typeof generateBundle !== 'function') return;
|
||||
|
||||
const bundle = createBundle([
|
||||
{
|
||||
fileName: 'element-x.js',
|
||||
source: '@layer el{.el-button{color:red}}',
|
||||
type: 'chunk',
|
||||
},
|
||||
]);
|
||||
|
||||
generateBundle.call(undefined as never, {} as never, bundle);
|
||||
|
||||
expect(bundle['element-x.js'].source).toBe(
|
||||
'@layer el{.el-button{color:red}}',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
interface CssLayerRule {
|
||||
/** 层名,需在 css 中先于 @import 'tailwindcss' 声明层顺序(见 internal/tailwind-config/theme.css) */
|
||||
layerName: string;
|
||||
/** 需要包层的包名(匹配该包在 node_modules 下的 css 模块 id) */
|
||||
packageName: string;
|
||||
}
|
||||
|
||||
function escapeRegExp(value: string): string {
|
||||
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 与 internal/tailwind-config/src/theme.css 中的层顺序保持一致:
|
||||
* properties(Tailwind 的 --tw-* 回退变量层) < preflight(base) <
|
||||
* UI 库组件样式(antd / el / td) < Tailwind 工具类。
|
||||
* 该声明必须在打包后注入:生产构建下 css 按 chunk 注入,若组件库的 css
|
||||
* chunk 先于含层顺序声明的 theme.css 加载,层名会先被注册为最低优先级,
|
||||
* 导致 Tailwind base/utilities 覆盖组件样式。打包后在每个含组件库层的
|
||||
* css 产物顶部补上该声明,无论 chunk 加载顺序如何,层优先级都正确
|
||||
* (theme.css 先加载时该声明是幂等的 no-op)。
|
||||
* 注意 properties 必须排在首位:它是 Tailwind 为不支持 @property 的旧
|
||||
* 浏览器准备的 --tw-* 回退变量层,必须始终是最低优先级,否则其回退声明
|
||||
* 会覆盖工具类写入的变量。
|
||||
*/
|
||||
const LAYER_ORDER_STATEMENT =
|
||||
'@layer properties, theme, base, ant, antd, el, td, components, utilities;';
|
||||
|
||||
/**
|
||||
* 把指定包内的 css 包进 @layer:
|
||||
* 组件库的 css 是无层样式,无层样式在级联中永远压过 @layer 内的 Tailwind 工具类;
|
||||
* 包层后由 theme.css 的层声明决定顺序(utilities 排在组件库层之后),
|
||||
* 使 Tailwind 工具类可以覆盖组件库样式。
|
||||
*
|
||||
* 注意:层顺序声明不能在 transform 阶段随包层 css 一起输出——rolldown-vite
|
||||
* 的 css 合并会将「层顺序声明 + @layer 规则」形式的模块拆层(规则被脱层),
|
||||
* 且产物压缩阶段也会丢弃该声明。因此改在 generateBundle 阶段对最终 css
|
||||
* 产物统一注入(见 LAYER_ORDER_STATEMENT)。
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* plugins: [viteCssLayerPlugin({ packageName: 'element-plus', layerName: 'el' })]
|
||||
* ```
|
||||
*/
|
||||
export function viteCssLayerPlugin(
|
||||
rules: CssLayerRule | CssLayerRule[],
|
||||
): Plugin {
|
||||
const list = Array.isArray(rules) ? rules : [rules];
|
||||
const matchers = list.map(({ layerName, packageName }) => ({
|
||||
layerName,
|
||||
regex: new RegExp(`${escapeRegExp(packageName)}[\\\\/].+\\.css$`, 'i'),
|
||||
}));
|
||||
return {
|
||||
name: 'vite-plugin-css-layer',
|
||||
enforce: 'pre',
|
||||
transform(code, id) {
|
||||
const [file] = id.split('?', 1);
|
||||
if (!file) return;
|
||||
const matched = matchers.find((m) => m.regex.test(file));
|
||||
if (matched) {
|
||||
return { code: `@layer ${matched.layerName} {\n${code}\n}`, map: null };
|
||||
}
|
||||
},
|
||||
generateBundle(_options, bundle) {
|
||||
for (const file of Object.values(bundle)) {
|
||||
if (file.type !== 'asset' || !file.fileName.endsWith('.css')) {
|
||||
continue;
|
||||
}
|
||||
const css = file.source.toString();
|
||||
const matched = matchers.some((m) =>
|
||||
css.includes(`@layer ${m.layerName}`),
|
||||
);
|
||||
if (!matched) {
|
||||
continue;
|
||||
}
|
||||
// 先去除可能的 BOM 再判断,避免重复注入层顺序声明
|
||||
const hasStatement = css
|
||||
.replace(/^\uFEFF/, '')
|
||||
.trimStart()
|
||||
.startsWith(LAYER_ORDER_STATEMENT);
|
||||
if (hasStatement) {
|
||||
continue;
|
||||
}
|
||||
file.source = `${LAYER_ORDER_STATEMENT}\n${css}`;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
function viteDayjsPlugin(): Plugin {
|
||||
return {
|
||||
name: 'vite-dayjs-plugin',
|
||||
enforce: 'pre',
|
||||
async resolveId(source, importer, options) {
|
||||
// 1) 已经使用了 dayjs/esm 的不处理
|
||||
if (source.startsWith('dayjs/esm')) return null;
|
||||
|
||||
// 2) 根入口:dayjs -> dayjs/esm
|
||||
if (source === 'dayjs') {
|
||||
return await this.resolve('dayjs/esm', importer, {
|
||||
skipSelf: true,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
// 3) 插件入口的多种写法
|
||||
// - dayjs/plugin/xxx.js -> dayjs/esm/plugin/xxx/index.js
|
||||
// - dayjs/plugin/xxx -> dayjs/esm/plugin/xxx
|
||||
const pluginWithJs = source.match(/^dayjs\/plugin\/([^/]+)\.js$/);
|
||||
if (pluginWithJs) {
|
||||
const target = `dayjs/esm/plugin/${pluginWithJs[1]}/index.js`;
|
||||
return await this.resolve(target, importer, {
|
||||
skipSelf: true,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
const pluginBare = source.match(/^dayjs\/plugin\/([^/]+)$/);
|
||||
if (pluginBare) {
|
||||
const target = `dayjs/esm/plugin/${pluginBare[1]}`;
|
||||
return await this.resolve(target, importer, {
|
||||
skipSelf: true,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
// 4) 处理多语言包
|
||||
// - dayjs/locale/xxx.js -> dayjs/esm/locale/xxx.js
|
||||
const localeWithJs = source.match(/^dayjs\/locale\/([^/]+)\.js$/);
|
||||
if (localeWithJs) {
|
||||
const target = `dayjs/esm/locale/${localeWithJs[1]}.js`;
|
||||
return await this.resolve(target, importer, {
|
||||
skipSelf: true,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
const localeBare = source.match(/^dayjs\/locale\/([^/]+)$/);
|
||||
if (localeBare) {
|
||||
const target = `dayjs/esm/locale/${localeBare[1]}`;
|
||||
return await this.resolve(target, importer, {
|
||||
skipSelf: true,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
config() {
|
||||
return {
|
||||
optimizeDeps: {
|
||||
exclude: ['dayjs'],
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
export { viteDayjsPlugin };
|
||||
@@ -0,0 +1,92 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import {
|
||||
colors,
|
||||
generatorContentHash,
|
||||
readPackageJSON,
|
||||
} from '@vben/node-utils';
|
||||
|
||||
import { loadEnv } from '../utils/env';
|
||||
|
||||
interface PluginOptions {
|
||||
isBuild: boolean;
|
||||
root: string;
|
||||
}
|
||||
|
||||
const GLOBAL_CONFIG_FILE_NAME = '_app-config';
|
||||
const VBEN_ADMIN_PRO_APP_CONF = '_VBEN_ADMIN_PRO_APP_CONF_';
|
||||
|
||||
/**
|
||||
* 用于将配置文件抽离出来并注入到项目中
|
||||
* @returns
|
||||
*/
|
||||
|
||||
async function viteExtraAppConfigPlugin({
|
||||
isBuild,
|
||||
root,
|
||||
}: PluginOptions): Promise<PluginOption | undefined> {
|
||||
let publicPath: string;
|
||||
let source: string;
|
||||
let hash: string;
|
||||
|
||||
if (!isBuild) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { version = '' } = await readPackageJSON(root);
|
||||
|
||||
return {
|
||||
async configResolved(config) {
|
||||
publicPath = ensureTrailingSlash(config.base);
|
||||
source = await getConfigSource();
|
||||
hash = generatorContentHash(source, 8);
|
||||
},
|
||||
async generateBundle() {
|
||||
try {
|
||||
this.emitFile({
|
||||
fileName: `${GLOBAL_CONFIG_FILE_NAME}-${version}-${hash}.js`,
|
||||
source,
|
||||
type: 'asset',
|
||||
});
|
||||
|
||||
console.log(colors.cyan(`✨configuration file is build successfully!`));
|
||||
} catch (error) {
|
||||
console.log(
|
||||
colors.red(
|
||||
`configuration file configuration file failed to package:\n${error}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
name: 'vite:extra-app-config',
|
||||
async transformIndexHtml(html) {
|
||||
const appConfigSrc = `${publicPath}${GLOBAL_CONFIG_FILE_NAME}-${version}-${hash}.js`;
|
||||
|
||||
return {
|
||||
html,
|
||||
tags: [{ attrs: { src: appConfigSrc }, tag: 'script' }],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function getConfigSource() {
|
||||
const config = await loadEnv();
|
||||
const windowVariable = `window.${VBEN_ADMIN_PRO_APP_CONF}`;
|
||||
// 确保变量不会被修改
|
||||
let source = `${windowVariable}=${JSON.stringify(config)};`;
|
||||
source += `
|
||||
Object.freeze(${windowVariable});
|
||||
Object.defineProperty(window, "${VBEN_ADMIN_PRO_APP_CONF}", {
|
||||
configurable: false,
|
||||
writable: false,
|
||||
});
|
||||
`.replaceAll(/\s/g, '');
|
||||
return source;
|
||||
}
|
||||
|
||||
function ensureTrailingSlash(path: string) {
|
||||
return path.endsWith('/') ? path : `${path}/`;
|
||||
}
|
||||
|
||||
export { viteExtraAppConfigPlugin };
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { ResolvedConfig } from 'vite';
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
FORM_FIELD_SLOT_MIGRATION_WARNING,
|
||||
viteFormFieldSlotMigrationWarningPlugin,
|
||||
} from './form-field-slot-migration-warning';
|
||||
|
||||
describe('form field slot migration warning plugin', () => {
|
||||
it('warns in serve mode and injects the same browser message', () => {
|
||||
const plugin = viteFormFieldSlotMigrationWarningPlugin();
|
||||
const warning = vi.fn();
|
||||
|
||||
expect(plugin.apply).toBe('serve');
|
||||
expect(plugin.configResolved).toBeTypeOf('function');
|
||||
if (typeof plugin.configResolved !== 'function') return;
|
||||
|
||||
plugin.configResolved({ logger: { warn: warning } } as ResolvedConfig);
|
||||
|
||||
expect(warning).toHaveBeenCalledOnce();
|
||||
expect(warning).toHaveBeenCalledWith(FORM_FIELD_SLOT_MIGRATION_WARNING);
|
||||
expect(plugin.transformIndexHtml).toBeTypeOf('function');
|
||||
if (typeof plugin.transformIndexHtml !== 'function') return;
|
||||
|
||||
const result = plugin.transformIndexHtml('', {} as never);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
attrs: {
|
||||
'data-vben-form-field-slot-migration-warning': '',
|
||||
},
|
||||
children: `console.warn(${JSON.stringify(FORM_FIELD_SLOT_MIGRATION_WARNING)});`,
|
||||
injectTo: 'body',
|
||||
tag: 'script',
|
||||
},
|
||||
]);
|
||||
expect(FORM_FIELD_SLOT_MIGRATION_WARNING).toContain('`v-bind="slotProps"`');
|
||||
expect(FORM_FIELD_SLOT_MIGRATION_WARNING).toContain(
|
||||
'`v-bind="slotProps.componentProps"`',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
const FORM_FIELD_SLOT_MIGRATION_WARNING =
|
||||
'[Vben Form] BREAKING CHANGE: Named field slot control bindings moved to `slotProps.componentProps`. Replace `v-bind="slotProps"` with `v-bind="slotProps.componentProps"`. See https://doc.vben.pro/components/common-ui/vben-form.html';
|
||||
|
||||
function viteFormFieldSlotMigrationWarningPlugin(): Plugin {
|
||||
return {
|
||||
apply: 'serve',
|
||||
configResolved(config) {
|
||||
config.logger.warn(FORM_FIELD_SLOT_MIGRATION_WARNING);
|
||||
},
|
||||
name: 'vite:form-field-slot-migration-warning',
|
||||
transformIndexHtml() {
|
||||
return [
|
||||
{
|
||||
attrs: {
|
||||
'data-vben-form-field-slot-migration-warning': '',
|
||||
},
|
||||
children: `console.warn(${JSON.stringify(FORM_FIELD_SLOT_MIGRATION_WARNING)});`,
|
||||
injectTo: 'body',
|
||||
tag: 'script',
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
FORM_FIELD_SLOT_MIGRATION_WARNING,
|
||||
viteFormFieldSlotMigrationWarningPlugin,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { Options as HtmlMinifierOptions } from 'html-minifier-terser';
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import { minify } from 'html-minifier-terser';
|
||||
|
||||
const HTML_MINIFY_OPTIONS = {
|
||||
collapseWhitespace: true,
|
||||
minifyCSS: true,
|
||||
minifyJS: true,
|
||||
removeComments: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
useShortDoctype: true,
|
||||
} as const;
|
||||
|
||||
function viteHtmlPlugin(options: HtmlMinifierOptions = {}): PluginOption {
|
||||
return {
|
||||
name: 'vben-native-html',
|
||||
transformIndexHtml: {
|
||||
order: 'post',
|
||||
async handler(html, ctx) {
|
||||
if (!ctx.bundle) {
|
||||
return html;
|
||||
}
|
||||
return await minify(html, {
|
||||
...HTML_MINIFY_OPTIONS,
|
||||
...options,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { viteHtmlPlugin };
|
||||
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* 参考 https://github.com/jspm/vite-plugin-jspm,调整为需要的功能
|
||||
*/
|
||||
import type { GeneratorOptions } from '@jspm/generator';
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
import { Generator } from '@jspm/generator';
|
||||
import { load } from 'cheerio';
|
||||
import { minify } from 'html-minifier-terser';
|
||||
|
||||
const DEFAULT_PROVIDER = 'jspm.io';
|
||||
|
||||
type pluginOptions = GeneratorOptions & {
|
||||
debug?: boolean;
|
||||
defaultProvider?: 'esm.sh' | 'jsdelivr' | 'jspm.io';
|
||||
importmap?: Array<{ name: string; range?: string }>;
|
||||
};
|
||||
|
||||
// async function getLatestVersionOfShims() {
|
||||
// const result = await fetch('https://ga.jspm.io/npm:es-module-shims');
|
||||
// const version = result.text();
|
||||
// return version;
|
||||
// }
|
||||
|
||||
async function getShimsUrl(provide: string) {
|
||||
// const version = await getLatestVersionOfShims();
|
||||
const version = '1.10.0';
|
||||
|
||||
const shimsSubpath = `dist/es-module-shims.js`;
|
||||
const providerShimsMap: Record<string, string> = {
|
||||
'esm.sh': `https://esm.sh/es-module-shims@${version}/${shimsSubpath}`,
|
||||
// unpkg: `https://unpkg.com/es-module-shims@${version}/${shimsSubpath}`,
|
||||
jsdelivr: `https://cdn.jsdelivr.net/npm/es-module-shims@${version}/${shimsSubpath}`,
|
||||
|
||||
// 下面两个CDN不稳定,暂时不用
|
||||
'jspm.io': `https://ga.jspm.io/npm:es-module-shims@${version}/${shimsSubpath}`,
|
||||
};
|
||||
|
||||
return providerShimsMap[provide] || providerShimsMap[DEFAULT_PROVIDER];
|
||||
}
|
||||
|
||||
let generator: Generator;
|
||||
|
||||
async function viteImportMapPlugin(
|
||||
pluginOptions?: pluginOptions,
|
||||
): Promise<Plugin[]> {
|
||||
const { importmap } = pluginOptions || {};
|
||||
|
||||
let isSSR = false;
|
||||
let isBuild = false;
|
||||
let installed = false;
|
||||
let installError: Error | null = null;
|
||||
|
||||
const options: pluginOptions = Object.assign(
|
||||
{},
|
||||
{
|
||||
debug: false,
|
||||
defaultProvider: 'jspm.io',
|
||||
env: ['production', 'browser', 'module'],
|
||||
importmap: [],
|
||||
},
|
||||
pluginOptions,
|
||||
);
|
||||
|
||||
generator = new Generator({
|
||||
...options,
|
||||
baseUrl: process.cwd(),
|
||||
});
|
||||
|
||||
if (options?.debug) {
|
||||
(async () => {
|
||||
for await (const { message, type } of generator.logStream()) {
|
||||
console.log(`${type}: ${message}`);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
const imports = options.inputMap?.imports ?? {};
|
||||
const scopes = options.inputMap?.scopes ?? {};
|
||||
const firstLayerKeys = Object.keys(scopes);
|
||||
const inputMapScopes: string[] = [];
|
||||
firstLayerKeys.forEach((key) => {
|
||||
inputMapScopes.push(...Object.keys(scopes[key] || {}));
|
||||
});
|
||||
const inputMapImports = Object.keys(imports);
|
||||
|
||||
const allDepNames: string[] = [
|
||||
...(importmap?.map((item) => item.name) || []),
|
||||
...inputMapImports,
|
||||
...inputMapScopes,
|
||||
];
|
||||
const depNames = new Set<string>(allDepNames);
|
||||
|
||||
const installDeps = importmap?.map((item) => ({
|
||||
range: item.range,
|
||||
target: item.name,
|
||||
}));
|
||||
|
||||
return [
|
||||
{
|
||||
async config(_, { command, isSsrBuild }) {
|
||||
isBuild = command === 'build';
|
||||
isSSR = !!isSsrBuild;
|
||||
},
|
||||
enforce: 'pre',
|
||||
name: 'importmap:external',
|
||||
resolveId(id) {
|
||||
if (isSSR || !isBuild) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!depNames.has(id)) {
|
||||
return null;
|
||||
}
|
||||
return { external: true, id };
|
||||
},
|
||||
},
|
||||
{
|
||||
enforce: 'post',
|
||||
name: 'importmap:install',
|
||||
async resolveId() {
|
||||
if (isSSR || !isBuild || installed) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
installed = true;
|
||||
await Promise.allSettled(
|
||||
(installDeps || []).map((dep) => generator.install(dep)),
|
||||
);
|
||||
} catch (error: any) {
|
||||
installError = error;
|
||||
installed = false;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
buildEnd() {
|
||||
// 未生成importmap时,抛出错误,防止被turbo缓存
|
||||
if (!installed && !isSSR) {
|
||||
installError && console.error(installError);
|
||||
throw new Error('Importmap installation failed.');
|
||||
}
|
||||
},
|
||||
enforce: 'post',
|
||||
name: 'importmap:html',
|
||||
transformIndexHtml: {
|
||||
async handler(html) {
|
||||
if (isSSR || !isBuild) {
|
||||
return html;
|
||||
}
|
||||
|
||||
const importmapJson = generator.getMap();
|
||||
|
||||
if (!importmapJson) {
|
||||
return html;
|
||||
}
|
||||
|
||||
const esModuleShimsSrc = await getShimsUrl(
|
||||
options.defaultProvider || DEFAULT_PROVIDER,
|
||||
);
|
||||
|
||||
const resultHtml = await injectShimsToHtml(
|
||||
html,
|
||||
esModuleShimsSrc || '',
|
||||
);
|
||||
html = await minify(resultHtml || html, {
|
||||
collapseWhitespace: true,
|
||||
minifyCSS: true,
|
||||
minifyJS: true,
|
||||
removeComments: false,
|
||||
});
|
||||
|
||||
return {
|
||||
html,
|
||||
tags: [
|
||||
{
|
||||
attrs: {
|
||||
type: 'importmap',
|
||||
},
|
||||
injectTo: 'head-prepend',
|
||||
tag: 'script',
|
||||
children: `${JSON.stringify(importmapJson)}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
order: 'post',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async function injectShimsToHtml(html: string, esModuleShimUrl: string) {
|
||||
const $ = load(html);
|
||||
|
||||
const $script = $(`script[type='module']`);
|
||||
|
||||
if (!$script) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = $script.attr('src');
|
||||
|
||||
$script.removeAttr('type');
|
||||
$script.removeAttr('crossorigin');
|
||||
$script.removeAttr('src');
|
||||
$script.html(`
|
||||
if (!HTMLScriptElement.supports || !HTMLScriptElement.supports('importmap')) {
|
||||
self.importShim = function () {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
document.head.appendChild(
|
||||
Object.assign(document.createElement('script'), {
|
||||
src: '${esModuleShimUrl}',
|
||||
crossorigin: 'anonymous',
|
||||
async: true,
|
||||
onload() {
|
||||
if (!importShim.$proxy) {
|
||||
resolve(importShim);
|
||||
} else {
|
||||
reject(new Error('No globalThis.importShim found:' + esModuleShimUrl));
|
||||
}
|
||||
},
|
||||
onerror(error) {
|
||||
reject(error);
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
importShim.$proxy = true;
|
||||
return promise.then((importShim) => importShim(...arguments));
|
||||
};
|
||||
}
|
||||
|
||||
var modules = ['${entry}'];
|
||||
typeof importShim === 'function'
|
||||
? modules.forEach((moduleName) => importShim(moduleName))
|
||||
: modules.forEach((moduleName) => import(moduleName));
|
||||
`);
|
||||
$('body').after($script);
|
||||
$('head').remove(`script[type='module']`);
|
||||
return $.html();
|
||||
}
|
||||
|
||||
export { viteImportMapPlugin };
|
||||
@@ -0,0 +1,268 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import type {
|
||||
ApplicationPluginOptions,
|
||||
CommonPluginOptions,
|
||||
ConditionPlugin,
|
||||
LibraryPluginOptions,
|
||||
} from '../typing';
|
||||
|
||||
import viteVueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import viteVue from '@vitejs/plugin-vue';
|
||||
import viteVueJsx from '@vitejs/plugin-vue-jsx';
|
||||
import { visualizer as viteVisualizerPlugin } from 'rollup-plugin-visualizer';
|
||||
import viteDtsPlugin from 'unplugin-dts/vite';
|
||||
import viteCompressPlugin from 'vite-plugin-compression';
|
||||
import { VitePWA } from 'vite-plugin-pwa';
|
||||
import viteVueDevTools from 'vite-plugin-vue-devtools';
|
||||
|
||||
import { viteArchiverPlugin } from './archiver';
|
||||
import { viteDayjsPlugin } from './dayjs';
|
||||
import { viteExtraAppConfigPlugin } from './extra-app-config';
|
||||
import { viteFormFieldSlotMigrationWarningPlugin } from './form-field-slot-migration-warning';
|
||||
import { viteHtmlPlugin } from './html';
|
||||
import { viteImportMapPlugin } from './importmap';
|
||||
import { viteInjectAppLoadingPlugin } from './inject-app-loading';
|
||||
import { viteMetadataPlugin } from './inject-metadata';
|
||||
import { viteLicensePlugin } from './license';
|
||||
import { viteNitroMockPlugin } from './nitro-mock';
|
||||
import { vitePrintPlugin } from './print';
|
||||
import { viteTailwindReferencePlugin } from './tailwind-reference';
|
||||
import { viteVxeTableImportsPlugin } from './vxe-table';
|
||||
|
||||
/**
|
||||
* 获取条件成立的 vite 插件
|
||||
* @param conditionPlugins
|
||||
*/
|
||||
async function loadConditionPlugins(conditionPlugins: ConditionPlugin[]) {
|
||||
const plugins: PluginOption[] = [];
|
||||
for (const conditionPlugin of conditionPlugins) {
|
||||
if (conditionPlugin.condition) {
|
||||
const realPlugins = await conditionPlugin.plugins();
|
||||
plugins.push(...realPlugins);
|
||||
}
|
||||
}
|
||||
return plugins.flat();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件获取通用的vite插件
|
||||
*/
|
||||
async function loadCommonPlugins(
|
||||
options: CommonPluginOptions,
|
||||
): Promise<ConditionPlugin[]> {
|
||||
const { devtools, injectMetadata, isBuild, visualizer } = options;
|
||||
return [
|
||||
{
|
||||
condition: true,
|
||||
plugins: () => [
|
||||
viteVue({
|
||||
script: {
|
||||
defineModel: true,
|
||||
// propsDestructure: true,
|
||||
},
|
||||
}),
|
||||
viteVueJsx(),
|
||||
viteTailwindReferencePlugin(),
|
||||
tailwindcss(),
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
condition: !isBuild && devtools,
|
||||
plugins: () => [viteVueDevTools()],
|
||||
},
|
||||
{
|
||||
condition: injectMetadata,
|
||||
plugins: async () => [await viteMetadataPlugin()],
|
||||
},
|
||||
{
|
||||
condition: isBuild && !!visualizer,
|
||||
plugins: () => [
|
||||
viteVisualizerPlugin({
|
||||
filename: './node_modules/.cache/visualizer/stats.html',
|
||||
gzipSize: true,
|
||||
open: true,
|
||||
}) as PluginOption,
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件获取应用类型的vite插件
|
||||
*/
|
||||
async function loadApplicationPlugins(
|
||||
options: ApplicationPluginOptions,
|
||||
): Promise<PluginOption[]> {
|
||||
// 单独取,否则commonOptions拿不到
|
||||
const isBuild = options.isBuild;
|
||||
const env = options.env;
|
||||
|
||||
const {
|
||||
archiver,
|
||||
archiverPluginOptions,
|
||||
compress,
|
||||
compressTypes,
|
||||
extraAppConfig,
|
||||
html,
|
||||
dayjs,
|
||||
i18n,
|
||||
importmap,
|
||||
importmapOptions,
|
||||
injectAppLoading,
|
||||
license,
|
||||
nitroMock,
|
||||
nitroMockOptions,
|
||||
print,
|
||||
printInfoMap,
|
||||
pwa,
|
||||
pwaOptions,
|
||||
vxeTableLazyImport,
|
||||
...commonOptions
|
||||
} = options;
|
||||
|
||||
const commonPlugins = await loadCommonPlugins(commonOptions);
|
||||
|
||||
return await loadConditionPlugins([
|
||||
...commonPlugins,
|
||||
{
|
||||
condition: i18n,
|
||||
plugins: async () => {
|
||||
return [
|
||||
viteVueI18nPlugin({
|
||||
compositionOnly: true,
|
||||
fullInstall: true,
|
||||
runtimeOnly: true,
|
||||
}),
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: print,
|
||||
plugins: async () => {
|
||||
return [await vitePrintPlugin({ infoMap: printInfoMap })];
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: !isBuild,
|
||||
plugins: () => [viteFormFieldSlotMigrationWarningPlugin()],
|
||||
},
|
||||
{
|
||||
condition: vxeTableLazyImport,
|
||||
plugins: async () => {
|
||||
return [await viteVxeTableImportsPlugin()];
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: nitroMock,
|
||||
plugins: async () => {
|
||||
return [await viteNitroMockPlugin(nitroMockOptions)];
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
condition: injectAppLoading,
|
||||
plugins: async () => [await viteInjectAppLoadingPlugin(!!isBuild, env)],
|
||||
},
|
||||
{
|
||||
condition: license,
|
||||
plugins: async () => [await viteLicensePlugin()],
|
||||
},
|
||||
{
|
||||
condition: pwa,
|
||||
plugins: () =>
|
||||
VitePWA({
|
||||
injectRegister: false,
|
||||
workbox: {
|
||||
globPatterns: [],
|
||||
},
|
||||
...pwaOptions,
|
||||
manifest: {
|
||||
display: 'standalone',
|
||||
start_url: '/',
|
||||
theme_color: '#ffffff',
|
||||
...pwaOptions?.manifest,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
condition: isBuild && !!compress,
|
||||
plugins: () => {
|
||||
const compressPlugins: PluginOption[] = [];
|
||||
if (compressTypes?.includes('brotli')) {
|
||||
compressPlugins.push(
|
||||
viteCompressPlugin({ deleteOriginFile: false, ext: '.br' }),
|
||||
);
|
||||
}
|
||||
if (compressTypes?.includes('gzip')) {
|
||||
compressPlugins.push(
|
||||
viteCompressPlugin({ deleteOriginFile: false, ext: '.gz' }),
|
||||
);
|
||||
}
|
||||
return compressPlugins;
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: !!html,
|
||||
plugins: () => [viteHtmlPlugin(typeof html === 'object' ? html : {})],
|
||||
},
|
||||
{
|
||||
condition: isBuild && importmap,
|
||||
plugins: () => {
|
||||
return [viteImportMapPlugin(importmapOptions)];
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: isBuild && extraAppConfig,
|
||||
plugins: async () => [
|
||||
await viteExtraAppConfigPlugin({ isBuild: true, root: process.cwd() }),
|
||||
],
|
||||
},
|
||||
{
|
||||
condition: archiver,
|
||||
plugins: async () => {
|
||||
return [await viteArchiverPlugin(archiverPluginOptions)];
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: dayjs,
|
||||
plugins: () => [viteDayjsPlugin()],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件获取库类型的vite插件
|
||||
*/
|
||||
async function loadLibraryPlugins(
|
||||
options: LibraryPluginOptions,
|
||||
): Promise<PluginOption[]> {
|
||||
// 单独取,否则commonOptions拿不到
|
||||
const isBuild = options.isBuild;
|
||||
const { dts, ...commonOptions } = options;
|
||||
const dtsOptions = typeof dts === 'object' ? dts : undefined;
|
||||
const commonPlugins = await loadCommonPlugins(commonOptions);
|
||||
return await loadConditionPlugins([
|
||||
...commonPlugins,
|
||||
{
|
||||
condition: isBuild && !!dts,
|
||||
plugins: () => [viteDtsPlugin(dtsOptions)],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
export { viteCssLayerPlugin } from './css-layer';
|
||||
|
||||
export {
|
||||
loadApplicationPlugins,
|
||||
loadLibraryPlugins,
|
||||
viteArchiverPlugin,
|
||||
viteCompressPlugin,
|
||||
viteDayjsPlugin,
|
||||
viteDtsPlugin,
|
||||
viteHtmlPlugin,
|
||||
viteVisualizerPlugin,
|
||||
viteVxeTableImportsPlugin,
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
# inject-app-loading
|
||||
|
||||
用于在应用加载时显示加载动画的插件,可自行选择加载动画的样式。
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<style data-app-loading="inject-css">
|
||||
html {
|
||||
/* same as ant-design-vue/dist/reset.css setting, avoid the title line-height changed */
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.dark .loading {
|
||||
background-color: #0d0d10;
|
||||
}
|
||||
|
||||
.dark .loading .title {
|
||||
color: rgb(255 255 255 / 85%);
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
background-color: #f4f7f9;
|
||||
}
|
||||
|
||||
.loading.hidden {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.6s ease-out;
|
||||
}
|
||||
|
||||
.loading .title {
|
||||
margin-top: 36px;
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
color: rgb(0 0 0 / 85%);
|
||||
}
|
||||
|
||||
.dot {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-top: 30px;
|
||||
font-size: 32px;
|
||||
transform: rotate(45deg);
|
||||
animation: rotate-ani 1.2s infinite linear;
|
||||
}
|
||||
|
||||
.dot i {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: hsl(var(--primary, 210 100% 50%));
|
||||
border-radius: 100%;
|
||||
opacity: 0.3;
|
||||
transform: scale(0.75);
|
||||
transform-origin: 50% 50%;
|
||||
animation: spin-move-ani 1s infinite linear alternate;
|
||||
}
|
||||
|
||||
.dot i:nth-child(1) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.dot i:nth-child(2) {
|
||||
top: 0;
|
||||
right: 0;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.dot i:nth-child(3) {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
|
||||
.dot i:nth-child(4) {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
|
||||
@keyframes rotate-ani {
|
||||
to {
|
||||
transform: rotate(405deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin-move-ani {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="loading" id="__app-loading__">
|
||||
<span class="dot"><i></i><i></i><i></i><i></i></span>
|
||||
<div class="title">%VITE_APP_TITLE%</div>
|
||||
</div>
|
||||
@@ -0,0 +1,113 @@
|
||||
<style data-app-loading="inject-css">
|
||||
html {
|
||||
/* same as ant-design-vue/dist/reset.css setting, avoid the title line-height changed */
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: #f4f7f9;
|
||||
|
||||
/* transition: all 0.8s ease-out; */
|
||||
}
|
||||
|
||||
.loading.hidden {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: all 0.8s ease-out;
|
||||
}
|
||||
|
||||
.dark .loading {
|
||||
background: #0d0d10;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-top: 66px;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: rgb(0 0 0 / 85%);
|
||||
}
|
||||
|
||||
.dark .title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.loader {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.loader::before {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
left: 0;
|
||||
width: 48px;
|
||||
height: 5px;
|
||||
content: '';
|
||||
background: hsl(var(--primary, 210 100% 50%) / 50%);
|
||||
border-radius: 50%;
|
||||
animation: shadow-ani 0.5s linear infinite;
|
||||
}
|
||||
|
||||
.loader::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: '';
|
||||
background: hsl(var(--primary, 210 100% 50%));
|
||||
border-radius: 4px;
|
||||
animation: jump-ani 0.5s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes jump-ani {
|
||||
15% {
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: translateY(9px) rotate(22.5deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
border-bottom-right-radius: 40px;
|
||||
transform: translateY(18px) scale(1, 0.9) rotate(45deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translateY(9px) rotate(67.5deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0) rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shadow-ani {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.2, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="loading" id="__app-loading__">
|
||||
<div class="loader"></div>
|
||||
<div class="title">%VITE_APP_TITLE%</div>
|
||||
</div>
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import fs from 'node:fs';
|
||||
import fsp from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { readPackageJSON } from '@vben/node-utils';
|
||||
|
||||
/**
|
||||
* 用于生成将loading样式注入到项目中
|
||||
* 为多app提供loading样式,无需在每个 app -> index.html单独引入
|
||||
*/
|
||||
async function viteInjectAppLoadingPlugin(
|
||||
isBuild: boolean,
|
||||
env: Record<string, any> = {},
|
||||
loadingTemplate = 'loading.html',
|
||||
): Promise<PluginOption | undefined> {
|
||||
const loadingHtml = await getLoadingRawByHtmlTemplate(loadingTemplate);
|
||||
const { version } = await readPackageJSON(process.cwd());
|
||||
const envRaw = isBuild ? 'prod' : 'dev';
|
||||
const cacheName = `'${env.VITE_APP_NAMESPACE}-${version}-${envRaw}-preferences-theme'`;
|
||||
|
||||
// 获取缓存的主题
|
||||
// 保证黑暗主题下,刷新页面时,loading也是黑暗主题
|
||||
const injectScript = `
|
||||
<script data-app-loading="inject-js">
|
||||
var theme = localStorage.getItem(${cacheName});
|
||||
document.documentElement.classList.toggle('dark', /dark/.test(theme));
|
||||
</script>
|
||||
`;
|
||||
|
||||
if (!loadingHtml) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
enforce: 'pre',
|
||||
name: 'vite:inject-app-loading',
|
||||
transformIndexHtml: {
|
||||
handler(html) {
|
||||
const re = /<body\s*>/;
|
||||
html = html.replace(re, `<body>${injectScript}${loadingHtml}`);
|
||||
return html;
|
||||
},
|
||||
order: 'pre',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于获取loading的html模板
|
||||
*/
|
||||
async function getLoadingRawByHtmlTemplate(loadingTemplate: string) {
|
||||
// 支持在app内自定义loading模板,模版参考default-loading.html即可
|
||||
let appLoadingPath = join(process.cwd(), loadingTemplate);
|
||||
|
||||
if (!fs.existsSync(appLoadingPath)) {
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
appLoadingPath = join(__dirname, './default-loading.html');
|
||||
}
|
||||
|
||||
return await fsp.readFile(appLoadingPath, 'utf8');
|
||||
}
|
||||
|
||||
export { viteInjectAppLoadingPlugin };
|
||||
@@ -0,0 +1,111 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import {
|
||||
dateUtil,
|
||||
findMonorepoRoot,
|
||||
getPackages,
|
||||
readPackageJSON,
|
||||
} from '@vben/node-utils';
|
||||
|
||||
import { readWorkspaceManifest } from '@pnpm/workspace.read-manifest';
|
||||
|
||||
function resolvePackageVersion(
|
||||
pkgsMeta: Record<string, string>,
|
||||
name: string,
|
||||
value: string,
|
||||
catalog: Record<string, string>,
|
||||
) {
|
||||
if (value.includes('catalog:')) {
|
||||
return catalog[name];
|
||||
}
|
||||
|
||||
if (value.includes('workspace')) {
|
||||
return pkgsMeta[name];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async function resolveMonorepoDependencies() {
|
||||
const { packages } = await getPackages();
|
||||
const manifest = await readWorkspaceManifest(findMonorepoRoot());
|
||||
const catalog = manifest?.catalog || {};
|
||||
|
||||
const resultDevDependencies: Record<string, string | undefined> = {};
|
||||
const resultDependencies: Record<string, string | undefined> = {};
|
||||
const pkgsMeta: Record<string, string> = {};
|
||||
|
||||
for (const { packageJson } of packages) {
|
||||
pkgsMeta[packageJson.name] = packageJson.version;
|
||||
}
|
||||
|
||||
for (const { packageJson } of packages) {
|
||||
const { dependencies = {}, devDependencies = {} } = packageJson;
|
||||
for (const [key, value] of Object.entries(dependencies)) {
|
||||
resultDependencies[key] = resolvePackageVersion(
|
||||
pkgsMeta,
|
||||
key,
|
||||
value,
|
||||
catalog,
|
||||
);
|
||||
}
|
||||
for (const [key, value] of Object.entries(devDependencies)) {
|
||||
resultDevDependencies[key] = resolvePackageVersion(
|
||||
pkgsMeta,
|
||||
key,
|
||||
value,
|
||||
catalog,
|
||||
);
|
||||
}
|
||||
}
|
||||
return {
|
||||
dependencies: resultDependencies,
|
||||
devDependencies: resultDevDependencies,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于注入项目信息
|
||||
*/
|
||||
async function viteMetadataPlugin(
|
||||
root = process.cwd(),
|
||||
): Promise<PluginOption | undefined> {
|
||||
const { author, description, homepage, license, version } =
|
||||
await readPackageJSON(root);
|
||||
|
||||
const buildTime = dateUtil().format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
return {
|
||||
async config() {
|
||||
const { dependencies, devDependencies } =
|
||||
await resolveMonorepoDependencies();
|
||||
|
||||
const isAuthorObject = typeof author === 'object';
|
||||
const authorName = isAuthorObject ? author.name : author;
|
||||
const authorEmail = isAuthorObject ? author.email : null;
|
||||
const authorUrl = isAuthorObject ? author.url : null;
|
||||
|
||||
return {
|
||||
define: {
|
||||
__VBEN_ADMIN_METADATA__: JSON.stringify({
|
||||
authorEmail,
|
||||
authorName,
|
||||
authorUrl,
|
||||
buildTime,
|
||||
dependencies,
|
||||
description,
|
||||
devDependencies,
|
||||
homepage,
|
||||
license,
|
||||
version,
|
||||
}),
|
||||
'import.meta.env.VITE_APP_VERSION': JSON.stringify(version),
|
||||
},
|
||||
};
|
||||
},
|
||||
enforce: 'post',
|
||||
name: 'vite:inject-metadata',
|
||||
};
|
||||
}
|
||||
|
||||
export { viteMetadataPlugin };
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import { EOL } from 'node:os';
|
||||
|
||||
import { dateUtil, readPackageJSON } from '@vben/node-utils';
|
||||
|
||||
/**
|
||||
* 用于注入版权信息
|
||||
* @returns
|
||||
*/
|
||||
async function viteLicensePlugin(
|
||||
root = process.cwd(),
|
||||
): Promise<PluginOption | undefined> {
|
||||
const {
|
||||
description = '',
|
||||
homepage = '',
|
||||
version = '',
|
||||
} = await readPackageJSON(root);
|
||||
|
||||
return {
|
||||
apply: 'build',
|
||||
enforce: 'post',
|
||||
generateBundle: {
|
||||
handler(_options, bundle) {
|
||||
const date = dateUtil().format('YYYY-MM-DD ');
|
||||
const copyrightText = `/*!
|
||||
* Vben Admin
|
||||
* Version: ${version}
|
||||
* Author: vben
|
||||
* Copyright (C) 2024 Vben
|
||||
* License: MIT License
|
||||
* Description: ${description}
|
||||
* Date Created: ${date}
|
||||
* Homepage: ${homepage}
|
||||
* Contact: ann.vben@gmail.com
|
||||
*/
|
||||
`.trim();
|
||||
|
||||
for (const [, fileContent] of Object.entries(bundle)) {
|
||||
if (fileContent.type === 'chunk' && fileContent.isEntry) {
|
||||
// 插入版权信息
|
||||
const content = fileContent.code;
|
||||
const updatedContent = `${copyrightText}${EOL}${content}`;
|
||||
// 更新bundle
|
||||
fileContent.code = updatedContent;
|
||||
}
|
||||
}
|
||||
},
|
||||
order: 'post',
|
||||
},
|
||||
name: 'vite:license',
|
||||
};
|
||||
}
|
||||
|
||||
export { viteLicensePlugin };
|
||||
@@ -0,0 +1,98 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import type { NitroMockPluginOptions } from '../typing';
|
||||
|
||||
import { colors, consola, getPackage } from '@vben/node-utils';
|
||||
|
||||
import getPort from 'get-port';
|
||||
import { build, createDevServer, createNitro, prepare } from 'nitropack';
|
||||
|
||||
const hmrKeyRe = /^runtimeConfig\.|routeRules\./;
|
||||
|
||||
export const viteNitroMockPlugin = ({
|
||||
mockServerPackage = '@vben/backend-mock',
|
||||
port = 5320,
|
||||
verbose = true,
|
||||
}: NitroMockPluginOptions = {}): PluginOption => {
|
||||
return {
|
||||
async configureServer(server) {
|
||||
const availablePort = await getPort({ port });
|
||||
if (availablePort !== port) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pkg = await getPackage(mockServerPackage);
|
||||
if (!pkg) {
|
||||
consola.log(
|
||||
`Package ${mockServerPackage} not found. Skip mock server.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
runNitroServer(pkg.dir, port, verbose);
|
||||
|
||||
const _printUrls = server.printUrls;
|
||||
server.printUrls = () => {
|
||||
_printUrls();
|
||||
|
||||
consola.log(
|
||||
` ${colors.green('➜')} ${colors.bold('Nitro Mock Server')}: ${colors.cyan(`http://localhost:${port}/api`)}`,
|
||||
);
|
||||
};
|
||||
},
|
||||
enforce: 'pre',
|
||||
name: 'vite:mock-server',
|
||||
};
|
||||
};
|
||||
|
||||
async function runNitroServer(rootDir: string, port: number, verbose: boolean) {
|
||||
let nitro: any;
|
||||
const reload = async () => {
|
||||
if (nitro) {
|
||||
consola.info('Restarting dev server...');
|
||||
if ('unwatch' in nitro.options._c12) {
|
||||
await nitro.options._c12.unwatch();
|
||||
}
|
||||
await nitro.close();
|
||||
}
|
||||
nitro = await createNitro(
|
||||
{
|
||||
dev: true,
|
||||
preset: 'nitro-dev',
|
||||
rootDir,
|
||||
},
|
||||
{
|
||||
c12: {
|
||||
async onUpdate({ getDiff, newConfig }) {
|
||||
const diff = getDiff();
|
||||
if (diff.length === 0) {
|
||||
return;
|
||||
}
|
||||
verbose &&
|
||||
consola.info(
|
||||
`Nitro config updated:\n${diff
|
||||
.map((entry) => ` ${entry.toString()}`)
|
||||
.join('\n')}`,
|
||||
);
|
||||
await (diff.every((e) => hmrKeyRe.test(e.key))
|
||||
? nitro.updateConfig(newConfig.config)
|
||||
: reload());
|
||||
},
|
||||
},
|
||||
watch: true,
|
||||
},
|
||||
);
|
||||
nitro.hooks.hookOnce('restart', reload);
|
||||
|
||||
const server = createDevServer(nitro);
|
||||
await server.listen(port, { showURL: false });
|
||||
await prepare(nitro);
|
||||
await build(nitro);
|
||||
|
||||
if (verbose) {
|
||||
console.log('');
|
||||
consola.success(colors.bold(colors.green('Nitro Mock Server started.')));
|
||||
}
|
||||
};
|
||||
return await reload();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import type { PrintPluginOptions } from '../typing';
|
||||
|
||||
import { colors } from '@vben/node-utils';
|
||||
|
||||
export const vitePrintPlugin = (
|
||||
options: PrintPluginOptions = {},
|
||||
): PluginOption => {
|
||||
const { infoMap = {} } = options;
|
||||
|
||||
return {
|
||||
configureServer(server) {
|
||||
const _printUrls = server.printUrls;
|
||||
server.printUrls = () => {
|
||||
_printUrls();
|
||||
|
||||
for (const [key, value] of Object.entries(infoMap)) {
|
||||
console.log(
|
||||
` ${colors.green('➜')} ${colors.bold(key)}: ${colors.cyan(value)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
enforce: 'pre',
|
||||
name: 'vite:print-info',
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
const REFERENCE_LINE = '@reference "@vben/tailwind-config/theme";\n';
|
||||
|
||||
/**
|
||||
* Auto-inject @reference into Vue SFC <style> blocks that use @apply.
|
||||
*
|
||||
* In Tailwind CSS v4, each Vue SFC <style scoped> block is processed as an
|
||||
* independent CSS module. If a style block uses @apply with custom theme
|
||||
* utilities (e.g. bg-primary, text-foreground), it needs access to the
|
||||
* @theme definition via @reference. This plugin auto-injects it so
|
||||
* individual components don't need to add it manually.
|
||||
*/
|
||||
export function viteTailwindReferencePlugin(): Plugin {
|
||||
return {
|
||||
enforce: 'pre',
|
||||
name: 'vite:tailwind-reference',
|
||||
transform(code, id) {
|
||||
// Only process Vue SFC style blocks
|
||||
if (!id.includes('.vue')) {
|
||||
return null;
|
||||
}
|
||||
if (!id.includes('type=style')) {
|
||||
return null;
|
||||
}
|
||||
// Skip if already has @reference
|
||||
if (code.includes('@reference')) {
|
||||
return null;
|
||||
}
|
||||
// Only inject if the style block uses @apply
|
||||
if (!code.includes('@apply')) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
code: REFERENCE_LINE + code,
|
||||
map: null,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import { lazyImport, VxeResolver } from 'vite-plugin-lazy-import';
|
||||
|
||||
async function viteVxeTableImportsPlugin(): Promise<PluginOption> {
|
||||
return [
|
||||
lazyImport({
|
||||
resolvers: [
|
||||
VxeResolver({
|
||||
libraryName: 'vxe-table',
|
||||
}),
|
||||
VxeResolver({
|
||||
libraryName: 'vxe-pc-ui',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
export { viteVxeTableImportsPlugin };
|
||||
@@ -0,0 +1,364 @@
|
||||
import type { Options as HtmlMinifierOptions } from 'html-minifier-terser';
|
||||
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
|
||||
import type { PluginOptions } from 'unplugin-dts';
|
||||
import type {
|
||||
ConfigEnv,
|
||||
PluginOption,
|
||||
UserConfig,
|
||||
UserConfigFnPromise,
|
||||
} from 'vite';
|
||||
import type { Options as PwaPluginOptions } from 'vite-plugin-pwa';
|
||||
|
||||
/**
|
||||
* ImportMap 配置接口
|
||||
* @description 用于配置模块导入映射,支持自定义导入路径和范围
|
||||
* @example
|
||||
* ```typescript
|
||||
* {
|
||||
* imports: {
|
||||
* 'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js'
|
||||
* },
|
||||
* scopes: {
|
||||
* 'https://site.com/': {
|
||||
* 'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js'
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
interface IImportMap {
|
||||
/** 模块导入映射 */
|
||||
imports?: Record<string, string>;
|
||||
/** 作用域特定的导入映射 */
|
||||
scopes?: {
|
||||
[scope: string]: Record<string, string>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印插件配置选项
|
||||
* @description 用于配置控制台打印信息
|
||||
*/
|
||||
interface PrintPluginOptions {
|
||||
/**
|
||||
* 打印的数据映射
|
||||
* @description 键值对形式的数据,将在控制台打印
|
||||
* @example
|
||||
* ```typescript
|
||||
* {
|
||||
* 'App Version': '1.0.0',
|
||||
* 'Build Time': '2024-01-01'
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
infoMap?: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nitro Mock 插件配置选项
|
||||
* @description 用于配置 Nitro Mock 服务器的行为
|
||||
*/
|
||||
interface NitroMockPluginOptions {
|
||||
/**
|
||||
* Mock 服务器包名
|
||||
* @default '@vbenjs/nitro-mock'
|
||||
*/
|
||||
mockServerPackage?: string;
|
||||
|
||||
/**
|
||||
* Mock 服务端口
|
||||
* @default 3000
|
||||
*/
|
||||
port?: number;
|
||||
|
||||
/**
|
||||
* 是否打印 Mock 日志
|
||||
* @default false
|
||||
*/
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 归档插件配置选项
|
||||
* @description 用于配置构建产物的压缩归档
|
||||
*/
|
||||
interface ArchiverPluginOptions {
|
||||
/**
|
||||
* 输出文件名
|
||||
* @default 'dist'
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 输出目录
|
||||
* @default '.'
|
||||
*/
|
||||
outputDir?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML 插件配置
|
||||
* @description 用于配置基于 transformIndexHtml 的 HTML 压缩行为
|
||||
*/
|
||||
type HtmlPluginOptions = HtmlMinifierOptions;
|
||||
|
||||
/**
|
||||
* ImportMap 插件配置
|
||||
* @description 用于配置模块的 CDN 导入
|
||||
*/
|
||||
interface ImportmapPluginOptions {
|
||||
/**
|
||||
* CDN 供应商
|
||||
* @default 'jspm.io'
|
||||
* @description 支持 esm.sh 和 jspm.io 两种 CDN 供应商
|
||||
*/
|
||||
defaultProvider?: 'esm.sh' | 'jspm.io';
|
||||
/**
|
||||
* ImportMap 配置数组
|
||||
* @description 配置需要从 CDN 导入的包
|
||||
* @example
|
||||
* ```typescript
|
||||
* [
|
||||
* { name: 'vue' },
|
||||
* { name: 'pinia', range: '^2.0.0' }
|
||||
* ]
|
||||
* ```
|
||||
*/
|
||||
importmap?: Array<{ name: string; range?: string }>;
|
||||
/**
|
||||
* 手动配置 ImportMap
|
||||
* @description 自定义 ImportMap 配置
|
||||
*/
|
||||
inputMap?: IImportMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件插件配置
|
||||
* @description 用于根据条件动态加载插件
|
||||
*/
|
||||
interface ConditionPlugin {
|
||||
/**
|
||||
* 判断条件
|
||||
* @description 当条件为 true 时加载插件
|
||||
*/
|
||||
condition?: boolean;
|
||||
/**
|
||||
* 插件对象
|
||||
* @description 返回插件数组或 Promise
|
||||
*/
|
||||
plugins: () => PluginOption[] | PromiseLike<PluginOption[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用插件配置选项
|
||||
* @description 所有插件共用的基础配置
|
||||
*/
|
||||
interface CommonPluginOptions {
|
||||
/**
|
||||
* 是否开启开发工具
|
||||
* @default false
|
||||
*/
|
||||
devtools?: boolean;
|
||||
/**
|
||||
* 环境变量
|
||||
* @description 自定义环境变量
|
||||
*/
|
||||
env?: Record<string, any>;
|
||||
/**
|
||||
* 是否注入元数据
|
||||
* @default true
|
||||
*/
|
||||
injectMetadata?: boolean;
|
||||
/**
|
||||
* 是否为构建模式
|
||||
* @default false
|
||||
*/
|
||||
isBuild?: boolean;
|
||||
/**
|
||||
* 构建模式
|
||||
* @default 'development'
|
||||
*/
|
||||
mode?: string;
|
||||
/**
|
||||
* 是否开启依赖分析
|
||||
* @default false
|
||||
* @description 使用 rollup-plugin-visualizer 分析依赖
|
||||
*/
|
||||
visualizer?: boolean | PluginVisualizerOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用插件配置选项
|
||||
* @description 用于配置应用构建时的插件选项
|
||||
*/
|
||||
interface ApplicationPluginOptions extends CommonPluginOptions {
|
||||
/**
|
||||
* 是否开启压缩归档
|
||||
* @default false
|
||||
* @description 开启后会在打包目录生成 zip 文件
|
||||
*/
|
||||
archiver?: boolean;
|
||||
/**
|
||||
* 压缩归档插件配置
|
||||
* @description 配置压缩归档的行为
|
||||
*/
|
||||
archiverPluginOptions?: ArchiverPluginOptions;
|
||||
/**
|
||||
* 是否开启压缩
|
||||
* @default false
|
||||
* @description 支持 gzip 和 brotli 压缩
|
||||
*/
|
||||
compress?: boolean;
|
||||
/**
|
||||
* 压缩类型
|
||||
* @default ['gzip']
|
||||
* @description 可选的压缩类型
|
||||
*/
|
||||
compressTypes?: ('brotli' | 'gzip')[];
|
||||
/**
|
||||
* 是否开启 dayjs 插件
|
||||
* @default true
|
||||
*/
|
||||
dayjs?: boolean;
|
||||
/**
|
||||
* 是否抽离配置文件
|
||||
* @default false
|
||||
* @description 在构建时抽离配置文件
|
||||
*/
|
||||
extraAppConfig?: boolean;
|
||||
/**
|
||||
* 是否开启 HTML 插件
|
||||
* @default true
|
||||
*/
|
||||
html?: boolean | HtmlPluginOptions;
|
||||
/**
|
||||
* 是否开启国际化
|
||||
* @default false
|
||||
*/
|
||||
i18n?: boolean;
|
||||
/**
|
||||
* 是否开启 ImportMap CDN
|
||||
* @default false
|
||||
*/
|
||||
importmap?: boolean;
|
||||
/**
|
||||
* ImportMap 插件配置
|
||||
*/
|
||||
importmapOptions?: ImportmapPluginOptions;
|
||||
/**
|
||||
* 是否注入应用加载动画
|
||||
* @default true
|
||||
*/
|
||||
injectAppLoading?: boolean;
|
||||
/**
|
||||
* 是否注入全局 SCSS
|
||||
* @default true
|
||||
*/
|
||||
injectGlobalScss?: boolean;
|
||||
/**
|
||||
* 是否注入版权信息
|
||||
* @default true
|
||||
*/
|
||||
license?: boolean;
|
||||
/**
|
||||
* 是否开启 Nitro Mock
|
||||
* @default false
|
||||
*/
|
||||
nitroMock?: boolean;
|
||||
/**
|
||||
* Nitro Mock 插件配置
|
||||
*/
|
||||
nitroMockOptions?: NitroMockPluginOptions;
|
||||
/**
|
||||
* 是否开启控制台打印
|
||||
* @default false
|
||||
*/
|
||||
print?: boolean;
|
||||
/**
|
||||
* 打印插件配置
|
||||
*/
|
||||
printInfoMap?: PrintPluginOptions['infoMap'];
|
||||
/**
|
||||
* 是否开启 PWA
|
||||
* @default false
|
||||
*/
|
||||
pwa?: boolean;
|
||||
/**
|
||||
* PWA 插件配置
|
||||
*/
|
||||
pwaOptions?: Partial<PwaPluginOptions>;
|
||||
/**
|
||||
* 是否开启 VXE Table 懒加载
|
||||
* @default false
|
||||
*/
|
||||
vxeTableLazyImport?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 库插件配置选项
|
||||
* @description 用于配置库构建时的插件选项
|
||||
*/
|
||||
interface LibraryPluginOptions extends CommonPluginOptions {
|
||||
/**
|
||||
* 是否开启 DTS 输出
|
||||
* @default true
|
||||
* @description 生成 TypeScript 类型声明文件
|
||||
*/
|
||||
dts?: boolean | PluginOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用配置选项类型
|
||||
*/
|
||||
type ApplicationOptions = ApplicationPluginOptions;
|
||||
|
||||
/**
|
||||
* 库配置选项类型
|
||||
*/
|
||||
type LibraryOptions = LibraryPluginOptions;
|
||||
|
||||
/**
|
||||
* 应用配置定义函数类型
|
||||
* @description 用于定义应用构建配置
|
||||
*/
|
||||
type DefineApplicationOptions = (config?: ConfigEnv) => Promise<{
|
||||
/** 应用插件配置 */
|
||||
application?: ApplicationOptions;
|
||||
/** Vite 配置 */
|
||||
vite?: UserConfig;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* 库配置定义函数类型
|
||||
* @description 用于定义库构建配置
|
||||
*/
|
||||
type DefineLibraryOptions = (config?: ConfigEnv) => Promise<{
|
||||
/** 库插件配置 */
|
||||
library?: LibraryOptions;
|
||||
/** Vite 配置 */
|
||||
vite?: UserConfig;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* 配置定义类型
|
||||
* @description 应用或库的配置定义
|
||||
*/
|
||||
type DefineConfig = DefineApplicationOptions | DefineLibraryOptions;
|
||||
|
||||
type VbenViteConfig = Promise<UserConfig> | UserConfig | UserConfigFnPromise;
|
||||
|
||||
export type {
|
||||
ApplicationPluginOptions,
|
||||
ArchiverPluginOptions,
|
||||
CommonPluginOptions,
|
||||
ConditionPlugin,
|
||||
DefineApplicationOptions,
|
||||
DefineConfig,
|
||||
DefineLibraryOptions,
|
||||
HtmlPluginOptions,
|
||||
IImportMap,
|
||||
ImportmapPluginOptions,
|
||||
LibraryPluginOptions,
|
||||
NitroMockPluginOptions,
|
||||
PrintPluginOptions,
|
||||
VbenViteConfig,
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
import type { ApplicationPluginOptions } from '../typing';
|
||||
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { fs } from '@vben/node-utils';
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
const getBoolean = (value: string | undefined) => value === 'true';
|
||||
|
||||
const getString = (value: string | undefined, fallback: string) =>
|
||||
value ?? fallback;
|
||||
|
||||
const getNumber = (value: string | undefined, fallback: number) =>
|
||||
Number(value) || fallback;
|
||||
|
||||
/**
|
||||
* 获取当前环境下生效的配置文件名
|
||||
*/
|
||||
function getConfFiles() {
|
||||
const script = process.env.npm_lifecycle_script as string;
|
||||
const reg = /--mode ([\d_a-z]+)/;
|
||||
const result = reg.exec(script);
|
||||
let mode = 'production';
|
||||
if (result) {
|
||||
mode = result[1] as string;
|
||||
}
|
||||
return ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the environment variables starting with the specified prefix
|
||||
* @param match prefix
|
||||
* @param confFiles ext
|
||||
*/
|
||||
async function loadEnv<T = Record<string, string>>(
|
||||
match = 'VITE_GLOB_',
|
||||
confFiles = getConfFiles(),
|
||||
) {
|
||||
let envConfig = {};
|
||||
|
||||
for (const confFile of confFiles) {
|
||||
try {
|
||||
const confFilePath = join(process.cwd(), confFile);
|
||||
if (existsSync(confFilePath)) {
|
||||
const envPath = await fs.readFile(confFilePath, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
const env = dotenv.parse(envPath);
|
||||
envConfig = { ...envConfig, ...env };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error while parsing ${confFile}`, error);
|
||||
}
|
||||
}
|
||||
const reg = new RegExp(`^(${match})`);
|
||||
Object.keys(envConfig).forEach((key) => {
|
||||
if (!reg.test(key)) {
|
||||
Reflect.deleteProperty(envConfig, key);
|
||||
}
|
||||
});
|
||||
return envConfig as T;
|
||||
}
|
||||
|
||||
async function loadAndConvertEnv(
|
||||
match = 'VITE_',
|
||||
confFiles = getConfFiles(),
|
||||
): Promise<
|
||||
Partial<ApplicationPluginOptions> & {
|
||||
appTitle: string;
|
||||
base: string;
|
||||
port: number;
|
||||
}
|
||||
> {
|
||||
const envConfig = await loadEnv(match, confFiles);
|
||||
|
||||
const {
|
||||
VITE_APP_TITLE,
|
||||
VITE_ARCHIVER,
|
||||
VITE_BASE,
|
||||
VITE_COMPRESS,
|
||||
VITE_DEVTOOLS,
|
||||
VITE_INJECT_APP_LOADING,
|
||||
VITE_NITRO_MOCK,
|
||||
VITE_PORT,
|
||||
VITE_PWA,
|
||||
VITE_VISUALIZER,
|
||||
} = envConfig;
|
||||
|
||||
const compressTypes = (VITE_COMPRESS ?? '')
|
||||
.split(',')
|
||||
.filter((item) => item === 'brotli' || item === 'gzip');
|
||||
|
||||
return {
|
||||
appTitle: getString(VITE_APP_TITLE, 'Vben Admin'),
|
||||
archiver: getBoolean(VITE_ARCHIVER),
|
||||
base: getString(VITE_BASE, '/'),
|
||||
compress: compressTypes.length > 0,
|
||||
compressTypes,
|
||||
devtools: getBoolean(VITE_DEVTOOLS),
|
||||
injectAppLoading: getBoolean(VITE_INJECT_APP_LOADING),
|
||||
nitroMock: getBoolean(VITE_NITRO_MOCK),
|
||||
port: getNumber(VITE_PORT, 5173),
|
||||
pwa: getBoolean(VITE_PWA),
|
||||
visualizer: getBoolean(VITE_VISUALIZER),
|
||||
};
|
||||
}
|
||||
|
||||
export { loadAndConvertEnv, loadEnv };
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/node.json",
|
||||
"include": ["src", "tsdown.config.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user