perf: 精简打包体积,砍掉从未被使用的 Piper 引擎和渲染层专用依赖

排查确认两处真实浪费:

1. resources/tts/piper/ 里除 espeak-ng-data 外的所有文件(Piper 神经
   语音模型、二进制、自带 onnxruntime、phonemize 库、tashkeel 阿拉伯语
   音标模型,合计 ~113M)从未被实际调用——detectBackend() 第一优先级
   永远命中 VITS(小雅),Piper 引擎分支代码走不到,只有它 bundle 里的
   espeak-ng-data 目录被 VITS 借用当 sherpa-onnx 校验的 dataDir。
   systemTts.ts 新增 findPiperEspeakDataDir(),VITS 分支改为只依赖这一
   个子目录,不再要求 Piper 引擎的 bin/model/config 存在。

2. electron-builder.config.ts 的 files 补充排除 onnxruntime-web(
   @huggingface/transformers 的硬依赖但项目强制走 onnxruntime-node,
   完全未用)、naive-ui/date-fns/highlight.js/lodash 系(只在
   src/renderer/ 被引用,electron-vite build 时已被 Vite 打包进
   out/renderer/*.js,主进程不需要原始 node_modules),约省 227M。

两项合计预计单包体积从 ~789M 降到 ~560M 左右,待真机重新打包安装、
实际听一次语音播报确认功能未受影响。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ssurui 2026-08-23 00:19:14 +08:00
parent b512857629
commit d512ad41f9
16 changed files with 40 additions and 495 deletions

View File

@ -28,7 +28,22 @@ const config: Configuration = {
},
// resources/icon.png 单独列出:src/main/index.ts 用相对路径 __dirname/../../resources/icon.png
// 定位窗口图标(打包后 __dirname 是 <app>/out/main),必须让它落在 <app>/resources/ 下
files: ['out/**/*', 'resources/icon.png'],
//
// 排除项:这几个包只在 src/renderer/ 被引用,electron-vite build 时已经被 Vite
// 静态打包进 out/renderer/*.js,主进程运行时不会去 node_modules 里 require 它们;
// onnxruntime-web 是 @huggingface/transformers 的硬依赖但项目强制走 onnxruntime-node,
// 从未被使用。排除后单包体积减少约 227M(naive-ui/date-fns/highlight.js/lodash 系
// 渲染层已打包、onnxruntime-web 完全未用,详见排查记录)。
files: [
'out/**/*',
'resources/icon.png',
'!node_modules/onnxruntime-web/**',
'!node_modules/naive-ui/**',
'!node_modules/date-fns/**',
'!node_modules/highlight.js/**',
'!node_modules/lodash/**',
'!node_modules/lodash-es/**'
],
asarUnpack: [
'node_modules/@lancedb/**',
'node_modules/apache-arrow/**',

Binary file not shown.

Binary file not shown.

View File

@ -1,487 +0,0 @@
{
"audio": {
"sample_rate": 22050,
"quality": "medium"
},
"espeak": {
"voice": "cmn"
},
"inference": {
"noise_scale": 0.667,
"length_scale": 1,
"noise_w": 0.8
},
"phoneme_type": "espeak",
"phoneme_map": {},
"phoneme_id_map": {
"_": [
0
],
"^": [
1
],
"$": [
2
],
" ": [
3
],
"!": [
4
],
"'": [
5
],
"(": [
6
],
")": [
7
],
",": [
8
],
"-": [
9
],
".": [
10
],
":": [
11
],
";": [
12
],
"?": [
13
],
"a": [
14
],
"b": [
15
],
"c": [
16
],
"d": [
17
],
"e": [
18
],
"f": [
19
],
"h": [
20
],
"i": [
21
],
"j": [
22
],
"k": [
23
],
"l": [
24
],
"m": [
25
],
"n": [
26
],
"o": [
27
],
"p": [
28
],
"q": [
29
],
"r": [
30
],
"s": [
31
],
"t": [
32
],
"u": [
33
],
"v": [
34
],
"w": [
35
],
"x": [
36
],
"y": [
37
],
"z": [
38
],
"æ": [
39
],
"ç": [
40
],
"ð": [
41
],
"ø": [
42
],
"ħ": [
43
],
"ŋ": [
44
],
"œ": [
45
],
"ǀ": [
46
],
"ǁ": [
47
],
"ǂ": [
48
],
"ǃ": [
49
],
"ɐ": [
50
],
"ɑ": [
51
],
"ɒ": [
52
],
"ɓ": [
53
],
"ɔ": [
54
],
"ɕ": [
55
],
"ɖ": [
56
],
"ɗ": [
57
],
"ɘ": [
58
],
"ə": [
59
],
"ɚ": [
60
],
"ɛ": [
61
],
"ɜ": [
62
],
"ɞ": [
63
],
"ɟ": [
64
],
"ɠ": [
65
],
"ɡ": [
66
],
"ɢ": [
67
],
"ɣ": [
68
],
"ɤ": [
69
],
"ɥ": [
70
],
"ɦ": [
71
],
"ɧ": [
72
],
"ɨ": [
73
],
"ɪ": [
74
],
"ɫ": [
75
],
"ɬ": [
76
],
"ɭ": [
77
],
"ɮ": [
78
],
"ɯ": [
79
],
"ɰ": [
80
],
"ɱ": [
81
],
"ɲ": [
82
],
"ɳ": [
83
],
"ɴ": [
84
],
"ɵ": [
85
],
"ɶ": [
86
],
"ɸ": [
87
],
"ɹ": [
88
],
"ɺ": [
89
],
"ɻ": [
90
],
"ɽ": [
91
],
"ɾ": [
92
],
"ʀ": [
93
],
"ʁ": [
94
],
"ʂ": [
95
],
"ʃ": [
96
],
"ʄ": [
97
],
"ʈ": [
98
],
"ʉ": [
99
],
"ʊ": [
100
],
"ʋ": [
101
],
"ʌ": [
102
],
"ʍ": [
103
],
"ʎ": [
104
],
"ʏ": [
105
],
"ʐ": [
106
],
"ʑ": [
107
],
"ʒ": [
108
],
"ʔ": [
109
],
"ʕ": [
110
],
"ʘ": [
111
],
"ʙ": [
112
],
"ʛ": [
113
],
"ʜ": [
114
],
"ʝ": [
115
],
"ʟ": [
116
],
"ʡ": [
117
],
"ʢ": [
118
],
"ʲ": [
119
],
"ˈ": [
120
],
"ˌ": [
121
],
"ː": [
122
],
"ˑ": [
123
],
"˞": [
124
],
"β": [
125
],
"θ": [
126
],
"χ": [
127
],
"ᵻ": [
128
],
"ⱱ": [
129
],
"0": [
130
],
"1": [
131
],
"2": [
132
],
"3": [
133
],
"4": [
134
],
"5": [
135
],
"6": [
136
],
"7": [
137
],
"8": [
138
],
"9": [
139
],
"̧": [
140
],
"̃": [
141
],
"̪": [
142
],
"̯": [
143
],
"̩": [
144
],
"ʰ": [
145
],
"ˤ": [
146
],
"ε": [
147
],
"↓": [
148
],
"#": [
149
],
"\"": [
150
],
"↑": [
151
]
},
"num_symbols": 256,
"num_speakers": 1,
"speaker_id_map": {},
"piper_version": "1.0.0",
"language": {
"code": "zh_CN",
"family": "zh",
"region": "CN",
"name_native": "简体中文",
"name_english": "Chinese",
"country_english": "China"
},
"dataset": "huayan"
}

Binary file not shown.

Binary file not shown.

View File

@ -122,6 +122,25 @@ function findBundledVits(): string | null {
return null
}
/**
* VITS(小雅)的 sherpa-onnx 校验要求指定一个 espeak-ng-data 目录,历史上直接复用
* Piper bundle 里的那份数据。Piper 自己的引擎(模型/二进制/onnxruntime/phonemize)
* 在有 VITS 时永远不会被实际调用(detectBackend 第一优先级永远命中 VITS),
* 所以只保留 piper/espeak-ng-data 这一个子目录,不要求整套 Piper 引擎存在。
*/
function findPiperEspeakDataDir(): string | null {
const candidates = [
join(process.resourcesPath, 'tts', 'piper'),
join(__dirname, '..', '..', 'resources', 'tts', 'piper'),
process.env.TTS_BUNDLE_DIR ? join(process.env.TTS_BUNDLE_DIR, 'piper') : ''
].filter(Boolean)
for (const dir of candidates) {
if (existsSync(join(dir, 'espeak-ng-data'))) return dir
}
return null
}
function findBundledEspeakNg(): string | null {
const candidates = [
join(process.resourcesPath, 'tts', 'espeak-ng'),
@ -303,15 +322,13 @@ function detectBackend(): Promise<Backend | null> {
if (detectedBackendPromise) return detectedBackendPromise
detectedBackendPromise = (async () => {
// 1. 优先使用内嵌 sherpa-onnx VITS 小雅模型(BZNSYP 标准普通话)。
// 依赖 Piper bundle 里的 espeak-ng-data 作为 dataDir(sherpa-onnx 校验需要)。
// 依赖 Piper bundle 里的 espeak-ng-data 作为 dataDir(sherpa-onnx 校验需要),
// 不需要 Piper 引擎本身(bin/model/config),见 findPiperEspeakDataDir 注释。
const bundledVits = findBundledVits()
const piperForData = findBundledPiper()
if (bundledVits && piperForData) {
const piperDataDir = findPiperEspeakDataDir()
if (bundledVits && piperDataDir) {
bundledVitsDir = bundledVits
bundledPiperBin = piperForData.bin
bundledPiperDir = piperForData.dir
bundledPiperModel = piperForData.model
bundledPiperConfig = piperForData.config
bundledPiperDir = piperDataDir
detectedBackend = 'bundled-vits'
console.log('[systemTts] detected backend:', detectedBackend, 'at', bundledVitsDir)
return detectedBackend