fix: reduce desktop package size and loose files
Exclude validation outputs from desktop packages, ship renderer-only dependencies through Vite bundles, and omit DS Web type declarations, source maps, and PDB symbols. Preserve runtime assets and licenses without changing dependency versions. Validate builder filters, production build and type checks, Windows packaged runtime smoke paths, and App page loading. Record measured size reductions and remaining validation limits in BUILD.md. Release note: 减少桌面安装包中的重复依赖和非运行文件,降低安装后的磁盘占用与零散文件数量。
This commit is contained in:
@@ -182,6 +182,30 @@ npm run dist:linux:arm64
|
||||
已由 Vite 打包的 `@antv/g6` 归类为构建期依赖,不会再作为独立 Node 模块复制进桌面
|
||||
安装包。
|
||||
|
||||
Desktop packaging includes only `out/main`, `out/preload`, and `out/renderer`;
|
||||
validation builds elsewhere under `out` are not shipped. Renderer-only React,
|
||||
Xterm, Mermaid, Fabric, Quill, i18next, and Markdown/math dependencies are build-time
|
||||
dependencies: Vite ships their browser bundles, while the existing license
|
||||
resources remain included. Install development dependencies before building;
|
||||
do not prune them before bundling and copying license resources.
|
||||
|
||||
The desktop DS Web resource filter excludes JavaScript/TypeScript source maps,
|
||||
`.d.ts` / `.d.mts` / `.d.cts` declarations, and `.pdb` debug symbols. It preserves
|
||||
the installed dependency layout, plugin configuration, browser assets, licenses,
|
||||
native binaries, and non-code `.map` assets. This filter does not change the
|
||||
separately built remote Agent or Runtime payloads.
|
||||
|
||||
Windows x64 development validation on 2026-09-28 compared the existing 0.15.2
|
||||
portable directory (excluding `data`) with a fresh unpacked test build:
|
||||
`app.asar` decreased from 609.4 to 350.5 MiB, and DS Web decreased from 28,701
|
||||
files / 463.4 MiB to 14,984 files / 357.0 MiB. Total measured size decreased from
|
||||
1,758.3 to 1,438.4 MiB despite the test build retaining 45.6 MiB more Electron
|
||||
locales than the portable build. These are unpacked sizes, not installer download
|
||||
sizes. Builder-copy regression tests, production build/type checks, packaged
|
||||
Harness/OpenCode/npm smoke checks, DS Web authentication/page scripts/workspace
|
||||
creation, and packaged App/settings/notes-page loading passed. Full editor/canvas
|
||||
interaction and other platform packages were not revalidated in this check.
|
||||
|
||||
以上 `dist*` 与 `portable` 命令用于普通本地桌面打包。它们会携带
|
||||
`agent-runtime-lock.json`、`remote-runtime-lock.json` 与公开的
|
||||
`agent-release-keys.json`,但所有桌面构建路径(包括本地 `portable`)都不会嵌入
|
||||
|
||||
Generated
+327
-34
File diff suppressed because it is too large
Load Diff
+27
-19
@@ -76,14 +76,22 @@
|
||||
"npmRebuild": false,
|
||||
"compression": "maximum",
|
||||
"files": [
|
||||
"out/**/*",
|
||||
"out/main/**/*",
|
||||
"out/preload/**/*",
|
||||
"out/renderer/**/*",
|
||||
"package.json",
|
||||
"!node_modules/npm{,/**/*}"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
"from": ".runtime-resources/dsh-${arch}/node_modules",
|
||||
"to": "runtimes/dsh/node_modules"
|
||||
"to": "runtimes/dsh/node_modules",
|
||||
"filter": [
|
||||
"**/*",
|
||||
"!**/*.{js,mjs,cjs,ts,mts,cts}.map",
|
||||
"!**/*.d.{ts,mts,cts}",
|
||||
"!**/*.pdb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "resources/skills",
|
||||
@@ -332,37 +340,20 @@
|
||||
"@opencode-ai/sdk": "1.18.29",
|
||||
"@vscode/ripgrep": "1.18.0",
|
||||
"@wecom/aibot-node-sdk": "^1.0.6",
|
||||
"@xterm/addon-fit": "0.11.0",
|
||||
"@xterm/addon-search": "0.16.0",
|
||||
"@xterm/addon-webgl": "0.19.0",
|
||||
"@xterm/xterm": "6.0.0",
|
||||
"cross-spawn": "^7.0.6",
|
||||
"dingtalk-stream": "^2.1.6-beta.1",
|
||||
"dompurify": "^3.4.13",
|
||||
"fflate": "^0.8.3",
|
||||
"fabric": "7.4.0",
|
||||
"html-to-text": "^10.0.0",
|
||||
"i18next": "^25.10.10",
|
||||
"json5": "^2.2.3",
|
||||
"jspdf": "4.2.1",
|
||||
"katex": "^0.16.47",
|
||||
"koffi": "3.1.4",
|
||||
"lucide-react": "^1.27.0",
|
||||
"mermaid": "^11.16.1",
|
||||
"node-pty": "^1.2.0-beta.15",
|
||||
"npm": "11.19.0",
|
||||
"onnxruntime-web": "^1.23.2",
|
||||
"pdfjs-dist": "^6.2.108",
|
||||
"ppu-paddle-ocr": "^6.4.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"quill": "^2.0.3",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-i18next": "^16.6.6",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
"sherpa-onnx": "1.13.4",
|
||||
"ssh2": "^1.17.0",
|
||||
"undici": "^7.29.0",
|
||||
@@ -387,17 +378,34 @@
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/ssh2": "^1.15.5",
|
||||
"@vitejs/plugin-react": "^5.2.0",
|
||||
"@xterm/addon-fit": "0.11.0",
|
||||
"@xterm/addon-search": "0.16.0",
|
||||
"@xterm/addon-webgl": "0.19.0",
|
||||
"@xterm/xterm": "6.0.0",
|
||||
"electron": "^43.2.0",
|
||||
"electron-builder": "^26.15.3",
|
||||
"electron-vite": "^5.0.0",
|
||||
"esbuild": "0.25.12",
|
||||
"eslint": "^10.8.0",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"fabric": "7.4.0",
|
||||
"globals": "^17.8.0",
|
||||
"i18next": "^25.10.10",
|
||||
"jsdom": "^30.0.1",
|
||||
"katex": "^0.16.47",
|
||||
"lucide-react": "^1.27.0",
|
||||
"mermaid": "^11.16.1",
|
||||
"opencode-ai": "1.18.29",
|
||||
"png-to-ico": "^3.0.2",
|
||||
"pngjs": "^7.0.0",
|
||||
"quill": "^2.0.3",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-i18next": "^16.6.6",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
"tar": "7.5.22",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.65.0",
|
||||
|
||||
@@ -8890,7 +8890,11 @@ function App(): React.JSX.Element {
|
||||
}}
|
||||
>
|
||||
<span className="conversation-item__primary">
|
||||
{conversation.pinned && <Pin className="conversation-pin" size={13} role="img" aria-label={t("conversation.actions.pinned")}><title>{t("conversation.actions.pinned")}</title></Pin>}
|
||||
{conversation.pinned && (
|
||||
<span className="conversation-pin">
|
||||
<Pin size={13} role="img" aria-label={t("conversation.actions.pinned")}><title>{t("conversation.actions.pinned")}</title></Pin>
|
||||
</span>
|
||||
)}
|
||||
{branchSourceTitle && (
|
||||
<ConversationBranchBadge
|
||||
sourceTitle={branchSourceTitle}
|
||||
|
||||
@@ -12554,7 +12554,13 @@ textarea.field-control {
|
||||
}
|
||||
|
||||
.conversation-pin {
|
||||
display: inline-flex;
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: var(--space-1);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
|
||||
@@ -810,7 +810,9 @@ describe('release build arguments', () => {
|
||||
)
|
||||
|
||||
expect(packageJson.build.files).toEqual([
|
||||
'out/**/*',
|
||||
'out/main/**/*',
|
||||
'out/preload/**/*',
|
||||
'out/renderer/**/*',
|
||||
'package.json',
|
||||
'!node_modules/npm{,/**/*}'
|
||||
])
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @vitest-environment node
|
||||
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
@@ -13,6 +13,27 @@ const { getFileMatchers, copyFiles } = require('app-builder-lib/out/fileMatcher'
|
||||
const { verifyOpenCodeConfig, verifyHarnessBundleImports } = require('../build/verify-desktop-runtimes.cjs')
|
||||
|
||||
describe('desktop Runtime packaging', () => {
|
||||
it('ships only production output even when validation builds remain in out', async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'goodbuddy-production-output-'))
|
||||
const output = join(root, 'output')
|
||||
const included = ['out/main/index.js', 'out/main/chunks/shared.js', 'out/main/obsidian-mcpvault/server.js', 'out/preload/index.cjs', 'out/renderer/index.html', 'package.json']
|
||||
const excluded = ['out/chat-paste-validation/renderer/index.html', 'out/another-probe/main.js']
|
||||
try {
|
||||
for (const file of [...included, ...excluded]) {
|
||||
mkdirSync(dirname(join(root, file)), { recursive: true })
|
||||
writeFileSync(join(root, file), file)
|
||||
}
|
||||
await copyFiles(getFileMatchers({ files: packageJson.build.files }, 'files', output, {
|
||||
defaultSrc: root, globalOutDir: output, customBuildOptions: {},
|
||||
macroExpander: (value: string) => value
|
||||
}))
|
||||
for (const file of included) expect(existsSync(join(output, file)), file).toBe(true)
|
||||
for (const file of excluded) expect(existsSync(join(output, file)), file).toBe(false)
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('runs packaged Windows Runtime checks in CI before removing the unpacked output', () => {
|
||||
const release = readFileSync(join(process.cwd(), 'build', 'build-release.cjs'), 'utf8')
|
||||
const smoke = readFileSync(join(process.cwd(), 'build', 'run-packaged-deepseek-harness-smoke.cjs'), 'utf8')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mkdirSync, mkdtempSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
@@ -12,9 +12,29 @@ it('copies DS dependencies and nested assets through the actual builder matcher'
|
||||
const root = mkdtempSync(join(tmpdir(), 'goodbuddy-dsh-packaging-'))
|
||||
const config = JSON.parse(readFileSync(resolve('package.json'), 'utf8'))
|
||||
const resource = config.build.extraResources.find((entry: { to: string }) => entry.to.startsWith('runtimes/dsh'))
|
||||
const files = ['@deepseek-ai/dsh/lib/bin.js', '@deepseek-ai/dsh/node_modules/example/index.js', '@deepseek-ai/dsh-web/dist/index.html']
|
||||
const files = [
|
||||
'@deepseek-ai/dsh/lib/bin.js',
|
||||
'@deepseek-ai/dsh/node_modules/example/index.js',
|
||||
'@deepseek-ai/dsh-web/dist/index.html',
|
||||
'@deepseek-ai/dsh-web/config.yml',
|
||||
'@deepseek-ai/dsh-web/LICENSE',
|
||||
'native/addon.node',
|
||||
'native/helper.exe',
|
||||
'native/helper.dll',
|
||||
'assets/data.map'
|
||||
]
|
||||
const excluded = [
|
||||
'@deepseek-ai/dsh/lib/bin.js.map',
|
||||
'@deepseek-ai/dsh/node_modules/example/index.d.ts',
|
||||
'example/index.d.mts',
|
||||
'example/index.d.cts',
|
||||
'example/index.d.ts.map',
|
||||
'example/index.mjs.map',
|
||||
'example/index.cjs.map',
|
||||
'native/helper.pdb'
|
||||
]
|
||||
try {
|
||||
for (const file of files) {
|
||||
for (const file of [...files, ...excluded]) {
|
||||
const source = join(root, '.runtime-resources/dsh-x64/node_modules', file)
|
||||
mkdirSync(dirname(source), { recursive: true })
|
||||
writeFileSync(source, file)
|
||||
@@ -30,6 +50,9 @@ it('copies DS dependencies and nested assets through the actual builder matcher'
|
||||
for (const file of files) {
|
||||
expect(readFileSync(join(destination, 'runtimes/dsh/node_modules', file), 'utf8')).toBe(file)
|
||||
}
|
||||
for (const file of excluded) {
|
||||
expect(existsSync(join(destination, 'runtimes/dsh/node_modules', file)), file).toBe(false)
|
||||
}
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user