diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs index 3deda6c226a..e68d8a3e026 100644 --- a/.lintstagedrc.mjs +++ b/.lintstagedrc.mjs @@ -1,7 +1,4 @@ export default { - '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [ - 'prettier --cache --write--parser json', - ], '*.{js,jsx,ts,tsx}': [ 'prettier --cache --ignore-unknown --write', 'eslint --cache --fix', @@ -16,5 +13,8 @@ export default { 'eslint --cache --fix', 'stylelint --fix --allow-empty-input', ], + '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [ + 'prettier --cache --write--parser json', + ], 'package.json': ['prettier --cache --write'], }; diff --git a/apps/web-antd/.env.production b/apps/web-antd/.env.production index 37947069ff5..5375847a6ca 100644 --- a/apps/web-antd/.env.production +++ b/apps/web-antd/.env.production @@ -14,3 +14,6 @@ VITE_ROUTER_HISTORY=hash # 是否注入全局loading VITE_INJECT_APP_LOADING=true + +# 打包后是否生成dist.zip +VITE_ARCHIVER=true diff --git a/apps/web-ele/.env.production b/apps/web-ele/.env.production index 37947069ff5..5375847a6ca 100644 --- a/apps/web-ele/.env.production +++ b/apps/web-ele/.env.production @@ -14,3 +14,6 @@ VITE_ROUTER_HISTORY=hash # 是否注入全局loading VITE_INJECT_APP_LOADING=true + +# 打包后是否生成dist.zip +VITE_ARCHIVER=true diff --git a/apps/web-naive/.env.production b/apps/web-naive/.env.production index 37947069ff5..5375847a6ca 100644 --- a/apps/web-naive/.env.production +++ b/apps/web-naive/.env.production @@ -14,3 +14,6 @@ VITE_ROUTER_HISTORY=hash # 是否注入全局loading VITE_INJECT_APP_LOADING=true + +# 打包后是否生成dist.zip +VITE_ARCHIVER=true diff --git a/docs/.vitepress/config/shared.mts b/docs/.vitepress/config/shared.mts index b7059f32250..43ce9ffa673 100644 --- a/docs/.vitepress/config/shared.mts +++ b/docs/.vitepress/config/shared.mts @@ -3,6 +3,8 @@ import type { HeadConfig } from 'vitepress'; import { resolve } from 'node:path'; +import { viteArchiverPlugin } from '@vben/vite-config'; + import { GitChangelog, GitChangelogMarkdownSection, @@ -76,6 +78,7 @@ export const shared = defineConfig({ repoURL: () => 'https://github.com/vbenjs/vue-vben-admin', }), GitChangelogMarkdownSection(), + viteArchiverPlugin({ outputDir: '.vitepress' }), ], server: { fs: { diff --git a/docs/package.json b/docs/package.json index e265455f9a1..1585e5f528b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -20,6 +20,7 @@ "devDependencies": { "@nolebase/vitepress-plugin-git-changelog": "^2.4.0", "@types/markdown-it": "^14.1.2", + "@vben/vite-config": "workspace:*", "@vite-pwa/vitepress": "^0.5.0", "vitepress": "^1.3.4", "vue": "^3.4.38" diff --git a/docs/src/components/common-ui/vben-drawer.md b/docs/src/components/common-ui/vben-drawer.md index 1c0f0310e9d..a8df3161691 100644 --- a/docs/src/components/common-ui/vben-drawer.md +++ b/docs/src/components/common-ui/vben-drawer.md @@ -58,22 +58,26 @@ const [Drawer, drawerApi] = useVbenDrawer({ 所有属性都可以传入 `useVbenDrawer` 的第一个参数中。 -| 属性名 | 描述 | 类型 | 默认值 | -| ------------------ | ------------------- | --------------- | ------- | -| title | 标题 | `string\|slot` | - | -| titleTooltip | 标题提示信息 | `string\|slot` | - | -| description | 描述信息 | `string\|slot` | - | -| isOpen | 弹窗打开状态 | `boolean` | `false` | -| loading | 弹窗加载状态 | `boolean` | `false` | -| closable | 显示关闭按钮 | `boolean` | `true` | -| modal | 显示遮罩 | `boolean` | `true` | -| header | 显示header | `boolean` | `true` | -| footer | 显示footer | `boolean\|slot` | `true` | -| confirmLoading | 确认按钮loading状态 | `boolean` | `false` | -| closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` | -| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` | -| confirmText | 确认按钮文本 | `boolean\|slot` | `确认` | -| cancelText | 取消按钮文本 | `boolean\|slot` | `取消` | +| 属性名 | 描述 | 类型 | 默认值 | +| --- | --- | --- | --- | +| title | 标题 | `string\|slot` | - | +| titleTooltip | 标题提示信息 | `string\|slot` | - | +| description | 描述信息 | `string\|slot` | - | +| isOpen | 弹窗打开状态 | `boolean` | `false` | +| loading | 弹窗加载状态 | `boolean` | `false` | +| closable | 显示关闭按钮 | `boolean` | `true` | +| modal | 显示遮罩 | `boolean` | `true` | +| header | 显示header | `boolean` | `true` | +| footer | 显示footer | `boolean\|slot` | `true` | +| confirmLoading | 确认按钮loading状态 | `boolean` | `false` | +| closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` | +| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` | +| confirmText | 确认按钮文本 | `string\|slot` | `确认` | +| cancelText | 取消按钮文本 | `string\|slot` | `取消` | +| class | modal的class,宽度通过这个配置 | `string` | - | +| contentClass | modal内容区域的class | `string` | - | +| footerClass | modal底部区域的class | `string` | - | +| headerClass | modal顶部区域的class | `string` | - | ### Event diff --git a/docs/src/components/common-ui/vben-modal.md b/docs/src/components/common-ui/vben-modal.md index f2df3441217..a2eecbde564 100644 --- a/docs/src/components/common-ui/vben-modal.md +++ b/docs/src/components/common-ui/vben-modal.md @@ -64,26 +64,30 @@ const [Modal, modalApi] = useVbenModal({ 所有属性都可以传入 `useVbenModal` 的第一个参数中。 -| 属性名 | 描述 | 类型 | 默认值 | -| ------------------ | ------------------- | --------------- | ------- | -| title | 标题 | `string\|slot` | - | -| titleTooltip | 标题提示信息 | `string\|slot` | - | -| description | 描述信息 | `string\|slot` | - | -| isOpen | 弹窗打开状态 | `boolean` | `false` | -| loading | 弹窗加载状态 | `boolean` | `false` | -| fullscreen | 全屏显示 | `boolean` | `false` | -| fullscreenButton | 显示全屏按钮 | `boolean` | `true` | -| draggable | 可拖拽 | `boolean` | `false` | -| closable | 显示关闭按钮 | `boolean` | `true` | -| centered | 居中显示 | `boolean` | `false` | -| modal | 显示遮罩 | `boolean` | `true` | -| header | 显示header | `boolean` | `true` | -| footer | 显示footer | `boolean\|slot` | `true` | -| confirmLoading | 确认按钮loading状态 | `boolean` | `false` | -| closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` | -| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` | -| confirmText | 确认按钮文本 | `boolean\|slot` | `确认` | -| cancelText | 取消按钮文本 | `boolean\|slot` | `取消` | +| 属性名 | 描述 | 类型 | 默认值 | +| --- | --- | --- | --- | +| title | 标题 | `string\|slot` | - | +| titleTooltip | 标题提示信息 | `string\|slot` | - | +| description | 描述信息 | `string\|slot` | - | +| isOpen | 弹窗打开状态 | `boolean` | `false` | +| loading | 弹窗加载状态 | `boolean` | `false` | +| fullscreen | 全屏显示 | `boolean` | `false` | +| fullscreenButton | 显示全屏按钮 | `boolean` | `true` | +| draggable | 可拖拽 | `boolean` | `false` | +| closable | 显示关闭按钮 | `boolean` | `true` | +| centered | 居中显示 | `boolean` | `false` | +| modal | 显示遮罩 | `boolean` | `true` | +| header | 显示header | `boolean` | `true` | +| footer | 显示footer | `boolean\|slot` | `true` | +| confirmLoading | 确认按钮loading状态 | `boolean` | `false` | +| closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` | +| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` | +| confirmText | 确认按钮文本 | `string\|slot` | `确认` | +| cancelText | 取消按钮文本 | `string\|slot` | `取消` | +| class | modal的class,宽度通过这个配置 | `string` | - | +| contentClass | modal内容区域的class | `string` | - | +| footerClass | modal底部区域的class | `string` | - | +| headerClass | modal顶部区域的class | `string` | - | ### Event diff --git a/docs/src/en/guide/essentials/settings.md b/docs/src/en/guide/essentials/settings.md index 5a7cec18dda..7fc2c399a2f 100644 --- a/docs/src/en/guide/essentials/settings.md +++ b/docs/src/en/guide/essentials/settings.md @@ -55,6 +55,9 @@ VITE_DEVTOOLS=true # Whether to inject global loading VITE_INJECT_APP_LOADING=true + +# Whether to generate after packaging dist.zip +VITE_ARCHIVER=true ``` ::: diff --git a/docs/src/guide/essentials/settings.md b/docs/src/guide/essentials/settings.md index 6631740071f..f5b740d8c82 100644 --- a/docs/src/guide/essentials/settings.md +++ b/docs/src/guide/essentials/settings.md @@ -55,6 +55,7 @@ VITE_DEVTOOLS=true # 是否注入全局loading VITE_INJECT_APP_LOADING=true + ``` ```bash [.env.production] @@ -75,6 +76,10 @@ VITE_ROUTER_HISTORY=hash # 是否注入全局loading VITE_INJECT_APP_LOADING=true + +# 打包后是否生成dist.zip +VITE_ARCHIVER=true + ``` ::: diff --git a/internal/lint-configs/eslint-config/src/configs/perfectionist.ts b/internal/lint-configs/eslint-config/src/configs/perfectionist.ts index 51a2249b3b6..1b17b30f31a 100644 --- a/internal/lint-configs/eslint-config/src/configs/perfectionist.ts +++ b/internal/lint-configs/eslint-config/src/configs/perfectionist.ts @@ -68,7 +68,7 @@ export async function perfectionist(): Promise { ignorePattern: ['children'], order: 'asc', partitionByComment: 'Part:**', - type: 'alphabetical', + type: 'natural', }, ], 'perfectionist/sort-vue-attributes': [ diff --git a/internal/tailwind-config/src/index.ts b/internal/tailwind-config/src/index.ts index b1b1922a9b4..7043b8dadf0 100644 --- a/internal/tailwind-config/src/index.ts +++ b/internal/tailwind-config/src/index.ts @@ -190,8 +190,8 @@ export default { }, float: { '0%': { transform: 'translateY(0)' }, - '100%': { transform: 'translateY(0)' }, '50%': { transform: 'translateY(-20px)' }, + '100%': { transform: 'translateY(0)' }, }, }, zIndex: { @@ -228,11 +228,11 @@ function createColorsPalette(name: string) { // • backgroundDarkest (#172554): 适用于最深的背景,可能用于非常深色的区域或极端对比色。 return { + 50: `hsl(var(--${name}-50))`, 100: `hsl(var(--${name}-100))`, 200: `hsl(var(--${name}-200))`, 300: `hsl(var(--${name}-300))`, 400: `hsl(var(--${name}-400))`, - 50: `hsl(var(--${name}-50))`, 500: `hsl(var(--${name}-500))`, 600: `hsl(var(--${name}-600))`, 700: `hsl(var(--${name}-700))`, diff --git a/internal/vite-config/package.json b/internal/vite-config/package.json index 1692662362e..505b21f8bbf 100644 --- a/internal/vite-config/package.json +++ b/internal/vite-config/package.json @@ -29,6 +29,7 @@ "dependencies": { "@intlify/unplugin-vue-i18n": "^4.0.0", "@jspm/generator": "^2.1.3", + "archiver": "^7.0.1", "cheerio": "1.0.0", "get-port": "^7.1.0", "html-minifier-terser": "^7.2.0", @@ -39,6 +40,7 @@ "vite-plugin-vue-devtools": "^7.3.9" }, "devDependencies": { + "@types/archiver": "^6.0.2", "@types/html-minifier-terser": "^7.0.2", "@vben/node-utils": "workspace:*", "@vitejs/plugin-vue": "^5.1.2", diff --git a/internal/vite-config/src/config/application.ts b/internal/vite-config/src/config/application.ts index fd05f3b46e2..067d17bae49 100644 --- a/internal/vite-config/src/config/application.ts +++ b/internal/vite-config/src/config/application.ts @@ -24,6 +24,8 @@ function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) { const env = loadEnv(mode, root); const plugins = await loadApplicationPlugins({ + archiver: true, + archiverPluginOptions: {}, compress: false, compressTypes: ['brotli', 'gzip'], devtools: true, diff --git a/internal/vite-config/src/plugins/archiver.ts b/internal/vite-config/src/plugins/archiver.ts new file mode 100644 index 00000000000..ea04ce07ee6 --- /dev/null +++ b/internal/vite-config/src/plugins/archiver.ts @@ -0,0 +1,67 @@ +import type { PluginOption } from 'vite'; + +import type { ArchiverPluginOptions } from '../typing'; + +import fs from 'node:fs'; +import { join } from 'node:path'; + +import archiver from 'archiver'; + +export const viteArchiverPlugin = ( + options: ArchiverPluginOptions = {}, +): PluginOption => { + return { + apply: 'build', + closeBundle: { + handler() { + const { name = 'dist', outputDir = '.' } = options; + + setTimeout(async () => { + const folderToZip = 'dist'; + const zipOutputPath = join(process.cwd(), outputDir, `${name}.zip`); + + 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 { + return new Promise((resolve, reject) => { + const output = fs.createWriteStream(outputPath); + const archive = archiver('zip', { + 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(); + }); +} diff --git a/internal/vite-config/src/plugins/index.ts b/internal/vite-config/src/plugins/index.ts index c497edd336e..de559fd3d36 100644 --- a/internal/vite-config/src/plugins/index.ts +++ b/internal/vite-config/src/plugins/index.ts @@ -18,6 +18,7 @@ import { libInjectCss as viteLibInjectCss } from 'vite-plugin-lib-inject-css'; import { VitePWA } from 'vite-plugin-pwa'; import viteVueDevTools from 'vite-plugin-vue-devtools'; +import { viteArchiverPlugin } from './archiver'; import { viteExtraAppConfigPlugin } from './extra-app-config'; import { viteImportMapPlugin } from './importmap'; import { viteInjectAppLoadingPlugin } from './inject-app-loading'; @@ -92,6 +93,8 @@ async function loadApplicationPlugins( const env = options.env; const { + archiver, + archiverPluginOptions, compress, compressTypes, extraAppConfig, @@ -138,6 +141,7 @@ async function loadApplicationPlugins( return [await viteNitroMockPlugin(nitroMockOptions)]; }, }, + { condition: injectAppLoading, plugins: async () => [await viteInjectAppLoadingPlugin(!!isBuild, env)], @@ -184,7 +188,6 @@ async function loadApplicationPlugins( condition: !!html, plugins: () => [viteHtmlPlugin({ minify: true })], }, - { condition: isBuild && importmap, plugins: () => { @@ -197,6 +200,12 @@ async function loadApplicationPlugins( await viteExtraAppConfigPlugin({ isBuild: true, root: process.cwd() }), ], }, + { + condition: archiver, + plugins: async () => { + return [await viteArchiverPlugin(archiverPluginOptions)]; + }, + }, ]); } @@ -226,6 +235,7 @@ async function loadLibraryPlugins( export { loadApplicationPlugins, loadLibraryPlugins, + viteArchiverPlugin, viteCompressPlugin, viteDtsPlugin, viteHtmlPlugin, diff --git a/internal/vite-config/src/typing.ts b/internal/vite-config/src/typing.ts index 86609c6255a..7505ab3eb4a 100644 --- a/internal/vite-config/src/typing.ts +++ b/internal/vite-config/src/typing.ts @@ -33,6 +33,19 @@ interface NitroMockPluginOptions { verbose?: boolean; } +interface ArchiverPluginOptions { + /** + * 输出文件名 + * @default dist + */ + name?: string; + /** + * 输出目录 + * @default . + */ + outputDir?: string; +} + /** * importmap 插件配置 */ @@ -74,6 +87,10 @@ interface CommonPluginOptions { } interface ApplicationPluginOptions extends CommonPluginOptions { + /** 开启后,会在打包dist同级生成dist.zip */ + archiver?: boolean; + /** 压缩归档插件配置 */ + archiverPluginOptions?: ArchiverPluginOptions; /** 开启 gzip|brotli 压缩 */ compress?: boolean; /** 压缩类型 */ @@ -134,6 +151,7 @@ type DefineConfig = DefineApplicationOptions | DefineLibraryOptions; export type { ApplicationPluginOptions, + ArchiverPluginOptions, CommonPluginOptions, ConditionPlugin, DefineApplicationOptions, diff --git a/internal/vite-config/src/utils/env.ts b/internal/vite-config/src/utils/env.ts index f9cdd4f57b0..1dfd180869f 100644 --- a/internal/vite-config/src/utils/env.ts +++ b/internal/vite-config/src/utils/env.ts @@ -74,6 +74,7 @@ async function loadAndConvertEnv( const { VITE_APP_TITLE, + VITE_ARCHIVER, VITE_BASE, VITE_COMPRESS, VITE_DEVTOOLS, @@ -90,6 +91,7 @@ async function loadAndConvertEnv( return { appTitle: getString(VITE_APP_TITLE, 'Vben Admin'), + archiver: getBoolean(VITE_ARCHIVER), base: getString(VITE_BASE, '/'), compress: compressTypes.length > 0, compressTypes, diff --git a/playground/.env.production b/playground/.env.production index 37947069ff5..5375847a6ca 100644 --- a/playground/.env.production +++ b/playground/.env.production @@ -14,3 +14,6 @@ VITE_ROUTER_HISTORY=hash # 是否注入全局loading VITE_INJECT_APP_LOADING=true + +# 打包后是否生成dist.zip +VITE_ARCHIVER=true diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66c2b68e6d5..24f9818fff5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -353,6 +353,9 @@ importers: '@types/markdown-it': specifier: ^14.1.2 version: 14.1.2 + '@vben/vite-config': + specifier: workspace:* + version: link:../internal/vite-config '@vite-pwa/vitepress': specifier: ^0.5.0 version: 0.5.0(vite-plugin-pwa@0.20.1(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(terser@5.31.6))(workbox-build@7.1.1)(workbox-window@7.1.0)) @@ -621,6 +624,9 @@ importers: '@jspm/generator': specifier: ^2.1.3 version: 2.1.3 + archiver: + specifier: ^7.0.1 + version: 7.0.1 cheerio: specifier: 1.0.0 version: 1.0.0 @@ -646,6 +652,9 @@ importers: specifier: ^7.3.9 version: 7.3.9(rollup@4.21.1)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(terser@5.31.6))(vue@3.4.38(typescript@5.5.4)) devDependencies: + '@types/archiver': + specifier: ^6.0.2 + version: 6.0.2 '@types/html-minifier-terser': specifier: ^7.0.2 version: 7.0.2 @@ -4010,6 +4019,9 @@ packages: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} + '@types/archiver@6.0.2': + resolution: {integrity: sha512-KmROQqbQzKGuaAbmK+ZcytkJ51+YqDa7NmbXjmtC5YBLSyQYo21YaUnQ3HbaPFKL1ooo6RQ6OPYPIDyxfpDDXw==} + '@types/argparse@1.0.38': resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} @@ -4113,6 +4125,9 @@ packages: '@types/qrcode@1.5.5': resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==} + '@types/readdir-glob@1.1.5': + resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -12942,6 +12957,10 @@ snapshots: '@trysound/sax@0.2.0': {} + '@types/archiver@6.0.2': + dependencies: + '@types/readdir-glob': 1.1.5 + '@types/argparse@1.0.38': {} '@types/bintrees@1.0.6': {} @@ -13050,6 +13069,10 @@ snapshots: dependencies: '@types/node': 22.5.1 + '@types/readdir-glob@1.1.5': + dependencies: + '@types/node': 22.5.1 + '@types/resolve@1.20.2': {} '@types/semver@7.5.8': {} diff --git a/turbo.json b/turbo.json index 56c0ef0145c..d46594d5df9 100644 --- a/turbo.json +++ b/turbo.json @@ -16,7 +16,7 @@ "tasks": { "build": { "dependsOn": ["^build"], - "outputs": ["dist/**"] + "outputs": ["dist/**", "dist.zip"] }, "preview": { "dependsOn": ["^build"],