Skip to content

Commit 01d2378

Browse files
committed
feat: add new plugin and update dependencies; optimize build info and update notification
1 parent 2054dd6 commit 01d2378

File tree

14 files changed

+369
-290
lines changed

14 files changed

+369
-290
lines changed

build/config/utils.ts

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import fs from 'node:fs';
12
import path from 'node:path';
23
import process from 'node:process';
4+
import { sum } from 'es-toolkit';
5+
import { isEmpty } from 'es-toolkit/compat';
6+
import colors from 'picocolors';
37

48
export function isDevFn(mode: string): boolean {
59
return mode === 'development';
@@ -40,8 +44,10 @@ export function wrapperEnv(envConf: Env.ImportMeta): Env.ImportMeta {
4044
if (envName === 'VITE_PROXY') {
4145
try {
4246
realName = JSON.parse(realName);
43-
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
44-
} catch (error) {}
47+
} catch (error) {
48+
// eslint-disable-next-line no-console
49+
console.log(colors.red(`wrapper vite env error:\n${error}`));
50+
}
4551
}
4652
ret[envName] = realName;
4753
process.env[envName] = realName;
@@ -57,3 +63,90 @@ export function wrapperEnv(envConf: Env.ImportMeta): Env.ImportMeta {
5763
export function getRootPath(...dir: string[]) {
5864
return path.resolve(process.cwd(), ...dir);
5965
}
66+
67+
/**
68+
* Format bytes
69+
* @descCN 格式化字节
70+
* @param bytes - The number of bytes to format
71+
* @returns A string representing the size in a human-readable format
72+
*/
73+
export function formatBytes(bytes: number): string {
74+
const units = ['B', 'KB', 'MB', 'GB'];
75+
let size = bytes;
76+
let unitIndex = 0;
77+
78+
while (size >= 1024 && unitIndex < units.length - 1) {
79+
size /= 1024;
80+
unitIndex++;
81+
}
82+
83+
return `${size.toFixed(2)} ${units[unitIndex]}`;
84+
}
85+
86+
/**
87+
* Get the size of the package
88+
* @descCN 获取包的大小
89+
* @param options - The options for the package size calculation
90+
* @param options.folder - The folder to calculate the size of
91+
* @param options.callback - The callback function to call with the size
92+
* @param options.format - Whether to format the size
93+
*/
94+
export function getPackageSize(options: {
95+
folder?: string;
96+
callback: (size: string | number) => void;
97+
format?: boolean;
98+
}) {
99+
const { folder = 'dist', callback, format = true } = options;
100+
101+
const calculateFolderSize = (folderPath: string, onComplete: (size: number) => void) => {
102+
fs.readdir(folderPath, (err: NodeJS.ErrnoException | null, files: string[]) => {
103+
if (err) {
104+
// eslint-disable-next-line no-console
105+
console.log(colors.red(`read dir ${folderPath} error: ${err}`));
106+
onComplete(0);
107+
return;
108+
}
109+
110+
if (isEmpty(files)) {
111+
onComplete(0);
112+
return;
113+
}
114+
115+
let count = 0;
116+
const fileSizes: number[] = [];
117+
118+
const checkEnd = (size: number = 0) => {
119+
fileSizes.push(size);
120+
121+
if (++count === files.length) {
122+
onComplete(sum(fileSizes));
123+
}
124+
};
125+
126+
files.forEach((item: string) => {
127+
const itemPath = path.join(folderPath, item);
128+
129+
fs.stat(itemPath, (error, stats) => {
130+
if (error) {
131+
// eslint-disable-next-line no-console
132+
console.log(colors.red(`get file status ${itemPath} error: ${error}`));
133+
checkEnd(0);
134+
return;
135+
}
136+
137+
if (stats.isFile()) {
138+
checkEnd(stats.size);
139+
} else if (stats.isDirectory()) {
140+
calculateFolderSize(itemPath, checkEnd);
141+
} else {
142+
checkEnd(0);
143+
}
144+
});
145+
});
146+
});
147+
};
148+
149+
calculateFolderSize(folder, (totalSize) => {
150+
callback(format ? formatBytes(totalSize) : totalSize);
151+
});
152+
}

build/plugins/html.ts

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { createHtmlPlugin } from 'vite-plugin-html';
2-
import pkg from '../../package.json';
32
import type { PluginOption } from 'vite';
43

54
/**
@@ -11,15 +10,7 @@ import type { PluginOption } from 'vite';
1110
* @see https://github.com/vbenjs/vite-plugin-html
1211
*/
1312
export function configHtmlPlugin(env: Env.ImportMeta, isBuild: boolean) {
14-
const { VITE_APP_TITLE, VITE_BASE_URL } = env;
15-
16-
const path = VITE_BASE_URL.endsWith('/') ? VITE_BASE_URL : `${VITE_BASE_URL}/`;
17-
18-
const GLOB_CONFIG_FILE_NAME = 'app.config.js';
19-
20-
const getAppConfigSrc = () => {
21-
return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
22-
};
13+
const { VITE_APP_TITLE } = env;
2314

2415
const htmlPlugin: PluginOption[] = createHtmlPlugin({
2516
minify: isBuild,
@@ -30,17 +21,30 @@ export function configHtmlPlugin(env: Env.ImportMeta, isBuild: boolean) {
3021
title: VITE_APP_TITLE,
3122
},
3223

33-
// Embed the generated app.config.js file 需要注入的标签列表
34-
tags: isBuild
35-
? [
36-
{
37-
tag: 'script',
38-
attrs: {
39-
src: getAppConfigSrc(),
40-
},
41-
},
42-
]
43-
: [],
24+
// 需要注入的标签列表
25+
tags: [
26+
{
27+
tag: 'meta',
28+
attrs: {
29+
'http-equiv': 'Cache-Control',
30+
content: 'no-cache, no-store, must-revalidate',
31+
},
32+
},
33+
{
34+
tag: 'meta',
35+
attrs: {
36+
'http-equiv': 'Pragma',
37+
content: 'no-cache',
38+
},
39+
},
40+
{
41+
tag: 'meta',
42+
attrs: {
43+
'http-equiv': 'Expires',
44+
content: '0',
45+
},
46+
},
47+
],
4448
},
4549
});
4650

build/plugins/index.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import ViteRestart from 'vite-plugin-restart';
66
import TsconfigPaths from 'vite-tsconfig-paths';
77
import { configCompressPlugin } from './compress';
88
import { configHtmlPlugin } from './html';
9+
import { configInfoPlugin } from './info';
910
import { configSvgIconsPlugin } from './svgPlugin';
11+
import { configAppUpdatePlugin } from './update';
1012
import type { PluginOption } from 'vite';
1113

1214
/**
@@ -42,18 +44,21 @@ export const createVitePlugins = (viteEnv: Env.ImportMeta, isBuild: boolean) =>
4244
ViteRestart({
4345
restart: ['vite.config.ts'],
4446
}),
45-
];
4647

47-
// 加载 html 插件 vite-plugin-html
48-
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
48+
configSvgIconsPlugin(viteEnv, isBuild),
49+
50+
configHtmlPlugin(viteEnv, isBuild),
51+
52+
configAppUpdatePlugin(viteEnv),
53+
54+
configInfoPlugin(),
55+
];
4956

5057
// 是否开启 mock 服务 https://github.com/pengzhanbo/vite-plugin-mock-dev-server
5158
if (VITE_USE_MOCK) {
5259
vitePlugins.push(mockDevServerPlugin());
5360
}
5461

55-
vitePlugins.push(configSvgIconsPlugin(viteEnv, isBuild));
56-
5762
if (isBuild) {
5863
// 创建打包压缩配置
5964
vitePlugins.push(configCompressPlugin(viteEnv));

build/plugins/info.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import boxen, { type Options as BoxenOptions } from 'boxen';
2+
import dayjs, { type Dayjs } from 'dayjs';
3+
import duration from 'dayjs/plugin/duration';
4+
import gradient from 'gradient-string';
5+
import { getPackageSize } from '../config';
6+
import type { Plugin } from 'vite';
7+
8+
dayjs.extend(duration);
9+
10+
const welcomeMessage = gradient(['#EACA44', 'magenta']).multiline(
11+
`您好! 欢迎使用 lemon-react 开源项目\n我们为您精心准备了精美的保姆级文档\nhttps://sankeyangshu.github.io/lemon-template-docs/react/`
12+
);
13+
14+
const boxenOptions: BoxenOptions = {
15+
padding: 0.5,
16+
borderColor: '#EACA44',
17+
borderStyle: 'round',
18+
};
19+
20+
/**
21+
* config build info plugin
22+
* @descCN 配置打包信息插件
23+
*/
24+
export function configInfoPlugin(): Plugin {
25+
let config: { command: string };
26+
let startTime: Dayjs;
27+
let endTime: Dayjs;
28+
let outDir: string;
29+
return {
30+
name: 'vite:buildInfo',
31+
32+
configResolved(resolvedConfig) {
33+
config = resolvedConfig;
34+
outDir = resolvedConfig.build?.outDir ?? 'dist';
35+
},
36+
37+
buildStart() {
38+
// eslint-disable-next-line no-console
39+
console.log(boxen(welcomeMessage, boxenOptions));
40+
if (config.command === 'build') {
41+
startTime = dayjs(new Date());
42+
}
43+
},
44+
45+
closeBundle() {
46+
if (config.command === 'build') {
47+
endTime = dayjs(new Date());
48+
49+
getPackageSize({
50+
folder: outDir,
51+
callback: (size: string | number) => {
52+
// eslint-disable-next-line no-console
53+
console.log(
54+
boxen(
55+
gradient(['#EACA44', 'magenta']).multiline(
56+
`🎉 恭喜打包完成(总用时${dayjs
57+
.duration(endTime.diff(startTime))
58+
.format('mm分ss秒')},打包后的大小为${size})`
59+
),
60+
boxenOptions
61+
)
62+
);
63+
},
64+
});
65+
}
66+
},
67+
};
68+
}

build/plugins/update.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { webUpdateNotice } from '@plugin-web-update-notification/vite';
2+
3+
/**
4+
* Configures the app update notice plugin for Vite.
5+
* @descCN 配置更新通知 vite 插件
6+
* @returns The configured app update notice plugin.
7+
* @see https://github.com/GreatAuk/plugin-web-update-notification
8+
*/
9+
export function configAppUpdatePlugin(env: Env.ImportMeta) {
10+
const { VITE_BASE_URL = '/' } = env;
11+
12+
const appUpdatePlugin = webUpdateNotice({
13+
injectFileBase: VITE_BASE_URL,
14+
locale: 'zh-CN',
15+
localeData: {
16+
'en-US': {
17+
title: '📢 system update',
18+
description: 'System update, please refresh the page',
19+
buttonText: 'refresh',
20+
dismissButtonText: 'dismiss',
21+
},
22+
'zh-CN': {
23+
title: '📢 系统升级通知',
24+
description: '检测到当前系统版本已更新,请刷新页面后使用',
25+
buttonText: '刷新',
26+
dismissButtonText: '忽略',
27+
},
28+
},
29+
});
30+
31+
return appUpdatePlugin;
32+
}

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"colord": "^2.9.3",
5959
"dayjs": "^1.11.13",
6060
"echarts": "^5.6.0",
61+
"es-toolkit": "^1.39.3",
6162
"i18next": "^25.2.1",
6263
"i18next-browser-languagedetector": "^8.1.0",
6364
"keepalive-for-react": "^4.0.2",
@@ -75,6 +76,7 @@
7576
"@commitlint/cli": "^19.8.1",
7677
"@commitlint/config-conventional": "^19.8.1",
7778
"@iconify/react": "^6.0.0",
79+
"@plugin-web-update-notification/vite": "^2.0.0",
7880
"@sankeyangshu/eslint-config": "^1.0.0",
7981
"@types/mockjs": "^1.0.10",
8082
"@types/node": "^22.15.26",
@@ -86,14 +88,17 @@
8688
"@unocss/preset-rem-to-px": "^66.1.2",
8789
"@vitejs/plugin-react": "^4.5.0",
8890
"autoprefixer": "^10.4.21",
91+
"boxen": "^8.0.1",
8992
"bumpp": "^10.1.1",
9093
"eslint": "^9.27.0",
9194
"eslint-plugin-react": "^7.37.5",
9295
"eslint-plugin-react-hooks": "^5.2.0",
9396
"eslint-plugin-react-refresh": "^0.4.20",
97+
"gradient-string": "^3.0.0",
9498
"less": "^4.3.0",
9599
"lint-staged": "^16.1.0",
96100
"mockjs": "^1.1.0",
101+
"picocolors": "^1.1.1",
97102
"postcss": "^8.5.4",
98103
"postcss-mobile-forever": "^5.0.0",
99104
"rimraf": "^6.0.1",

0 commit comments

Comments
 (0)