Browse Source

feat: add archive plug-in to output dist.zip after build (#4272)

* feat: add the archiver plug-in to output dist.zip after build

* chore: update env
Vben 6 months ago
parent
commit
98da0672e7

+ 3 - 3
.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'],
 };

+ 3 - 0
apps/web-antd/.env.production

@@ -14,3 +14,6 @@ VITE_ROUTER_HISTORY=hash
 
 # 是否注入全局loading
 VITE_INJECT_APP_LOADING=true
+
+# 打包后是否生成dist.zip
+VITE_ARCHIVER=true

+ 3 - 0
apps/web-ele/.env.production

@@ -14,3 +14,6 @@ VITE_ROUTER_HISTORY=hash
 
 # 是否注入全局loading
 VITE_INJECT_APP_LOADING=true
+
+# 打包后是否生成dist.zip
+VITE_ARCHIVER=true

+ 3 - 0
apps/web-naive/.env.production

@@ -14,3 +14,6 @@ VITE_ROUTER_HISTORY=hash
 
 # 是否注入全局loading
 VITE_INJECT_APP_LOADING=true
+
+# 打包后是否生成dist.zip
+VITE_ARCHIVER=true

+ 3 - 0
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: {

+ 1 - 0
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"

+ 20 - 16
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
 

+ 24 - 20
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
 

+ 3 - 0
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
 ```
 
 :::

+ 5 - 0
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
+
 ```
 
 :::

+ 1 - 1
internal/lint-configs/eslint-config/src/configs/perfectionist.ts

@@ -68,7 +68,7 @@ export async function perfectionist(): Promise<Linter.Config[]> {
             ignorePattern: ['children'],
             order: 'asc',
             partitionByComment: 'Part:**',
-            type: 'alphabetical',
+            type: 'natural',
           },
         ],
         'perfectionist/sort-vue-attributes': [

+ 2 - 2
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))`,

+ 2 - 0
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",

+ 2 - 0
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,

+ 67 - 0
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<void> {
+  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();
+  });
+}

+ 11 - 1
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,

+ 18 - 0
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,

+ 2 - 0
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,

+ 3 - 0
playground/.env.production

@@ -14,3 +14,6 @@ VITE_ROUTER_HISTORY=hash
 
 # 是否注入全局loading
 VITE_INJECT_APP_LOADING=true
+
+# 打包后是否生成dist.zip
+VITE_ARCHIVER=true

+ 23 - 0
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': {}

+ 1 - 1
turbo.json

@@ -16,7 +16,7 @@
   "tasks": {
     "build": {
       "dependsOn": ["^build"],
-      "outputs": ["dist/**"]
+      "outputs": ["dist/**", "dist.zip"]
     },
     "preview": {
       "dependsOn": ["^build"],