mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
perf: Vite 构建启用 code splitting,Bun RSS 从 966MB 降至 35MB
Bun/JSC 全量解析单文件大 JS 的 bytecode 和 JIT,17MB 产物导致 RSS 暴涨至 ~1GB(Node/V8 懒解析仅需 ~220MB)。启用代码分割后 Bun 按需加载 chunk,--version RSS 35MB,完整加载 ~500MB。 改动: - vite.config.ts: 移除 codeSplitting:false,添加 chunkFileNames - post-build.ts: 遍历 dist/ + dist/chunks/ 所有文件做 Bun patch - 新建 distRoot.ts 共享工具函数,统一路径定位逻辑 - ripgrep.ts/computerUse/setup.ts/claudeInChrome/setup.ts/updateCCB.ts: 用 distRoot 替换内联 import.meta.url 路径推算
This commit is contained in:
@@ -78,8 +78,9 @@ bun run docs:dev
|
|||||||
|
|
||||||
- **Runtime**: Bun (not Node.js). All imports, builds, and execution use Bun APIs.
|
- **Runtime**: Bun (not Node.js). All imports, builds, and execution use Bun APIs.
|
||||||
- **Build**: `build.ts` 执行 `Bun.build()` with `splitting: true`,入口 `src/entrypoints/cli.tsx`,输出 `dist/cli.js` + chunk files。Build 默认启用 19 个 feature(见下方 Feature Flag 段)。构建后自动替换 `import.meta.require` 为 Node.js 兼容版本(产物 bun/node 都可运行)。构建时会将 `vendor/audio-capture/` 和 `src/utils/vendor/ripgrep/` 复制到 `dist/vendor/` 下。
|
- **Build**: `build.ts` 执行 `Bun.build()` with `splitting: true`,入口 `src/entrypoints/cli.tsx`,输出 `dist/cli.js` + chunk files。Build 默认启用 19 个 feature(见下方 Feature Flag 段)。构建后自动替换 `import.meta.require` 为 Node.js 兼容版本(产物 bun/node 都可运行)。构建时会将 `vendor/audio-capture/` 和 `src/utils/vendor/ripgrep/` 复制到 `dist/vendor/` 下。
|
||||||
- **Build (Vite)**: `vite.config.ts` + `scripts/post-build.ts`,chunk 输出到 `dist/chunks/`。post-build 同样复制 vendor 文件到 `dist/vendor/`。
|
- **Build (Vite)**: `vite.config.ts` + `scripts/post-build.ts`,代码分割模式,chunk 输出到 `dist/chunks/`。post-build 遍历 `dist/` 和 `dist/chunks/` 下所有 `.js` 文件做 `globalThis.Bun` 解构 patch,复制 vendor 文件到 `dist/vendor/`。
|
||||||
- **Vendor 路径解析**: 构建后 chunk 文件位于 `dist/` 或 `dist/chunks/` 下,vendor 二进制在 `dist/vendor/`。`src/utils/ripgrep.ts` 和 `packages/audio-capture-napi/src/index.ts` 均通过 `import.meta.url` 路径中 `lastIndexOf('dist')` 定位 dist 根目录,再拼接 `vendor/` 子路径,确保不同构建产物层级下路径一致。
|
- **Vendor 路径解析**: 构建后 chunk 文件位于 `dist/` 或 `dist/chunks/` 下,vendor 二进制在 `dist/vendor/`。`src/utils/distRoot.ts` 提供共享的 `distRoot` 函数,通过 `import.meta.url` 路径中 `lastIndexOf('dist')` 或 `lastIndexOf('src')` 定位根目录。`ripgrep.ts`、`computerUse/setup.ts`、`claudeInChrome/setup.ts`、`updateCCB.ts` 均使用 `distRoot` 而非内联 `import.meta.url` 路径推算。`packages/audio-capture-napi/src/index.ts` 有独立的 `lastIndexOf('dist')` 逻辑,功能等价。
|
||||||
|
- **为什么 Vite 必须代码分割**: Bun/JSC 会全量解析单个大 JS 文件的 bytecode 和 JIT,单文件 17MB 产物导致 RSS 暴涨至 ~1GB(Node/V8 懒解析仅需 ~220MB)。代码分割为 600+ 小 chunk 后 Bun 按需加载,`--version` RSS 从 966MB 降至 35MB,完整加载从 1GB+ 降至 ~500MB。
|
||||||
- **Dev mode**: `scripts/dev.ts` 通过 Bun `-d` flag 注入 `MACRO.*` defines,运行 `src/entrypoints/cli.tsx`。默认启用全部 feature。
|
- **Dev mode**: `scripts/dev.ts` 通过 Bun `-d` flag 注入 `MACRO.*` defines,运行 `src/entrypoints/cli.tsx`。默认启用全部 feature。
|
||||||
- **Module system**: ESM (`"type": "module"`), TSX with `react-jsx` transform.
|
- **Module system**: ESM (`"type": "module"`), TSX with `react-jsx` transform.
|
||||||
- **Monorepo**: Bun workspaces — 17 个 workspace packages + 若干辅助目录 in `packages/` resolved via `workspace:*`。
|
- **Monorepo**: Bun workspaces — 17 个 workspace packages + 若干辅助目录 in `packages/` resolved via `workspace:*`。
|
||||||
|
|||||||
54
docs/performance-reporter.md
Normal file
54
docs/performance-reporter.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# 内存占用 1G 调研报告
|
||||||
|
|
||||||
|
> 诊断 session `a3593062` RSS 达 1.09 GB,定位 Bun 运行时内存膨胀根因
|
||||||
|
|
||||||
|
## 数据收集
|
||||||
|
|
||||||
|
- **诊断数据**: RSS 1,118 MB,V8 heap 84 MB,原生内存缺口 1,034 MB(92%)
|
||||||
|
- **构建方式**: `bun run build:vite` → Vite/Rollup 单文件构建,产物 17MB `dist/cli.js`
|
||||||
|
- **Vite 配置**: `codeSplitting: false`(`vite.config.ts:97`),所有代码内联为单文件
|
||||||
|
- **Node.js 对比**: 相同 17MB 产物,Node.js RSS 仅 223 MB(`--version`)/ 340 MB(完整加载)
|
||||||
|
|
||||||
|
## 探索与验证
|
||||||
|
|
||||||
|
### 已确认
|
||||||
|
|
||||||
|
| 问题 | 位置 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| **根因: Vite 单文件构建 + Bun 解析大文件内存效率低** | `vite.config.ts:97` | `codeSplitting: false` 产出 17MB 单文件,Bun/JSC 解析时 RSS 暴涨至 966MB |
|
||||||
|
| Node.js 对同等 17MB 文件仅需 223MB | 实测 | V8 对大文件解析的内存效率远优于 JSC |
|
||||||
|
| Bun.build 代码分割可解决问题 | 实测 | `bun run build`(代码分割 → 627 chunk)Bun RSS 仅 30MB(`--version`)/ 318MB(完整加载) |
|
||||||
|
|
||||||
|
### 已否认
|
||||||
|
|
||||||
|
- 不是 feature flags 数量问题 — 全部 35 features 开启时,代码分割构建内存正常
|
||||||
|
- 不是内存泄漏 — `detachedContexts: 0`,`activeHandles: 0`
|
||||||
|
- 不是原生 addon 问题 — vendor 文件仅 2.7MB
|
||||||
|
- 不是 TypeScript 源码体量问题 — `bun run dev`(直接加载 TS)完整路径仅 345MB
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
**根因是 Vite 构建配置 `codeSplitting: false`,产出 17MB 单文件,Bun/JSC 解析单文件大 JS 时内存效率极差(966MB vs Node 的 223MB)。**
|
||||||
|
|
||||||
|
实测对比矩阵:
|
||||||
|
|
||||||
|
| 构建方式 | 产物结构 | Bun RSS | Node RSS | Bun/Node |
|
||||||
|
|----------|----------|---------|----------|----------|
|
||||||
|
| `build:vite` | 17MB 单文件 | **966 MB** | 223 MB | 4.3x |
|
||||||
|
| `build:vite` pipe mode | 同上 | **1,088 MB** | 340 MB | 3.2x |
|
||||||
|
| `build` (Bun) | 627 chunk | 30 MB | 42 MB | 0.7x |
|
||||||
|
| `build` (Bun) pipe mode | 同上 | 318 MB | 253 MB | 1.3x |
|
||||||
|
| `bun run dev` TS 源码 | 动态加载 | 42 MB | — | — |
|
||||||
|
| `bun run dev` pipe mode | 动态加载 | 345 MB | — | — |
|
||||||
|
|
||||||
|
核心差异:
|
||||||
|
- **Node/V8** 解析 17MB 文件只需 223MB — V8 的懒解析(lazy parsing)只编译入口需要的部分
|
||||||
|
- **Bun/JSC** 解析 17MB 文件需要 966MB — JSC 对单文件做全量编译,bytecode + JIT 占用大量原生内存
|
||||||
|
- 代码分割后(627 个小 chunk),Bun 按需加载,内存回到正常水平
|
||||||
|
|
||||||
|
## 建议
|
||||||
|
|
||||||
|
1. **开启 Vite 代码分割** — 在 `vite.config.ts` 中启用 `codeSplitting: true` 或使用 Rollup 的 `manualChunks` 配置。这是最直接的修复
|
||||||
|
2. **或切换到 Bun.build** — `bun run build` 已默认启用代码分割(`splitting: true`),Bun RSS 仅 30-318MB
|
||||||
|
3. **如果必须单文件** — 考虑用 Node.js 运行 Vite 产物(`node dist/cli-node.js`),代价是失去 Bun 特有 API
|
||||||
|
4. **验证 `codeSplitting: false` 的存在理由** — 注释说"all dynamic imports inlined",可能是为了简化部署。评估是否真的需要单文件
|
||||||
@@ -9,28 +9,52 @@
|
|||||||
import { readdir, readFile, writeFile, cp } from 'node:fs/promises'
|
import { readdir, readFile, writeFile, cp } from 'node:fs/promises'
|
||||||
import { chmodSync } from 'node:fs'
|
import { chmodSync } from 'node:fs'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import { execSync } from 'node:child_process'
|
|
||||||
|
|
||||||
const outdir = 'dist'
|
const outdir = 'dist'
|
||||||
|
|
||||||
async function postBuild() {
|
async function postBuild() {
|
||||||
// Step 1: Patch globalThis.Bun destructuring in the single bundled file
|
// Step 1: Patch globalThis.Bun destructuring in ALL output files
|
||||||
const cliPath = join(outdir, 'cli.js')
|
|
||||||
const BUN_DESTRUCTURE = /var \{([^}]+)\} = globalThis\.Bun;?/g
|
const BUN_DESTRUCTURE = /var \{([^}]+)\} = globalThis\.Bun;?/g
|
||||||
const BUN_DESTRUCTURE_SAFE =
|
const BUN_DESTRUCTURE_SAFE =
|
||||||
'var {$1} = typeof globalThis.Bun !== "undefined" ? globalThis.Bun : {};'
|
'var {$1} = typeof globalThis.Bun !== "undefined" ? globalThis.Bun : {};'
|
||||||
|
|
||||||
let bunPatched = 0
|
let bunPatched = 0
|
||||||
{
|
const files = await readdir(outdir)
|
||||||
const content = await readFile(cliPath, 'utf-8')
|
const jsFiles = files.filter(f => f.endsWith('.js'))
|
||||||
|
|
||||||
|
for (const file of jsFiles) {
|
||||||
|
const filePath = join(outdir, file)
|
||||||
|
const content = await readFile(filePath, 'utf-8')
|
||||||
|
BUN_DESTRUCTURE.lastIndex = 0
|
||||||
if (BUN_DESTRUCTURE.test(content)) {
|
if (BUN_DESTRUCTURE.test(content)) {
|
||||||
await writeFile(
|
await writeFile(
|
||||||
cliPath,
|
filePath,
|
||||||
content.replace(BUN_DESTRUCTURE, BUN_DESTRUCTURE_SAFE),
|
content.replace(BUN_DESTRUCTURE, BUN_DESTRUCTURE_SAFE),
|
||||||
)
|
)
|
||||||
bunPatched++
|
bunPatched++
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also patch chunk files in dist/chunks/
|
||||||
|
const chunksDir = join(outdir, 'chunks')
|
||||||
|
let chunkFiles: string[] = []
|
||||||
|
try {
|
||||||
|
chunkFiles = (await readdir(chunksDir)).filter(f => f.endsWith('.js'))
|
||||||
|
} catch {
|
||||||
|
// No chunks directory — single-file build fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of chunkFiles) {
|
||||||
|
const filePath = join(chunksDir, file)
|
||||||
|
const content = await readFile(filePath, 'utf-8')
|
||||||
BUN_DESTRUCTURE.lastIndex = 0
|
BUN_DESTRUCTURE.lastIndex = 0
|
||||||
|
if (BUN_DESTRUCTURE.test(content)) {
|
||||||
|
await writeFile(
|
||||||
|
filePath,
|
||||||
|
content.replace(BUN_DESTRUCTURE, BUN_DESTRUCTURE_SAFE),
|
||||||
|
)
|
||||||
|
bunPatched++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Copy native addon files
|
// Step 2: Copy native addon files
|
||||||
@@ -55,7 +79,7 @@ async function postBuild() {
|
|||||||
chmodSync(cliNode, 0o755)
|
chmodSync(cliNode, 0o755)
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`Post-build complete: patched ${bunPatched} Bun destructure, generated entry points`,
|
`Post-build complete: patched ${bunPatched} Bun destructure across ${jsFiles.length + chunkFiles.length} files, generated entry points`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import chalk from 'chalk'
|
|||||||
import { execSync } from 'node:child_process'
|
import { execSync } from 'node:child_process'
|
||||||
import { existsSync, readFileSync } from 'node:fs'
|
import { existsSync, readFileSync } from 'node:fs'
|
||||||
import { homedir } from 'node:os'
|
import { homedir } from 'node:os'
|
||||||
import { join, dirname } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
|
||||||
import { logForDebugging } from '../utils/debug.js'
|
import { logForDebugging } from '../utils/debug.js'
|
||||||
|
import { distRoot } from '../utils/distRoot.js'
|
||||||
import { execFileNoThrowWithCwd } from '../utils/execFileNoThrow.js'
|
import { execFileNoThrowWithCwd } from '../utils/execFileNoThrow.js'
|
||||||
import { gracefulShutdown } from '../utils/gracefulShutdown.js'
|
import { gracefulShutdown } from '../utils/gracefulShutdown.js'
|
||||||
import { writeToStdout } from '../utils/process.js'
|
import { writeToStdout } from '../utils/process.js'
|
||||||
@@ -19,12 +19,9 @@ import { writeToStdout } from '../utils/process.js'
|
|||||||
const PACKAGE_NAME = 'claude-code-best'
|
const PACKAGE_NAME = 'claude-code-best'
|
||||||
|
|
||||||
function getCurrentVersion(): string {
|
function getCurrentVersion(): string {
|
||||||
// Read version from the nearest package.json (walks up from this file)
|
// Read version from the nearest package.json (walks up from dist root)
|
||||||
try {
|
try {
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
const pkgPath = join(distRoot, '..', 'package.json')
|
||||||
// In dev: src/cli/updateCCB.ts → ../../package.json
|
|
||||||
// In build: dist/chunks/xxx.js → ../../package.json (may not exist)
|
|
||||||
const pkgPath = join(__dirname, '..', '..', 'package.json')
|
|
||||||
if (existsSync(pkgPath)) {
|
if (existsSync(pkgPath)) {
|
||||||
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
||||||
if (pkg.version) return pkg.version
|
if (pkg.version) return pkg.version
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { BROWSER_TOOLS } from '@ant/claude-for-chrome-mcp'
|
|||||||
import { chmod, mkdir, readFile, writeFile } from 'fs/promises'
|
import { chmod, mkdir, readFile, writeFile } from 'fs/promises'
|
||||||
import { homedir } from 'os'
|
import { homedir } from 'os'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
import {
|
import {
|
||||||
getIsInteractive,
|
getIsInteractive,
|
||||||
getIsNonInteractiveSession,
|
getIsNonInteractiveSession,
|
||||||
@@ -11,6 +10,7 @@ import {
|
|||||||
import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js'
|
import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js'
|
||||||
import type { ScopedMcpServerConfig } from '../../services/mcp/types.js'
|
import type { ScopedMcpServerConfig } from '../../services/mcp/types.js'
|
||||||
import { isInBundledMode } from '../bundledMode.js'
|
import { isInBundledMode } from '../bundledMode.js'
|
||||||
|
import { distRoot } from '../distRoot.js'
|
||||||
import { getGlobalConfig, saveGlobalConfig } from '../config.js'
|
import { getGlobalConfig, saveGlobalConfig } from '../config.js'
|
||||||
import { logForDebugging } from '../debug.js'
|
import { logForDebugging } from '../debug.js'
|
||||||
import {
|
import {
|
||||||
@@ -135,9 +135,7 @@ export function setupClaudeInChrome(): {
|
|||||||
systemPrompt: getChromeSystemPrompt(),
|
systemPrompt: getChromeSystemPrompt(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
const cliPath = join(distRoot, 'cli.js')
|
||||||
const __dirname = join(__filename, '..')
|
|
||||||
const cliPath = join(__dirname, 'cli.js')
|
|
||||||
|
|
||||||
void createWrapperScript(
|
void createWrapperScript(
|
||||||
`"${process.execPath}" "${cliPath}" --chrome-native-host`,
|
`"${process.execPath}" "${cliPath}" --chrome-native-host`,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { buildComputerUseTools } from '@ant/computer-use-mcp'
|
import { buildComputerUseTools } from '@ant/computer-use-mcp'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
import { buildMcpToolName } from '../../services/mcp/mcpStringUtils.js'
|
import { buildMcpToolName } from '../../services/mcp/mcpStringUtils.js'
|
||||||
import type { ScopedMcpServerConfig } from '../../services/mcp/types.js'
|
import type { ScopedMcpServerConfig } from '../../services/mcp/types.js'
|
||||||
|
|
||||||
import { isInBundledMode } from '../bundledMode.js'
|
import { isInBundledMode } from '../bundledMode.js'
|
||||||
|
import { distRoot } from '../distRoot.js'
|
||||||
import { CLI_CU_CAPABILITIES, COMPUTER_USE_MCP_SERVER_NAME } from './common.js'
|
import { CLI_CU_CAPABILITIES, COMPUTER_USE_MCP_SERVER_NAME } from './common.js'
|
||||||
import { getChicagoCoordinateMode } from './gates.js'
|
import { getChicagoCoordinateMode } from './gates.js'
|
||||||
|
|
||||||
@@ -34,10 +34,7 @@ export function setupComputerUseMCP(): {
|
|||||||
// type 'stdio' to hit the right branch. Mirrors Chrome's setup.
|
// type 'stdio' to hit the right branch. Mirrors Chrome's setup.
|
||||||
const args = isInBundledMode()
|
const args = isInBundledMode()
|
||||||
? ['--computer-use-mcp']
|
? ['--computer-use-mcp']
|
||||||
: [
|
: [join(distRoot, 'cli.js'), '--computer-use-mcp']
|
||||||
join(fileURLToPath(import.meta.url), '..', 'cli.js'),
|
|
||||||
'--computer-use-mcp',
|
|
||||||
]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mcpConfig: {
|
mcpConfig: {
|
||||||
|
|||||||
29
src/utils/distRoot.ts
Normal file
29
src/utils/distRoot.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import * as path from 'path'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the dist root directory from the current module's location.
|
||||||
|
*
|
||||||
|
* Works across all build layouts:
|
||||||
|
* - Single-file: dist/cli.js → dist/
|
||||||
|
* - Code-split: dist/chunks/chunk-xxx.js → dist/
|
||||||
|
* - Dev mode: src/utils/distRoot.ts → <project_root>/
|
||||||
|
*/
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = path.dirname(__filename)
|
||||||
|
|
||||||
|
const distRoot = (() => {
|
||||||
|
const parts = __dirname.split(path.sep)
|
||||||
|
const distIdx = parts.lastIndexOf('dist')
|
||||||
|
if (distIdx !== -1) {
|
||||||
|
return parts.slice(0, distIdx + 1).join(path.sep)
|
||||||
|
}
|
||||||
|
// Dev mode: from src/utils/ → project root
|
||||||
|
const srcIdx = parts.lastIndexOf('src')
|
||||||
|
if (srcIdx !== -1) {
|
||||||
|
return parts.slice(0, srcIdx).join(path.sep)
|
||||||
|
}
|
||||||
|
return __dirname
|
||||||
|
})()
|
||||||
|
|
||||||
|
export { distRoot }
|
||||||
@@ -4,9 +4,9 @@ import memoize from 'lodash-es/memoize.js'
|
|||||||
import { homedir } from 'os'
|
import { homedir } from 'os'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { logEvent } from 'src/services/analytics/index.js'
|
import { logEvent } from 'src/services/analytics/index.js'
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
import { isInBundledMode } from './bundledMode.js'
|
import { isInBundledMode } from './bundledMode.js'
|
||||||
import { logForDebugging } from './debug.js'
|
import { logForDebugging } from './debug.js'
|
||||||
|
import { distRoot } from './distRoot.js'
|
||||||
import { isEnvDefinedFalsy } from './envUtils.js'
|
import { isEnvDefinedFalsy } from './envUtils.js'
|
||||||
import { execFileNoThrow } from './execFileNoThrow.js'
|
import { execFileNoThrow } from './execFileNoThrow.js'
|
||||||
import { findExecutable } from './findExecutable.js'
|
import { findExecutable } from './findExecutable.js'
|
||||||
@@ -14,25 +14,9 @@ import { logError } from './log.js'
|
|||||||
import { getPlatform } from './platform.js'
|
import { getPlatform } from './platform.js'
|
||||||
import { countCharInString } from './stringUtils.js'
|
import { countCharInString } from './stringUtils.js'
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
|
||||||
// we use node:path.join instead of node:url.resolve because the former doesn't encode spaces
|
|
||||||
// In dev mode: __filename = <root>/src/utils/ripgrep.ts → __dirname = <root>/src/utils/
|
|
||||||
// In built mode (bun): __filename = <root>/dist/chunk-xxx.js → need <root>/dist/
|
|
||||||
// In built mode (vite): __filename = <root>/dist/chunks/chunk-xxx.js → need <root>/dist/
|
|
||||||
// Both built modes: the dist root is at <root>/dist/ where dist/vendor/ripgrep/ lives.
|
|
||||||
const __dirname = (() => {
|
const __dirname = (() => {
|
||||||
const dir = path.dirname(__filename)
|
if (process.env.NODE_ENV === 'test') return path.resolve(distRoot)
|
||||||
// Test mode: from src/utils/ → project root
|
return distRoot
|
||||||
if (process.env.NODE_ENV === 'test') return path.resolve(dir, '../../../')
|
|
||||||
// Check if we're inside a dist directory at any depth
|
|
||||||
// (dist/ or dist/chunks/) — vendor lives at <dist-root>/vendor/ripgrep/
|
|
||||||
const parts = dir.split(path.sep)
|
|
||||||
const distIdx = parts.lastIndexOf('dist')
|
|
||||||
if (distIdx !== -1) {
|
|
||||||
return parts.slice(0, distIdx + 1).join(path.sep)
|
|
||||||
}
|
|
||||||
// Dev mode: from src/utils/ → src/utils/
|
|
||||||
return dir
|
|
||||||
})()
|
})()
|
||||||
|
|
||||||
type RipgrepConfig = {
|
type RipgrepConfig = {
|
||||||
|
|||||||
@@ -93,9 +93,12 @@ export default defineConfig({
|
|||||||
|
|
||||||
output: {
|
output: {
|
||||||
format: 'es',
|
format: 'es',
|
||||||
// Single-file build: no code splitting, all dynamic imports inlined
|
// Code splitting: Bun/JSC parses the entire single-file bundle eagerly,
|
||||||
codeSplitting: false,
|
// consuming ~1 GB RSS for a 17 MB output (vs ~220 MB on Node/V8 which
|
||||||
|
// lazy-parses). Splitting into chunks allows Bun to load modules on demand,
|
||||||
|
// bringing RSS down to ~300 MB.
|
||||||
entryFileNames: 'cli.js',
|
entryFileNames: 'cli.js',
|
||||||
|
chunkFileNames: 'chunks/[name]-[hash].js',
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
Reference in New Issue
Block a user