vite-plugin-multiversion 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # vite-plugin-multiversion
2
+
3
+ 通过打包进行多版本控制的 Vite 插件
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install vite-plugin-multiversion
9
+ ```
10
+
11
+ ## 使用
12
+
13
+ ### 1. vite.config.ts 配置
14
+
15
+ ```ts
16
+ import { defineConfig, loadEnv } from 'vite'
17
+ import { resolve } from 'path'
18
+ import { versionLink, cleanOldVersions } from 'vite-plugin-multiversion'
19
+
20
+ export default defineConfig(({ mode }) => {
21
+ const env = loadEnv(mode, resolve(process.cwd()))
22
+ const NowVersion = Date.now()
23
+ const outDir = mode === 'prod' ? 'dist-prod' : 'dist'
24
+
25
+ const appUrlPath = () => {
26
+ const base = env.VITE_APP_URL_PATH
27
+ if (mode === 'dev') return base
28
+ return base + NowVersion + '/'
29
+ }
30
+
31
+ return {
32
+ base: appUrlPath(),
33
+ build: {
34
+ outDir: outDir + `/${NowVersion}`,
35
+ },
36
+ plugins: [
37
+ // 其他插件
38
+ ].concat(env.VITE_APP_ENV === 'dev' ? [] : [
39
+ cleanOldVersions({
40
+ outDir,
41
+ now: NowVersion,
42
+ days: 1,
43
+ }),
44
+ versionLink({
45
+ versionDir: String(NowVersion),
46
+ outDir,
47
+ }),
48
+ ]),
49
+ }
50
+ })
51
+ ```
52
+
53
+ ### 2. 环境变量
54
+
55
+ `.env` 文件:
56
+ ```
57
+ VITE_APP_URL_PATH=/
58
+ VITE_APP_ENV=prod
59
+ ```
60
+
61
+ `.env.admin` 文件:
62
+ ```
63
+ VITE_APP_URL_PATH=/admin-web/
64
+ VITE_APP_ENV=prod
65
+ ```
66
+
67
+ ## 插件说明
68
+
69
+ ### versionLink
70
+
71
+ 将版本目录下的 `index.html` 复制到 dist 根目录。
72
+
73
+ | 参数 | 类型 | 默认值 | 说明 |
74
+ |------|------|--------|------|
75
+ | versionDir | string | - | 版本目录名(如时间戳) |
76
+ | outDir | string | 'dist-prod' | 输出目录 |
77
+
78
+ ### cleanOldVersions
79
+
80
+ 删除超过指定天数的旧版本目录。
81
+
82
+ | 参数 | 类型 | 默认值 | 说明 |
83
+ |------|------|--------|------|
84
+ | outDir | string | 'dist-prod' | 输出目录 |
85
+ | days | number | 2 | 保留天数 |
86
+ | now | number | Date.now() | 当前时间戳(用于测试) |
87
+ | minVersions | number | 1 | 最少保留版本数 |
88
+
89
+ ## 目录结构
90
+
91
+ 打包后的目录结构:
92
+ ```
93
+ dist-prod/
94
+ ├── index.html # 当前版本入口
95
+ ├── 1709200000000/ # 版本1
96
+ │ ├── index.html
97
+ │ ├── assets/
98
+ │ └── ...
99
+ ├── 1709300000000/ # 版本2
100
+ │ ├── index.html
101
+ │ ├── assets/
102
+ │ └── ...
103
+ └── 1709400000000/ # 当前版本
104
+ ├── index.html
105
+ ├── assets/
106
+ └── ...
107
+ ```
108
+
109
+ ## Nginx 配置
110
+
111
+ ```conf
112
+ # root写法
113
+ # VITE_APP_URL_PATH = '/'
114
+ server {
115
+ listen 8084;
116
+ server_name localhost;
117
+ location / {
118
+ root 项目路径/dist-prod;
119
+ try_files $uri $uri/ /index.html;
120
+ index index.html;
121
+ }
122
+ }
123
+
124
+ # alias写法
125
+ # VITE_APP_URL_PATH = '/admin-web/'
126
+ server {
127
+ listen 8077;
128
+ server_name localhost;
129
+ location /admin-web {
130
+ alias 项目路径/dist-prod/;
131
+ try_files $uri $uri/ /admin-web/index.html;
132
+ index index.html;
133
+ }
134
+ }
135
+ ```
@@ -0,0 +1,17 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface VersionLinkOptions {
4
+ versionDir: string;
5
+ outDir?: string;
6
+ }
7
+ declare function versionLink(options: VersionLinkOptions): Plugin;
8
+
9
+ interface CleanOldVersionsOptions {
10
+ outDir?: string;
11
+ days?: number;
12
+ now?: number;
13
+ minVersions?: number;
14
+ }
15
+ declare function cleanOldVersions(options: CleanOldVersionsOptions): Plugin;
16
+
17
+ export { type CleanOldVersionsOptions, type VersionLinkOptions, cleanOldVersions, versionLink };
@@ -0,0 +1,17 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface VersionLinkOptions {
4
+ versionDir: string;
5
+ outDir?: string;
6
+ }
7
+ declare function versionLink(options: VersionLinkOptions): Plugin;
8
+
9
+ interface CleanOldVersionsOptions {
10
+ outDir?: string;
11
+ days?: number;
12
+ now?: number;
13
+ minVersions?: number;
14
+ }
15
+ declare function cleanOldVersions(options: CleanOldVersionsOptions): Plugin;
16
+
17
+ export { type CleanOldVersionsOptions, type VersionLinkOptions, cleanOldVersions, versionLink };
package/dist/index.js ADDED
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ cleanOldVersions: () => cleanOldVersions,
34
+ versionLink: () => versionLink
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/versionLink.ts
39
+ var import_node_fs = __toESM(require("fs"));
40
+ var import_node_path = __toESM(require("path"));
41
+ function versionLink(options) {
42
+ const { versionDir, outDir = "dist-prod" } = options;
43
+ if (!versionDir) {
44
+ throw new Error("[version-link] versionDir is required");
45
+ }
46
+ return {
47
+ name: "version-link",
48
+ apply: "build",
49
+ closeBundle() {
50
+ try {
51
+ const outDirPath = import_node_path.default.resolve(process.cwd(), outDir);
52
+ const versionPath = import_node_path.default.join(outDirPath, versionDir, "index.html");
53
+ const copyPath = import_node_path.default.join(outDirPath, "index.html");
54
+ if (!import_node_fs.default.existsSync(versionPath)) {
55
+ console.error(`[version-link] \u6587\u4EF6\u4E0D\u5B58\u5728: ${versionPath}`);
56
+ return;
57
+ }
58
+ import_node_fs.default.copyFileSync(versionPath, copyPath);
59
+ console.log(`[version-link] \u2705 \u590D\u5236\u6210\u529F: ${versionDir}/index.html -> index.html`);
60
+ } catch (err) {
61
+ console.error("[version-link] \u64CD\u4F5C\u5931\u8D25:", err.message);
62
+ }
63
+ }
64
+ };
65
+ }
66
+
67
+ // src/cleanOldVersions.ts
68
+ var import_node_fs2 = __toESM(require("fs"));
69
+ var import_node_path2 = __toESM(require("path"));
70
+ function cleanOldVersions(options) {
71
+ const { outDir = "dist-prod", days = 2, now, minVersions = 1 } = options;
72
+ return {
73
+ name: "clean-old-versions",
74
+ apply: "build",
75
+ closeBundle() {
76
+ if (process.env.NODE_ENV !== "production") {
77
+ return;
78
+ }
79
+ try {
80
+ const outDirPath = import_node_path2.default.resolve(process.cwd(), outDir);
81
+ if (!import_node_fs2.default.existsSync(outDirPath)) {
82
+ console.error(`[clean-old-versions] \u76EE\u5F55\u4E0D\u5B58\u5728: ${outDirPath}`);
83
+ return;
84
+ }
85
+ const currentTime = now || Date.now();
86
+ const msPerDay = 24 * 60 * 60 * 1e3;
87
+ const threshold = currentTime - days * msPerDay;
88
+ const entries = import_node_fs2.default.readdirSync(outDirPath, { withFileTypes: true });
89
+ const oldVersions = [];
90
+ for (const entry of entries) {
91
+ if (!entry.isDirectory()) continue;
92
+ if (entry.name === "index.html") continue;
93
+ const timestamp = Number(entry.name);
94
+ if (isNaN(timestamp)) continue;
95
+ if (timestamp < threshold) {
96
+ oldVersions.push({
97
+ dirPath: import_node_path2.default.join(outDirPath, entry.name),
98
+ timestamp,
99
+ entryName: entry.name
100
+ });
101
+ }
102
+ }
103
+ let deletedCount = 0;
104
+ const maxDeletable = oldVersions.length - minVersions;
105
+ oldVersions.forEach((item) => {
106
+ const { dirPath, timestamp, entryName } = item;
107
+ if (deletedCount >= maxDeletable) {
108
+ console.log(`[clean-old-versions] \u89E6\u53D1\u6700\u5C11\u4FDD\u7559 ${minVersions} \u4E2A\u65E7\u5386\u53F2: ${entryName}`);
109
+ return;
110
+ }
111
+ import_node_fs2.default.rmSync(dirPath, { recursive: true, force: true });
112
+ console.log(`[clean-old-versions] \u5DF2\u5220\u9664: ${entryName}`);
113
+ deletedCount++;
114
+ });
115
+ console.log(`[clean-old-versions] \u2705 \u6E05\u7406\u5B8C\u6210\uFF0C\u5220\u9664 ${deletedCount} \u4E2A\u76EE\u5F55`);
116
+ } catch (err) {
117
+ console.error("[clean-old-versions] \u64CD\u4F5C\u5931\u8D25:", err.message);
118
+ }
119
+ }
120
+ };
121
+ }
122
+ // Annotate the CommonJS export names for ESM import in node:
123
+ 0 && (module.exports = {
124
+ cleanOldVersions,
125
+ versionLink
126
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,88 @@
1
+ // src/versionLink.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ function versionLink(options) {
5
+ const { versionDir, outDir = "dist-prod" } = options;
6
+ if (!versionDir) {
7
+ throw new Error("[version-link] versionDir is required");
8
+ }
9
+ return {
10
+ name: "version-link",
11
+ apply: "build",
12
+ closeBundle() {
13
+ try {
14
+ const outDirPath = path.resolve(process.cwd(), outDir);
15
+ const versionPath = path.join(outDirPath, versionDir, "index.html");
16
+ const copyPath = path.join(outDirPath, "index.html");
17
+ if (!fs.existsSync(versionPath)) {
18
+ console.error(`[version-link] \u6587\u4EF6\u4E0D\u5B58\u5728: ${versionPath}`);
19
+ return;
20
+ }
21
+ fs.copyFileSync(versionPath, copyPath);
22
+ console.log(`[version-link] \u2705 \u590D\u5236\u6210\u529F: ${versionDir}/index.html -> index.html`);
23
+ } catch (err) {
24
+ console.error("[version-link] \u64CD\u4F5C\u5931\u8D25:", err.message);
25
+ }
26
+ }
27
+ };
28
+ }
29
+
30
+ // src/cleanOldVersions.ts
31
+ import fs2 from "fs";
32
+ import path2 from "path";
33
+ function cleanOldVersions(options) {
34
+ const { outDir = "dist-prod", days = 2, now, minVersions = 1 } = options;
35
+ return {
36
+ name: "clean-old-versions",
37
+ apply: "build",
38
+ closeBundle() {
39
+ if (process.env.NODE_ENV !== "production") {
40
+ return;
41
+ }
42
+ try {
43
+ const outDirPath = path2.resolve(process.cwd(), outDir);
44
+ if (!fs2.existsSync(outDirPath)) {
45
+ console.error(`[clean-old-versions] \u76EE\u5F55\u4E0D\u5B58\u5728: ${outDirPath}`);
46
+ return;
47
+ }
48
+ const currentTime = now || Date.now();
49
+ const msPerDay = 24 * 60 * 60 * 1e3;
50
+ const threshold = currentTime - days * msPerDay;
51
+ const entries = fs2.readdirSync(outDirPath, { withFileTypes: true });
52
+ const oldVersions = [];
53
+ for (const entry of entries) {
54
+ if (!entry.isDirectory()) continue;
55
+ if (entry.name === "index.html") continue;
56
+ const timestamp = Number(entry.name);
57
+ if (isNaN(timestamp)) continue;
58
+ if (timestamp < threshold) {
59
+ oldVersions.push({
60
+ dirPath: path2.join(outDirPath, entry.name),
61
+ timestamp,
62
+ entryName: entry.name
63
+ });
64
+ }
65
+ }
66
+ let deletedCount = 0;
67
+ const maxDeletable = oldVersions.length - minVersions;
68
+ oldVersions.forEach((item) => {
69
+ const { dirPath, timestamp, entryName } = item;
70
+ if (deletedCount >= maxDeletable) {
71
+ console.log(`[clean-old-versions] \u89E6\u53D1\u6700\u5C11\u4FDD\u7559 ${minVersions} \u4E2A\u65E7\u5386\u53F2: ${entryName}`);
72
+ return;
73
+ }
74
+ fs2.rmSync(dirPath, { recursive: true, force: true });
75
+ console.log(`[clean-old-versions] \u5DF2\u5220\u9664: ${entryName}`);
76
+ deletedCount++;
77
+ });
78
+ console.log(`[clean-old-versions] \u2705 \u6E05\u7406\u5B8C\u6210\uFF0C\u5220\u9664 ${deletedCount} \u4E2A\u76EE\u5F55`);
79
+ } catch (err) {
80
+ console.error("[clean-old-versions] \u64CD\u4F5C\u5931\u8D25:", err.message);
81
+ }
82
+ }
83
+ };
84
+ }
85
+ export {
86
+ cleanOldVersions,
87
+ versionLink
88
+ };
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "vite-plugin-multiversion",
3
+ "version": "1.0.0",
4
+ "description": "Vite plugin for multi-version control through building",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsup src/index.ts --format cjs,esm --dts",
17
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
18
+ "prepublishOnly": "npm run build"
19
+ },
20
+ "keywords": [
21
+ "vite",
22
+ "plugin",
23
+ "multi-version",
24
+ "version-control"
25
+ ],
26
+ "author": "",
27
+ "license": "MIT",
28
+ "devDependencies": {
29
+ "@types/node": "^25.5.0",
30
+ "tsup": "^8.0.0",
31
+ "typescript": "^5.0.0",
32
+ "vite": "^5.0.0"
33
+ },
34
+ "peerDependencies": {
35
+ "vite": ">=5.0.0"
36
+ }
37
+ }
@@ -0,0 +1,77 @@
1
+ import type { Plugin } from 'vite'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+
5
+ export interface CleanOldVersionsOptions {
6
+ outDir?: string
7
+ days?: number
8
+ now?: number
9
+ minVersions?: number
10
+ }
11
+
12
+ export function cleanOldVersions(options: CleanOldVersionsOptions): Plugin {
13
+ const { outDir = 'dist-prod', days = 2, now, minVersions = 1 } = options
14
+
15
+ return {
16
+ name: 'clean-old-versions',
17
+ apply: 'build',
18
+ closeBundle() {
19
+ if (process.env.NODE_ENV !== 'production') {
20
+ return
21
+ }
22
+
23
+ try {
24
+ const outDirPath = path.resolve(process.cwd(), outDir)
25
+
26
+ if (!fs.existsSync(outDirPath)) {
27
+ console.error(`[clean-old-versions] 目录不存在: ${outDirPath}`)
28
+ return
29
+ }
30
+
31
+ const currentTime = now || Date.now()
32
+ const msPerDay = 24 * 60 * 60 * 1000
33
+ const threshold = currentTime - days * msPerDay
34
+
35
+ const entries = fs.readdirSync(outDirPath, { withFileTypes: true })
36
+
37
+ const oldVersions: { dirPath: string; timestamp: number; entryName: string }[] = []
38
+
39
+ for (const entry of entries) {
40
+ if (!entry.isDirectory()) continue
41
+ if (entry.name === 'index.html') continue
42
+
43
+ const timestamp = Number(entry.name)
44
+ if (isNaN(timestamp)) continue
45
+
46
+ if (timestamp < threshold) {
47
+ oldVersions.push({
48
+ dirPath: path.join(outDirPath, entry.name),
49
+ timestamp,
50
+ entryName: entry.name,
51
+ })
52
+ }
53
+ }
54
+
55
+ let deletedCount = 0
56
+ const maxDeletable = oldVersions.length - minVersions
57
+
58
+ oldVersions.forEach(item => {
59
+ const { dirPath, timestamp, entryName } = item
60
+
61
+ if (deletedCount >= maxDeletable) {
62
+ console.log(`[clean-old-versions] 触发最少保留 ${minVersions} 个旧历史: ${entryName}`)
63
+ return
64
+ }
65
+
66
+ fs.rmSync(dirPath, { recursive: true, force: true })
67
+ console.log(`[clean-old-versions] 已删除: ${entryName}`)
68
+ deletedCount++
69
+ })
70
+
71
+ console.log(`[clean-old-versions] ✅ 清理完成,删除 ${deletedCount} 个目录`)
72
+ } catch (err: any) {
73
+ console.error('[clean-old-versions] 操作失败:', err.message)
74
+ }
75
+ },
76
+ }
77
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { versionLink, type VersionLinkOptions } from './versionLink'
2
+ export { cleanOldVersions, type CleanOldVersionsOptions } from './cleanOldVersions'
@@ -0,0 +1,38 @@
1
+ import type { Plugin } from 'vite'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+
5
+ export interface VersionLinkOptions {
6
+ versionDir: string
7
+ outDir?: string
8
+ }
9
+
10
+ export function versionLink(options: VersionLinkOptions): Plugin {
11
+ const { versionDir, outDir = 'dist-prod' } = options
12
+
13
+ if (!versionDir) {
14
+ throw new Error('[version-link] versionDir is required')
15
+ }
16
+
17
+ return {
18
+ name: 'version-link',
19
+ apply: 'build',
20
+ closeBundle() {
21
+ try {
22
+ const outDirPath = path.resolve(process.cwd(), outDir)
23
+ const versionPath = path.join(outDirPath, versionDir, 'index.html')
24
+ const copyPath = path.join(outDirPath, 'index.html')
25
+
26
+ if (!fs.existsSync(versionPath)) {
27
+ console.error(`[version-link] 文件不存在: ${versionPath}`)
28
+ return
29
+ }
30
+
31
+ fs.copyFileSync(versionPath, copyPath)
32
+ console.log(`[version-link] ✅ 复制成功: ${versionDir}/index.html -> index.html`)
33
+ } catch (err: any) {
34
+ console.error('[version-link] 操作失败:', err.message)
35
+ }
36
+ },
37
+ }
38
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "lib": ["ES2020"],
6
+ "types": ["node"],
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "moduleResolution": "bundler",
12
+ "esModuleInterop": true,
13
+ "strict": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "resolveJsonModule": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }