@tnotesjs/core 0.0.5 → 0.0.7
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 +84 -84
- package/dist/vitepress/config/index.js +4 -1
- package/package.json +1 -1
- package/types/config.ts +61 -61
- package/types/index.ts +11 -11
- package/types/note.ts +33 -33
- package/vitepress/assets/icons/icon__check.svg +3 -3
- package/vitepress/assets/icons/icon__clipboard.svg +7 -7
- package/vitepress/assets/icons/icon__close.svg +1 -1
- package/vitepress/assets/icons/icon__collapse.svg +1 -1
- package/vitepress/assets/icons/icon__confirm.svg +1 -1
- package/vitepress/assets/icons/icon__copy.svg +4 -4
- package/vitepress/assets/icons/icon__focus.svg +1 -1
- package/vitepress/assets/icons/icon__fold.svg +3 -3
- package/vitepress/assets/icons/icon__folder.svg +1 -1
- package/vitepress/assets/icons/icon__fullscreen.svg +1 -1
- package/vitepress/assets/icons/icon__fullscreen_exit.svg +1 -1
- package/vitepress/assets/icons/icon__github.svg +3 -3
- package/vitepress/assets/icons/icon__mindmap.svg +1 -1
- package/vitepress/assets/icons/icon__next.svg +1 -1
- package/vitepress/assets/icons/icon__number_gray.svg +1 -1
- package/vitepress/assets/icons/icon__number_purple.svg +1 -1
- package/vitepress/assets/icons/icon__prev.svg +1 -1
- package/vitepress/assets/icons/icon__restore.svg +1 -1
- package/vitepress/assets/icons/icon__rotate.svg +4 -4
- package/vitepress/assets/icons/icon__search.svg +1 -1
- package/vitepress/assets/icons/icon__sidebar_collapsed.svg +1 -1
- package/vitepress/assets/icons/icon__sidebar_opened.svg +1 -1
- package/vitepress/assets/icons/icon__totop.svg +5 -5
- package/vitepress/assets/icons/icon__vscode.svg +5 -5
- package/vitepress/assets/icons/icon__zoom_fit.svg +1 -1
- package/vitepress/assets/icons/icon__zoom_in.svg +1 -1
- package/vitepress/assets/icons/icon__zoom_out.svg +1 -1
- package/vitepress/assets/icons/icon__zoom_reset.svg +1 -1
- package/vitepress/assets/icons/index.ts +38 -38
- package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +20 -20
- package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +373 -373
- package/vitepress/components/CodeBlockFullscreen/index.ts +115 -115
- package/vitepress/components/CodeBlockFullscreen/styles.css +64 -64
- package/vitepress/components/Discussions/Discussions.module.scss +32 -32
- package/vitepress/components/Discussions/Discussions.vue +211 -211
- package/vitepress/components/EnWordList/EnWordList.module.scss +124 -124
- package/vitepress/components/EnWordList/EnWordList.vue +543 -543
- package/vitepress/components/EnWordList/RightClickMenu.module.scss +22 -22
- package/vitepress/components/EnWordList/RightClickMenu.vue +66 -66
- package/vitepress/components/Footprints/Footprints.module.scss +93 -93
- package/vitepress/components/Footprints/Footprints.vue +377 -377
- package/vitepress/components/Layout/AboutModal.module.scss +233 -233
- package/vitepress/components/Layout/AboutModal.vue +105 -105
- package/vitepress/components/Layout/AboutPanel.vue +266 -266
- package/vitepress/components/Layout/ContentCollapse.vue +603 -603
- package/vitepress/components/Layout/CustomSidebar.vue +605 -605
- package/vitepress/components/Layout/DocBeforeControls.vue +149 -139
- package/vitepress/components/Layout/DocFooter.vue +233 -233
- package/vitepress/components/Layout/ImagePreview.module.scss +201 -201
- package/vitepress/components/Layout/ImagePreview.vue +281 -281
- package/vitepress/components/Layout/Layout.module.scss +661 -661
- package/vitepress/components/Layout/Layout.vue +542 -542
- package/vitepress/components/Layout/NoteStatus.vue +140 -140
- package/vitepress/components/Layout/SidebarItems.vue +263 -263
- package/vitepress/components/Layout/SidebarNavBefore.vue +92 -92
- package/vitepress/components/Layout/Swiper.vue +167 -167
- package/vitepress/components/Layout/ToggleFullContent.module.scss +11 -11
- package/vitepress/components/Layout/ToggleFullContent.vue +34 -34
- package/vitepress/components/Layout/ToggleSidebar.module.scss +11 -11
- package/vitepress/components/Layout/ToggleSidebar.vue +35 -35
- package/vitepress/components/Layout/composables/useCollapseControl.ts +88 -88
- package/vitepress/components/Layout/composables/useNoteConfig.ts +121 -121
- package/vitepress/components/Layout/composables/useNoteSave.ts +173 -173
- package/vitepress/components/Layout/composables/useNoteValidation.ts +85 -85
- package/vitepress/components/Layout/composables/useRedirect.ts +110 -110
- package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +85 -85
- package/vitepress/components/Layout/homeReadme.data.ts +124 -124
- package/vitepress/components/LoadingPage/LoadingPage.vue +192 -192
- package/vitepress/components/MarkMap/MarkMap.module.scss +159 -159
- package/vitepress/components/MarkMap/MarkMap.vue +404 -404
- package/vitepress/components/Mermaid/Mermaid.module.scss +275 -275
- package/vitepress/components/Mermaid/Mermaid.vue +364 -364
- package/vitepress/components/NotesTable/NotesTable.module.scss +77 -77
- package/vitepress/components/NotesTable/NotesTable.vue +98 -98
- package/vitepress/components/NotesTable/README.md +67 -67
- package/vitepress/components/Settings/Settings.module.scss +433 -433
- package/vitepress/components/Settings/Settings.vue +306 -306
- package/vitepress/components/SidebarCard/MindMapView.vue +483 -483
- package/vitepress/components/SidebarCard/NotesTrendChart.vue +108 -108
- package/vitepress/components/SidebarCard/SidebarCard.vue +948 -948
- package/vitepress/components/Tooltip/Tooltip.vue +70 -70
- package/vitepress/components/constants.ts +91 -91
- package/vitepress/components/notesConfig.data.ts +73 -73
- package/vitepress/components/sidebar.data.ts +59 -59
- package/vitepress/components/tnotes-config.data.ts +21 -21
- package/vitepress/components/utils.ts +26 -26
- package/vitepress/config/index.ts +160 -141
- package/vitepress/configs/constants.ts +26 -26
- package/vitepress/configs/head.config.ts +25 -25
- package/vitepress/configs/index.ts +9 -9
- package/vitepress/configs/markdown-it.d.ts +23 -23
- package/vitepress/configs/markdown.config.ts +366 -366
- package/vitepress/configs/theme.config.ts +108 -108
- package/vitepress/plugins/buildProgressPlugin.ts +470 -470
- package/vitepress/plugins/getNoteByConfigIdPlugin.ts +107 -107
- package/vitepress/plugins/renameNotePlugin.ts +60 -60
- package/vitepress/plugins/updateConfigPlugin.ts +63 -63
- package/vitepress/theme/index.ts +95 -95
- package/vitepress/theme/styles/base.scss +50 -50
- package/vitepress/theme/styles/components/404.scss +31 -31
- package/vitepress/theme/styles/components/collapse.scss +175 -175
- package/vitepress/theme/styles/components/markmap.scss +101 -101
- package/vitepress/theme/styles/components/swiper.scss +255 -255
- package/vitepress/theme/styles/index.scss +25 -25
- package/vitepress/theme/styles/layout.scss +62 -62
- package/vitepress/theme/styles/utilities.scss +39 -39
- package/vitepress/theme/styles/vitepress-override.scss +25 -25
|
@@ -1,470 +1,470 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* .vitepress/tnotes/vitepress/plugins/buildProgressPlugin.ts
|
|
3
|
-
*
|
|
4
|
-
* 构建进度插件 - 仅在 build 模式下显示真实进度
|
|
5
|
-
*
|
|
6
|
-
* 基于 vite-plugin-progress 源码简化实现
|
|
7
|
-
* https://github.com/jeddygong/vite-plugin-progress
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { Plugin } from 'vite'
|
|
11
|
-
import {
|
|
12
|
-
existsSync,
|
|
13
|
-
readFileSync,
|
|
14
|
-
writeFileSync,
|
|
15
|
-
mkdirSync,
|
|
16
|
-
readdirSync,
|
|
17
|
-
} from 'fs'
|
|
18
|
-
import { join } from 'path'
|
|
19
|
-
|
|
20
|
-
/** 缓存目录和文件路径 */
|
|
21
|
-
const CACHE_DIR = join(process.cwd(), 'node_modules', '.tnotes-progress')
|
|
22
|
-
const CACHE_FILE = join(CACHE_DIR, 'build-cache.json')
|
|
23
|
-
|
|
24
|
-
/** 缓存数据结构 */
|
|
25
|
-
interface CacheData {
|
|
26
|
-
transformCount: number
|
|
27
|
-
chunkCount: number
|
|
28
|
-
/** transform 阶段耗时占总构建时间的比例(0-1),用于下次按时间权重分配进度 */
|
|
29
|
-
transformEndRatio?: number
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** 插件配置选项 */
|
|
33
|
-
interface BuildProgressOptions {
|
|
34
|
-
width?: number
|
|
35
|
-
complete?: string
|
|
36
|
-
incomplete?: string
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 读取缓存数据
|
|
41
|
-
*/
|
|
42
|
-
function getCacheData(): CacheData {
|
|
43
|
-
try {
|
|
44
|
-
if (existsSync(CACHE_FILE)) {
|
|
45
|
-
return JSON.parse(readFileSync(CACHE_FILE, 'utf-8'))
|
|
46
|
-
}
|
|
47
|
-
} catch {
|
|
48
|
-
// 忽略错误
|
|
49
|
-
}
|
|
50
|
-
return { transformCount: 0, chunkCount: 0 }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* 写入缓存数据
|
|
55
|
-
*/
|
|
56
|
-
function setCacheData(data: CacheData): void {
|
|
57
|
-
try {
|
|
58
|
-
if (!existsSync(CACHE_DIR)) {
|
|
59
|
-
mkdirSync(CACHE_DIR, { recursive: true })
|
|
60
|
-
}
|
|
61
|
-
writeFileSync(CACHE_FILE, JSON.stringify(data), 'utf-8')
|
|
62
|
-
} catch {
|
|
63
|
-
// 忽略错误
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 扫描源目录文件数量
|
|
69
|
-
*/
|
|
70
|
-
function countSourceFiles(srcDir: string): number {
|
|
71
|
-
let count = 0
|
|
72
|
-
const extensions = /\.(vue|ts|js|jsx|tsx|css|scss|sass|styl|less|md)$/i
|
|
73
|
-
|
|
74
|
-
const scan = (dir: string) => {
|
|
75
|
-
try {
|
|
76
|
-
const entries = readdirSync(dir, { withFileTypes: true })
|
|
77
|
-
for (const entry of entries) {
|
|
78
|
-
const fullPath = join(dir, entry.name)
|
|
79
|
-
if (
|
|
80
|
-
entry.isDirectory() &&
|
|
81
|
-
!entry.name.startsWith('.') &&
|
|
82
|
-
entry.name !== 'node_modules'
|
|
83
|
-
) {
|
|
84
|
-
scan(fullPath)
|
|
85
|
-
} else if (entry.isFile() && extensions.test(entry.name)) {
|
|
86
|
-
count++
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
} catch {
|
|
90
|
-
// 忽略错误
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
scan(srcDir)
|
|
95
|
-
return count
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// ============ 全局状态 ============
|
|
99
|
-
let globalStartTime = 0
|
|
100
|
-
let globalTransformCount = 0
|
|
101
|
-
let globalChunkCount = 0
|
|
102
|
-
let globalHasError = false
|
|
103
|
-
let globalIsBuilding = false
|
|
104
|
-
let globalOutDir = ''
|
|
105
|
-
let globalLastPercent = 0
|
|
106
|
-
let globalFileCount = 0
|
|
107
|
-
let globalLastLoggedPercent = -1 // 非 TTY 环境下上次输出的百分比区间,初始为 -1 以便第一次输出
|
|
108
|
-
let globalLastOutputTime = 0 // 非 TTY 环境下上次输出时间戳,用于时间节流
|
|
109
|
-
let globalTransformEndTime = 0 // transform 阶段结束时间戳
|
|
110
|
-
let globalLastHookTime = 0 // 最后一次 Vite hook 触发时间
|
|
111
|
-
let globalStallTimer: ReturnType<typeof setInterval> | null = null
|
|
112
|
-
|
|
113
|
-
/** 检测是否支持单行刷新(交互式终端) */
|
|
114
|
-
const isTTY = !!(process.stdout.isTTY && process.stderr.isTTY)
|
|
115
|
-
|
|
116
|
-
/** 检测是否在 CI 环境中运行 */
|
|
117
|
-
const isCI = !!(
|
|
118
|
-
process.env.CI ||
|
|
119
|
-
process.env.GITHUB_ACTIONS ||
|
|
120
|
-
process.env.GITLAB_CI ||
|
|
121
|
-
process.env.CIRCLECI ||
|
|
122
|
-
process.env.TRAVIS ||
|
|
123
|
-
process.env.JENKINS_URL
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
/** 是否使用单行刷新模式(TTY 且非 CI) */
|
|
127
|
-
const useSingleLineMode = isTTY && !isCI
|
|
128
|
-
|
|
129
|
-
// 保存原始输出函数
|
|
130
|
-
let originalStdoutWrite: typeof process.stdout.write | null = null
|
|
131
|
-
let originalStderrWrite: typeof process.stderr.write | null = null
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* 拦截输出
|
|
135
|
-
*/
|
|
136
|
-
function interceptOutput() {
|
|
137
|
-
if (originalStdoutWrite) return
|
|
138
|
-
|
|
139
|
-
originalStdoutWrite = process.stdout.write.bind(process.stdout)
|
|
140
|
-
originalStderrWrite = process.stderr.write.bind(process.stderr)
|
|
141
|
-
|
|
142
|
-
const filter = (chunk: string | Uint8Array): boolean => {
|
|
143
|
-
const str = chunk.toString()
|
|
144
|
-
// 只允许我们的输出
|
|
145
|
-
return (
|
|
146
|
-
str.includes('🔨') ||
|
|
147
|
-
str.includes('✅ 构建成功') ||
|
|
148
|
-
str.includes('❌ 构建失败') ||
|
|
149
|
-
str.includes('📁') ||
|
|
150
|
-
str.includes('📊') ||
|
|
151
|
-
str.includes('📦') ||
|
|
152
|
-
str.includes('⏱️') ||
|
|
153
|
-
str.includes('\x1b[2K') // 允许清屏指令
|
|
154
|
-
)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
process.stdout.write = ((
|
|
158
|
-
chunk: string | Uint8Array,
|
|
159
|
-
encodingOrCallback?: BufferEncoding | ((err?: Error | null) => void),
|
|
160
|
-
callback?: (err?: Error | null) => void,
|
|
161
|
-
): boolean => {
|
|
162
|
-
if (filter(chunk)) {
|
|
163
|
-
return originalStdoutWrite!(chunk, encodingOrCallback as any, callback)
|
|
164
|
-
}
|
|
165
|
-
if (typeof encodingOrCallback === 'function') encodingOrCallback()
|
|
166
|
-
else if (callback) callback()
|
|
167
|
-
return true
|
|
168
|
-
}) as typeof process.stdout.write
|
|
169
|
-
|
|
170
|
-
process.stderr.write = ((
|
|
171
|
-
chunk: string | Uint8Array,
|
|
172
|
-
encodingOrCallback?: BufferEncoding | ((err?: Error | null) => void),
|
|
173
|
-
callback?: (err?: Error | null) => void,
|
|
174
|
-
): boolean => {
|
|
175
|
-
const str = chunk.toString()
|
|
176
|
-
// 允许进度条和真正的错误
|
|
177
|
-
if (filter(chunk) || str.toLowerCase().includes('error')) {
|
|
178
|
-
return originalStderrWrite!(chunk, encodingOrCallback as any, callback)
|
|
179
|
-
}
|
|
180
|
-
if (typeof encodingOrCallback === 'function') encodingOrCallback()
|
|
181
|
-
else if (callback) callback()
|
|
182
|
-
return true
|
|
183
|
-
}) as typeof process.stderr.write
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* 恢复输出
|
|
188
|
-
*/
|
|
189
|
-
function restoreOutput() {
|
|
190
|
-
if (originalStdoutWrite) {
|
|
191
|
-
process.stdout.write = originalStdoutWrite
|
|
192
|
-
originalStdoutWrite = null
|
|
193
|
-
}
|
|
194
|
-
if (originalStderrWrite) {
|
|
195
|
-
process.stderr.write = originalStderrWrite
|
|
196
|
-
originalStderrWrite = null
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* 渲染进度条
|
|
202
|
-
*/
|
|
203
|
-
function renderProgress(
|
|
204
|
-
percent: number,
|
|
205
|
-
transforms: string,
|
|
206
|
-
chunks: string,
|
|
207
|
-
width: number,
|
|
208
|
-
complete: string,
|
|
209
|
-
incomplete: string,
|
|
210
|
-
isFinal: boolean = false,
|
|
211
|
-
) {
|
|
212
|
-
if (!originalStderrWrite) return
|
|
213
|
-
|
|
214
|
-
// 非单行模式下,只在 10% 间隔输出进度,并附加时间节流避免多行显示相同耗时
|
|
215
|
-
if (!useSingleLineMode && !isFinal) {
|
|
216
|
-
const currentPercent = Math.floor(percent * 100)
|
|
217
|
-
const currentBucket = Math.floor(currentPercent / 10) * 10
|
|
218
|
-
const now = Date.now()
|
|
219
|
-
if (
|
|
220
|
-
currentBucket <= globalLastLoggedPercent ||
|
|
221
|
-
now - globalLastOutputTime < 500
|
|
222
|
-
) {
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
|
-
globalLastLoggedPercent = currentBucket
|
|
226
|
-
globalLastOutputTime = now
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const filled = Math.floor(percent * width)
|
|
230
|
-
const empty = width - filled
|
|
231
|
-
const bar = complete.repeat(filled) + incomplete.repeat(empty)
|
|
232
|
-
const percentStr = (percent * 100).toFixed(0).padStart(3, ' ')
|
|
233
|
-
const elapsed = ((Date.now() - globalStartTime) / 1000).toFixed(1)
|
|
234
|
-
|
|
235
|
-
// 格式: Building [...] 100% | Transforms: x/y | Chunks: x/y | Time: xs
|
|
236
|
-
// 单行模式使用回车覆盖,否则直接换行
|
|
237
|
-
const prefix = useSingleLineMode ? '\r\x1b[2K' : ''
|
|
238
|
-
const ending = isFinal || !useSingleLineMode ? '\n' : ''
|
|
239
|
-
const line = `${prefix}Building [${bar}] ${percentStr}% | Transforms: ${transforms} | Chunks: ${chunks} | Time: ${elapsed}s${ending}`
|
|
240
|
-
originalStderrWrite(line)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* 创建构建进度插件
|
|
245
|
-
*/
|
|
246
|
-
export function buildProgressPlugin(
|
|
247
|
-
options: BuildProgressOptions = {},
|
|
248
|
-
): Plugin {
|
|
249
|
-
const { width = 40, complete = '█', incomplete = '░' } = options
|
|
250
|
-
|
|
251
|
-
const cache = getCacheData()
|
|
252
|
-
const hasCache = cache.transformCount > 0
|
|
253
|
-
|
|
254
|
-
return {
|
|
255
|
-
name: 'tnotes-build-progress',
|
|
256
|
-
enforce: 'pre',
|
|
257
|
-
apply: 'build',
|
|
258
|
-
|
|
259
|
-
config(config, { command }) {
|
|
260
|
-
if (command === 'build') {
|
|
261
|
-
config.logLevel = 'silent'
|
|
262
|
-
|
|
263
|
-
if (!globalIsBuilding) {
|
|
264
|
-
globalIsBuilding = true
|
|
265
|
-
globalStartTime = Date.now()
|
|
266
|
-
globalTransformCount = 0
|
|
267
|
-
globalChunkCount = 0
|
|
268
|
-
globalHasError = false
|
|
269
|
-
globalLastPercent = 0
|
|
270
|
-
globalLastLoggedPercent = -1
|
|
271
|
-
globalLastOutputTime = 0
|
|
272
|
-
globalTransformEndTime = 0
|
|
273
|
-
globalLastHookTime = Date.now()
|
|
274
|
-
globalOutDir = config.build?.outDir || 'dist'
|
|
275
|
-
|
|
276
|
-
if (!hasCache) {
|
|
277
|
-
globalFileCount = countSourceFiles(process.cwd())
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
interceptOutput()
|
|
281
|
-
|
|
282
|
-
// 停滞填充计时器:当 Vite hook 超过 2 秒没触发时(如 VitePress 渲染页面阶段),
|
|
283
|
-
// 缓慢推进进度条,避免长时间卡住不动
|
|
284
|
-
globalStallTimer = setInterval(() => {
|
|
285
|
-
if (
|
|
286
|
-
!globalIsBuilding ||
|
|
287
|
-
globalLastPercent <= 0 ||
|
|
288
|
-
globalLastPercent >= 0.98
|
|
289
|
-
)
|
|
290
|
-
return
|
|
291
|
-
const now = Date.now()
|
|
292
|
-
if (now - globalLastHookTime < 2000) return
|
|
293
|
-
|
|
294
|
-
// 每次推进剩余距离的 2%,形成自然减速曲线
|
|
295
|
-
const remaining = 0.98 - globalLastPercent
|
|
296
|
-
globalLastPercent += remaining * 0.02
|
|
297
|
-
|
|
298
|
-
const transformsStr = hasCache
|
|
299
|
-
? `${globalTransformCount}/${cache.transformCount * 2}`
|
|
300
|
-
: `${globalTransformCount}`
|
|
301
|
-
const chunksStr = hasCache
|
|
302
|
-
? `${globalChunkCount}/${cache.chunkCount * 2}`
|
|
303
|
-
: `${globalChunkCount}`
|
|
304
|
-
|
|
305
|
-
renderProgress(
|
|
306
|
-
globalLastPercent,
|
|
307
|
-
transformsStr,
|
|
308
|
-
chunksStr,
|
|
309
|
-
width,
|
|
310
|
-
complete,
|
|
311
|
-
incomplete,
|
|
312
|
-
)
|
|
313
|
-
}, 1000)
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
},
|
|
317
|
-
|
|
318
|
-
transform(_code, _id) {
|
|
319
|
-
globalTransformCount++
|
|
320
|
-
globalLastHookTime = Date.now()
|
|
321
|
-
|
|
322
|
-
// 根据缓存的时间比例确定 transform 阶段在进度条中的权重
|
|
323
|
-
// 例如 transform 占总时间 37% → 进度条 0-36%,chunk 占 63% → 进度条 36-98%
|
|
324
|
-
const transformWeight =
|
|
325
|
-
hasCache && cache.transformEndRatio != null
|
|
326
|
-
? Math.max(0.1, Math.min(0.85, cache.transformEndRatio * 0.98))
|
|
327
|
-
: 0.85
|
|
328
|
-
|
|
329
|
-
if (hasCache) {
|
|
330
|
-
const totalTransforms = cache.transformCount * 2
|
|
331
|
-
globalLastPercent = Math.min(
|
|
332
|
-
transformWeight,
|
|
333
|
-
(globalTransformCount / totalTransforms) * transformWeight,
|
|
334
|
-
)
|
|
335
|
-
} else {
|
|
336
|
-
// 无缓存模式:渐近曲线平滑逼近上限
|
|
337
|
-
const k = 500
|
|
338
|
-
globalLastPercent = Math.min(
|
|
339
|
-
transformWeight,
|
|
340
|
-
(transformWeight * globalTransformCount) / (globalTransformCount + k),
|
|
341
|
-
)
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const transformsStr = hasCache
|
|
345
|
-
? `${globalTransformCount}/${cache.transformCount * 2}`
|
|
346
|
-
: `${globalTransformCount}`
|
|
347
|
-
const chunksStr = hasCache
|
|
348
|
-
? `${globalChunkCount}/${cache.chunkCount * 2}`
|
|
349
|
-
: `${globalChunkCount}`
|
|
350
|
-
|
|
351
|
-
renderProgress(
|
|
352
|
-
globalLastPercent,
|
|
353
|
-
transformsStr,
|
|
354
|
-
chunksStr,
|
|
355
|
-
width,
|
|
356
|
-
complete,
|
|
357
|
-
incomplete,
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
return null
|
|
361
|
-
},
|
|
362
|
-
|
|
363
|
-
renderChunk() {
|
|
364
|
-
globalChunkCount++
|
|
365
|
-
globalLastHookTime = Date.now()
|
|
366
|
-
|
|
367
|
-
// 记录 transform 阶段结束时间(第一次进入 renderChunk)
|
|
368
|
-
if (!globalTransformEndTime) {
|
|
369
|
-
globalTransformEndTime = Date.now()
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const transformWeight =
|
|
373
|
-
hasCache && cache.transformEndRatio != null
|
|
374
|
-
? Math.max(0.1, Math.min(0.85, cache.transformEndRatio * 0.98))
|
|
375
|
-
: 0.85
|
|
376
|
-
const chunkWeight = 0.98 - transformWeight
|
|
377
|
-
|
|
378
|
-
if (hasCache) {
|
|
379
|
-
const totalChunks = cache.chunkCount * 2
|
|
380
|
-
globalLastPercent = Math.min(
|
|
381
|
-
0.98,
|
|
382
|
-
transformWeight + (globalChunkCount / totalChunks) * chunkWeight,
|
|
383
|
-
)
|
|
384
|
-
} else {
|
|
385
|
-
globalLastPercent = Math.max(globalLastPercent, transformWeight)
|
|
386
|
-
globalLastPercent = Math.min(0.98, globalLastPercent + 0.00005)
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const transformsStr = hasCache
|
|
390
|
-
? `${globalTransformCount}/${cache.transformCount * 2}`
|
|
391
|
-
: `${globalTransformCount}`
|
|
392
|
-
const chunksStr = hasCache
|
|
393
|
-
? `${globalChunkCount}/${cache.chunkCount * 2}`
|
|
394
|
-
: `${globalChunkCount}`
|
|
395
|
-
|
|
396
|
-
renderProgress(
|
|
397
|
-
globalLastPercent,
|
|
398
|
-
transformsStr,
|
|
399
|
-
chunksStr,
|
|
400
|
-
width,
|
|
401
|
-
complete,
|
|
402
|
-
incomplete,
|
|
403
|
-
)
|
|
404
|
-
|
|
405
|
-
return null
|
|
406
|
-
},
|
|
407
|
-
|
|
408
|
-
buildEnd(err) {
|
|
409
|
-
if (err) {
|
|
410
|
-
globalHasError = true
|
|
411
|
-
}
|
|
412
|
-
},
|
|
413
|
-
|
|
414
|
-
closeBundle() {
|
|
415
|
-
// 延迟检测是否是最后一次 closeBundle
|
|
416
|
-
setTimeout(() => {
|
|
417
|
-
if (!globalIsBuilding) return
|
|
418
|
-
|
|
419
|
-
if (globalStallTimer) {
|
|
420
|
-
clearInterval(globalStallTimer)
|
|
421
|
-
globalStallTimer = null
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
const elapsed = ((Date.now() - globalStartTime) / 1000).toFixed(1)
|
|
425
|
-
|
|
426
|
-
// 显示 100%,带换行
|
|
427
|
-
// 100% 时应该显示总数/总数,而不是实际计数/总数
|
|
428
|
-
const totalTransforms = hasCache
|
|
429
|
-
? cache.transformCount * 2
|
|
430
|
-
: globalTransformCount
|
|
431
|
-
const totalChunks = hasCache ? cache.chunkCount * 2 : globalChunkCount
|
|
432
|
-
const transformsStr = `${totalTransforms}/${totalTransforms}`
|
|
433
|
-
const chunksStr = `${totalChunks}/${totalChunks}`
|
|
434
|
-
|
|
435
|
-
renderProgress(
|
|
436
|
-
1,
|
|
437
|
-
transformsStr,
|
|
438
|
-
chunksStr,
|
|
439
|
-
width,
|
|
440
|
-
complete,
|
|
441
|
-
incomplete,
|
|
442
|
-
true,
|
|
443
|
-
)
|
|
444
|
-
|
|
445
|
-
restoreOutput()
|
|
446
|
-
|
|
447
|
-
if (!globalHasError) {
|
|
448
|
-
const totalTime = Date.now() - globalStartTime
|
|
449
|
-
const transformTime = globalTransformEndTime
|
|
450
|
-
? globalTransformEndTime - globalStartTime
|
|
451
|
-
: totalTime
|
|
452
|
-
setCacheData({
|
|
453
|
-
transformCount: Math.floor(globalTransformCount / 2),
|
|
454
|
-
chunkCount: Math.floor(globalChunkCount / 2),
|
|
455
|
-
transformEndRatio: transformTime / totalTime,
|
|
456
|
-
})
|
|
457
|
-
|
|
458
|
-
console.log(`🔨 Rollup 打包完成`)
|
|
459
|
-
console.log(` 📁 输出目录: ${globalOutDir}`)
|
|
460
|
-
console.log(` ⏱️ 耗时: ${elapsed}s`)
|
|
461
|
-
console.log(` ⏳ VitePress 正在渲染页面...`)
|
|
462
|
-
} else {
|
|
463
|
-
console.log(`\n❌ 构建失败,请检查错误信息`)
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
globalIsBuilding = false
|
|
467
|
-
}, 500)
|
|
468
|
-
},
|
|
469
|
-
}
|
|
470
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* .vitepress/tnotes/vitepress/plugins/buildProgressPlugin.ts
|
|
3
|
+
*
|
|
4
|
+
* 构建进度插件 - 仅在 build 模式下显示真实进度
|
|
5
|
+
*
|
|
6
|
+
* 基于 vite-plugin-progress 源码简化实现
|
|
7
|
+
* https://github.com/jeddygong/vite-plugin-progress
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Plugin } from 'vite'
|
|
11
|
+
import {
|
|
12
|
+
existsSync,
|
|
13
|
+
readFileSync,
|
|
14
|
+
writeFileSync,
|
|
15
|
+
mkdirSync,
|
|
16
|
+
readdirSync,
|
|
17
|
+
} from 'fs'
|
|
18
|
+
import { join } from 'path'
|
|
19
|
+
|
|
20
|
+
/** 缓存目录和文件路径 */
|
|
21
|
+
const CACHE_DIR = join(process.cwd(), 'node_modules', '.tnotes-progress')
|
|
22
|
+
const CACHE_FILE = join(CACHE_DIR, 'build-cache.json')
|
|
23
|
+
|
|
24
|
+
/** 缓存数据结构 */
|
|
25
|
+
interface CacheData {
|
|
26
|
+
transformCount: number
|
|
27
|
+
chunkCount: number
|
|
28
|
+
/** transform 阶段耗时占总构建时间的比例(0-1),用于下次按时间权重分配进度 */
|
|
29
|
+
transformEndRatio?: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 插件配置选项 */
|
|
33
|
+
interface BuildProgressOptions {
|
|
34
|
+
width?: number
|
|
35
|
+
complete?: string
|
|
36
|
+
incomplete?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 读取缓存数据
|
|
41
|
+
*/
|
|
42
|
+
function getCacheData(): CacheData {
|
|
43
|
+
try {
|
|
44
|
+
if (existsSync(CACHE_FILE)) {
|
|
45
|
+
return JSON.parse(readFileSync(CACHE_FILE, 'utf-8'))
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
// 忽略错误
|
|
49
|
+
}
|
|
50
|
+
return { transformCount: 0, chunkCount: 0 }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 写入缓存数据
|
|
55
|
+
*/
|
|
56
|
+
function setCacheData(data: CacheData): void {
|
|
57
|
+
try {
|
|
58
|
+
if (!existsSync(CACHE_DIR)) {
|
|
59
|
+
mkdirSync(CACHE_DIR, { recursive: true })
|
|
60
|
+
}
|
|
61
|
+
writeFileSync(CACHE_FILE, JSON.stringify(data), 'utf-8')
|
|
62
|
+
} catch {
|
|
63
|
+
// 忽略错误
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 扫描源目录文件数量
|
|
69
|
+
*/
|
|
70
|
+
function countSourceFiles(srcDir: string): number {
|
|
71
|
+
let count = 0
|
|
72
|
+
const extensions = /\.(vue|ts|js|jsx|tsx|css|scss|sass|styl|less|md)$/i
|
|
73
|
+
|
|
74
|
+
const scan = (dir: string) => {
|
|
75
|
+
try {
|
|
76
|
+
const entries = readdirSync(dir, { withFileTypes: true })
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
const fullPath = join(dir, entry.name)
|
|
79
|
+
if (
|
|
80
|
+
entry.isDirectory() &&
|
|
81
|
+
!entry.name.startsWith('.') &&
|
|
82
|
+
entry.name !== 'node_modules'
|
|
83
|
+
) {
|
|
84
|
+
scan(fullPath)
|
|
85
|
+
} else if (entry.isFile() && extensions.test(entry.name)) {
|
|
86
|
+
count++
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// 忽略错误
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
scan(srcDir)
|
|
95
|
+
return count
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ============ 全局状态 ============
|
|
99
|
+
let globalStartTime = 0
|
|
100
|
+
let globalTransformCount = 0
|
|
101
|
+
let globalChunkCount = 0
|
|
102
|
+
let globalHasError = false
|
|
103
|
+
let globalIsBuilding = false
|
|
104
|
+
let globalOutDir = ''
|
|
105
|
+
let globalLastPercent = 0
|
|
106
|
+
let globalFileCount = 0
|
|
107
|
+
let globalLastLoggedPercent = -1 // 非 TTY 环境下上次输出的百分比区间,初始为 -1 以便第一次输出
|
|
108
|
+
let globalLastOutputTime = 0 // 非 TTY 环境下上次输出时间戳,用于时间节流
|
|
109
|
+
let globalTransformEndTime = 0 // transform 阶段结束时间戳
|
|
110
|
+
let globalLastHookTime = 0 // 最后一次 Vite hook 触发时间
|
|
111
|
+
let globalStallTimer: ReturnType<typeof setInterval> | null = null
|
|
112
|
+
|
|
113
|
+
/** 检测是否支持单行刷新(交互式终端) */
|
|
114
|
+
const isTTY = !!(process.stdout.isTTY && process.stderr.isTTY)
|
|
115
|
+
|
|
116
|
+
/** 检测是否在 CI 环境中运行 */
|
|
117
|
+
const isCI = !!(
|
|
118
|
+
process.env.CI ||
|
|
119
|
+
process.env.GITHUB_ACTIONS ||
|
|
120
|
+
process.env.GITLAB_CI ||
|
|
121
|
+
process.env.CIRCLECI ||
|
|
122
|
+
process.env.TRAVIS ||
|
|
123
|
+
process.env.JENKINS_URL
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
/** 是否使用单行刷新模式(TTY 且非 CI) */
|
|
127
|
+
const useSingleLineMode = isTTY && !isCI
|
|
128
|
+
|
|
129
|
+
// 保存原始输出函数
|
|
130
|
+
let originalStdoutWrite: typeof process.stdout.write | null = null
|
|
131
|
+
let originalStderrWrite: typeof process.stderr.write | null = null
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 拦截输出
|
|
135
|
+
*/
|
|
136
|
+
function interceptOutput() {
|
|
137
|
+
if (originalStdoutWrite) return
|
|
138
|
+
|
|
139
|
+
originalStdoutWrite = process.stdout.write.bind(process.stdout)
|
|
140
|
+
originalStderrWrite = process.stderr.write.bind(process.stderr)
|
|
141
|
+
|
|
142
|
+
const filter = (chunk: string | Uint8Array): boolean => {
|
|
143
|
+
const str = chunk.toString()
|
|
144
|
+
// 只允许我们的输出
|
|
145
|
+
return (
|
|
146
|
+
str.includes('🔨') ||
|
|
147
|
+
str.includes('✅ 构建成功') ||
|
|
148
|
+
str.includes('❌ 构建失败') ||
|
|
149
|
+
str.includes('📁') ||
|
|
150
|
+
str.includes('📊') ||
|
|
151
|
+
str.includes('📦') ||
|
|
152
|
+
str.includes('⏱️') ||
|
|
153
|
+
str.includes('\x1b[2K') // 允许清屏指令
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
process.stdout.write = ((
|
|
158
|
+
chunk: string | Uint8Array,
|
|
159
|
+
encodingOrCallback?: BufferEncoding | ((err?: Error | null) => void),
|
|
160
|
+
callback?: (err?: Error | null) => void,
|
|
161
|
+
): boolean => {
|
|
162
|
+
if (filter(chunk)) {
|
|
163
|
+
return originalStdoutWrite!(chunk, encodingOrCallback as any, callback)
|
|
164
|
+
}
|
|
165
|
+
if (typeof encodingOrCallback === 'function') encodingOrCallback()
|
|
166
|
+
else if (callback) callback()
|
|
167
|
+
return true
|
|
168
|
+
}) as typeof process.stdout.write
|
|
169
|
+
|
|
170
|
+
process.stderr.write = ((
|
|
171
|
+
chunk: string | Uint8Array,
|
|
172
|
+
encodingOrCallback?: BufferEncoding | ((err?: Error | null) => void),
|
|
173
|
+
callback?: (err?: Error | null) => void,
|
|
174
|
+
): boolean => {
|
|
175
|
+
const str = chunk.toString()
|
|
176
|
+
// 允许进度条和真正的错误
|
|
177
|
+
if (filter(chunk) || str.toLowerCase().includes('error')) {
|
|
178
|
+
return originalStderrWrite!(chunk, encodingOrCallback as any, callback)
|
|
179
|
+
}
|
|
180
|
+
if (typeof encodingOrCallback === 'function') encodingOrCallback()
|
|
181
|
+
else if (callback) callback()
|
|
182
|
+
return true
|
|
183
|
+
}) as typeof process.stderr.write
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 恢复输出
|
|
188
|
+
*/
|
|
189
|
+
function restoreOutput() {
|
|
190
|
+
if (originalStdoutWrite) {
|
|
191
|
+
process.stdout.write = originalStdoutWrite
|
|
192
|
+
originalStdoutWrite = null
|
|
193
|
+
}
|
|
194
|
+
if (originalStderrWrite) {
|
|
195
|
+
process.stderr.write = originalStderrWrite
|
|
196
|
+
originalStderrWrite = null
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 渲染进度条
|
|
202
|
+
*/
|
|
203
|
+
function renderProgress(
|
|
204
|
+
percent: number,
|
|
205
|
+
transforms: string,
|
|
206
|
+
chunks: string,
|
|
207
|
+
width: number,
|
|
208
|
+
complete: string,
|
|
209
|
+
incomplete: string,
|
|
210
|
+
isFinal: boolean = false,
|
|
211
|
+
) {
|
|
212
|
+
if (!originalStderrWrite) return
|
|
213
|
+
|
|
214
|
+
// 非单行模式下,只在 10% 间隔输出进度,并附加时间节流避免多行显示相同耗时
|
|
215
|
+
if (!useSingleLineMode && !isFinal) {
|
|
216
|
+
const currentPercent = Math.floor(percent * 100)
|
|
217
|
+
const currentBucket = Math.floor(currentPercent / 10) * 10
|
|
218
|
+
const now = Date.now()
|
|
219
|
+
if (
|
|
220
|
+
currentBucket <= globalLastLoggedPercent ||
|
|
221
|
+
now - globalLastOutputTime < 500
|
|
222
|
+
) {
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
globalLastLoggedPercent = currentBucket
|
|
226
|
+
globalLastOutputTime = now
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const filled = Math.floor(percent * width)
|
|
230
|
+
const empty = width - filled
|
|
231
|
+
const bar = complete.repeat(filled) + incomplete.repeat(empty)
|
|
232
|
+
const percentStr = (percent * 100).toFixed(0).padStart(3, ' ')
|
|
233
|
+
const elapsed = ((Date.now() - globalStartTime) / 1000).toFixed(1)
|
|
234
|
+
|
|
235
|
+
// 格式: Building [...] 100% | Transforms: x/y | Chunks: x/y | Time: xs
|
|
236
|
+
// 单行模式使用回车覆盖,否则直接换行
|
|
237
|
+
const prefix = useSingleLineMode ? '\r\x1b[2K' : ''
|
|
238
|
+
const ending = isFinal || !useSingleLineMode ? '\n' : ''
|
|
239
|
+
const line = `${prefix}Building [${bar}] ${percentStr}% | Transforms: ${transforms} | Chunks: ${chunks} | Time: ${elapsed}s${ending}`
|
|
240
|
+
originalStderrWrite(line)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* 创建构建进度插件
|
|
245
|
+
*/
|
|
246
|
+
export function buildProgressPlugin(
|
|
247
|
+
options: BuildProgressOptions = {},
|
|
248
|
+
): Plugin {
|
|
249
|
+
const { width = 40, complete = '█', incomplete = '░' } = options
|
|
250
|
+
|
|
251
|
+
const cache = getCacheData()
|
|
252
|
+
const hasCache = cache.transformCount > 0
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
name: 'tnotes-build-progress',
|
|
256
|
+
enforce: 'pre',
|
|
257
|
+
apply: 'build',
|
|
258
|
+
|
|
259
|
+
config(config, { command }) {
|
|
260
|
+
if (command === 'build') {
|
|
261
|
+
config.logLevel = 'silent'
|
|
262
|
+
|
|
263
|
+
if (!globalIsBuilding) {
|
|
264
|
+
globalIsBuilding = true
|
|
265
|
+
globalStartTime = Date.now()
|
|
266
|
+
globalTransformCount = 0
|
|
267
|
+
globalChunkCount = 0
|
|
268
|
+
globalHasError = false
|
|
269
|
+
globalLastPercent = 0
|
|
270
|
+
globalLastLoggedPercent = -1
|
|
271
|
+
globalLastOutputTime = 0
|
|
272
|
+
globalTransformEndTime = 0
|
|
273
|
+
globalLastHookTime = Date.now()
|
|
274
|
+
globalOutDir = config.build?.outDir || 'dist'
|
|
275
|
+
|
|
276
|
+
if (!hasCache) {
|
|
277
|
+
globalFileCount = countSourceFiles(process.cwd())
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
interceptOutput()
|
|
281
|
+
|
|
282
|
+
// 停滞填充计时器:当 Vite hook 超过 2 秒没触发时(如 VitePress 渲染页面阶段),
|
|
283
|
+
// 缓慢推进进度条,避免长时间卡住不动
|
|
284
|
+
globalStallTimer = setInterval(() => {
|
|
285
|
+
if (
|
|
286
|
+
!globalIsBuilding ||
|
|
287
|
+
globalLastPercent <= 0 ||
|
|
288
|
+
globalLastPercent >= 0.98
|
|
289
|
+
)
|
|
290
|
+
return
|
|
291
|
+
const now = Date.now()
|
|
292
|
+
if (now - globalLastHookTime < 2000) return
|
|
293
|
+
|
|
294
|
+
// 每次推进剩余距离的 2%,形成自然减速曲线
|
|
295
|
+
const remaining = 0.98 - globalLastPercent
|
|
296
|
+
globalLastPercent += remaining * 0.02
|
|
297
|
+
|
|
298
|
+
const transformsStr = hasCache
|
|
299
|
+
? `${globalTransformCount}/${cache.transformCount * 2}`
|
|
300
|
+
: `${globalTransformCount}`
|
|
301
|
+
const chunksStr = hasCache
|
|
302
|
+
? `${globalChunkCount}/${cache.chunkCount * 2}`
|
|
303
|
+
: `${globalChunkCount}`
|
|
304
|
+
|
|
305
|
+
renderProgress(
|
|
306
|
+
globalLastPercent,
|
|
307
|
+
transformsStr,
|
|
308
|
+
chunksStr,
|
|
309
|
+
width,
|
|
310
|
+
complete,
|
|
311
|
+
incomplete,
|
|
312
|
+
)
|
|
313
|
+
}, 1000)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
transform(_code, _id) {
|
|
319
|
+
globalTransformCount++
|
|
320
|
+
globalLastHookTime = Date.now()
|
|
321
|
+
|
|
322
|
+
// 根据缓存的时间比例确定 transform 阶段在进度条中的权重
|
|
323
|
+
// 例如 transform 占总时间 37% → 进度条 0-36%,chunk 占 63% → 进度条 36-98%
|
|
324
|
+
const transformWeight =
|
|
325
|
+
hasCache && cache.transformEndRatio != null
|
|
326
|
+
? Math.max(0.1, Math.min(0.85, cache.transformEndRatio * 0.98))
|
|
327
|
+
: 0.85
|
|
328
|
+
|
|
329
|
+
if (hasCache) {
|
|
330
|
+
const totalTransforms = cache.transformCount * 2
|
|
331
|
+
globalLastPercent = Math.min(
|
|
332
|
+
transformWeight,
|
|
333
|
+
(globalTransformCount / totalTransforms) * transformWeight,
|
|
334
|
+
)
|
|
335
|
+
} else {
|
|
336
|
+
// 无缓存模式:渐近曲线平滑逼近上限
|
|
337
|
+
const k = 500
|
|
338
|
+
globalLastPercent = Math.min(
|
|
339
|
+
transformWeight,
|
|
340
|
+
(transformWeight * globalTransformCount) / (globalTransformCount + k),
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const transformsStr = hasCache
|
|
345
|
+
? `${globalTransformCount}/${cache.transformCount * 2}`
|
|
346
|
+
: `${globalTransformCount}`
|
|
347
|
+
const chunksStr = hasCache
|
|
348
|
+
? `${globalChunkCount}/${cache.chunkCount * 2}`
|
|
349
|
+
: `${globalChunkCount}`
|
|
350
|
+
|
|
351
|
+
renderProgress(
|
|
352
|
+
globalLastPercent,
|
|
353
|
+
transformsStr,
|
|
354
|
+
chunksStr,
|
|
355
|
+
width,
|
|
356
|
+
complete,
|
|
357
|
+
incomplete,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
return null
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
renderChunk() {
|
|
364
|
+
globalChunkCount++
|
|
365
|
+
globalLastHookTime = Date.now()
|
|
366
|
+
|
|
367
|
+
// 记录 transform 阶段结束时间(第一次进入 renderChunk)
|
|
368
|
+
if (!globalTransformEndTime) {
|
|
369
|
+
globalTransformEndTime = Date.now()
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const transformWeight =
|
|
373
|
+
hasCache && cache.transformEndRatio != null
|
|
374
|
+
? Math.max(0.1, Math.min(0.85, cache.transformEndRatio * 0.98))
|
|
375
|
+
: 0.85
|
|
376
|
+
const chunkWeight = 0.98 - transformWeight
|
|
377
|
+
|
|
378
|
+
if (hasCache) {
|
|
379
|
+
const totalChunks = cache.chunkCount * 2
|
|
380
|
+
globalLastPercent = Math.min(
|
|
381
|
+
0.98,
|
|
382
|
+
transformWeight + (globalChunkCount / totalChunks) * chunkWeight,
|
|
383
|
+
)
|
|
384
|
+
} else {
|
|
385
|
+
globalLastPercent = Math.max(globalLastPercent, transformWeight)
|
|
386
|
+
globalLastPercent = Math.min(0.98, globalLastPercent + 0.00005)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const transformsStr = hasCache
|
|
390
|
+
? `${globalTransformCount}/${cache.transformCount * 2}`
|
|
391
|
+
: `${globalTransformCount}`
|
|
392
|
+
const chunksStr = hasCache
|
|
393
|
+
? `${globalChunkCount}/${cache.chunkCount * 2}`
|
|
394
|
+
: `${globalChunkCount}`
|
|
395
|
+
|
|
396
|
+
renderProgress(
|
|
397
|
+
globalLastPercent,
|
|
398
|
+
transformsStr,
|
|
399
|
+
chunksStr,
|
|
400
|
+
width,
|
|
401
|
+
complete,
|
|
402
|
+
incomplete,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
return null
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
buildEnd(err) {
|
|
409
|
+
if (err) {
|
|
410
|
+
globalHasError = true
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
closeBundle() {
|
|
415
|
+
// 延迟检测是否是最后一次 closeBundle
|
|
416
|
+
setTimeout(() => {
|
|
417
|
+
if (!globalIsBuilding) return
|
|
418
|
+
|
|
419
|
+
if (globalStallTimer) {
|
|
420
|
+
clearInterval(globalStallTimer)
|
|
421
|
+
globalStallTimer = null
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const elapsed = ((Date.now() - globalStartTime) / 1000).toFixed(1)
|
|
425
|
+
|
|
426
|
+
// 显示 100%,带换行
|
|
427
|
+
// 100% 时应该显示总数/总数,而不是实际计数/总数
|
|
428
|
+
const totalTransforms = hasCache
|
|
429
|
+
? cache.transformCount * 2
|
|
430
|
+
: globalTransformCount
|
|
431
|
+
const totalChunks = hasCache ? cache.chunkCount * 2 : globalChunkCount
|
|
432
|
+
const transformsStr = `${totalTransforms}/${totalTransforms}`
|
|
433
|
+
const chunksStr = `${totalChunks}/${totalChunks}`
|
|
434
|
+
|
|
435
|
+
renderProgress(
|
|
436
|
+
1,
|
|
437
|
+
transformsStr,
|
|
438
|
+
chunksStr,
|
|
439
|
+
width,
|
|
440
|
+
complete,
|
|
441
|
+
incomplete,
|
|
442
|
+
true,
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
restoreOutput()
|
|
446
|
+
|
|
447
|
+
if (!globalHasError) {
|
|
448
|
+
const totalTime = Date.now() - globalStartTime
|
|
449
|
+
const transformTime = globalTransformEndTime
|
|
450
|
+
? globalTransformEndTime - globalStartTime
|
|
451
|
+
: totalTime
|
|
452
|
+
setCacheData({
|
|
453
|
+
transformCount: Math.floor(globalTransformCount / 2),
|
|
454
|
+
chunkCount: Math.floor(globalChunkCount / 2),
|
|
455
|
+
transformEndRatio: transformTime / totalTime,
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
console.log(`🔨 Rollup 打包完成`)
|
|
459
|
+
console.log(` 📁 输出目录: ${globalOutDir}`)
|
|
460
|
+
console.log(` ⏱️ 耗时: ${elapsed}s`)
|
|
461
|
+
console.log(` ⏳ VitePress 正在渲染页面...`)
|
|
462
|
+
} else {
|
|
463
|
+
console.log(`\n❌ 构建失败,请检查错误信息`)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
globalIsBuilding = false
|
|
467
|
+
}, 500)
|
|
468
|
+
},
|
|
469
|
+
}
|
|
470
|
+
}
|