From 51a3a83f0751eb034a3f0ef1ff323bae4edd0d80 Mon Sep 17 00:00:00 2001 From: Bot Date: Thu, 23 Apr 2026 18:47:31 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=B0=86=20highlight.js=20=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E9=9D=99=E6=80=81=E5=AF=BC=E5=85=A5=E4=BB=A5=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=20Bun=20--compile=20=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cliHighlight.ts: 使用静态 import 替换 dynamic import('highlight.js'), 因为编译模式下模块解析指向内部 bunfs 路径导致无法找到 - color-diff-napi/src/index.ts: 同样改为静态导入,移除 createRequire 延迟加载 --- packages/color-diff-napi/src/index.ts | 33 +++++++++------------------ src/utils/cliHighlight.ts | 17 +++++++------- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/packages/color-diff-napi/src/index.ts b/packages/color-diff-napi/src/index.ts index 9b662b6d1..692728e2a 100644 --- a/packages/color-diff-napi/src/index.ts +++ b/packages/color-diff-napi/src/index.ts @@ -17,32 +17,21 @@ * getSyntaxTheme always returns the default for the given Claude theme. */ -import { createRequire } from 'node:module' import { diffArrays } from 'diff' -import type * as hljsNamespace from 'highlight.js' +import hljs from 'highlight.js' import { basename, extname } from 'path' -// createRequire works in both Bun and Node.js ESM contexts. -// Needed because this package is "type": "module" but uses require() for -// lazy loading — bare require is not available in Node.js ESM. -const nodeRequire = createRequire(import.meta.url) - -// Lazy: defers loading highlight.js until first render. The full bundle -// registers 190+ language grammars at require time (~50MB, 100-200ms on -// macOS, several× that on Windows). With a top-level import, any caller -// chunk that reaches this module — including test/preload.ts via -// StructuredDiff.tsx → colorDiff.ts — pays that cost at module-eval time -// and carries the heap for the rest of the process. On Windows CI this -// pushed later tests in the same shard into GC-pause territory and a -// beforeEach/afterEach hook timeout (officialRegistry.test.ts, PR #24150). -// Same lazy pattern the NAPI wrapper used for dlopen. -type HLJSApi = typeof hljsNamespace.default +// Static import — createRequire(import.meta.url) fails in Bun --compile mode +// because the resolved path points to the internal bunfs binary path where +// node_modules cannot be found. A top-level import ensures the module is +// bundled and accessible at runtime. +type HLJSApi = typeof hljs let cachedHljs: HLJSApi | null = null -function hljs(): HLJSApi { +function hljsApi(): HLJSApi { if (cachedHljs) return cachedHljs - const mod = nodeRequire('highlight.js') // highlight.js uses `export =` (CJS). Under bun/ESM the interop wraps it // in .default; under node CJS the module IS the API. Check at runtime. + const mod = hljs as HLJSApi & { default?: HLJSApi } cachedHljs = 'default' in mod && mod.default ? mod.default : mod return cachedHljs! } @@ -441,9 +430,9 @@ function detectLanguage( // Filename-based lookup (handles Dockerfile, Makefile, CMakeLists.txt, etc.) const stem = base.split('.')[0] ?? '' const byName = FILENAME_LANGS[base] ?? FILENAME_LANGS[stem] - if (byName && hljs().getLanguage(byName)) return byName + if (byName && hljsApi().getLanguage(byName)) return byName if (ext) { - const lang = hljs().getLanguage(ext) + const lang = hljsApi().getLanguage(ext) if (lang) return ext } // Shebang / first-line detection (strip UTF-8 BOM) @@ -525,7 +514,7 @@ function highlightLine( } let result try { - result = hljs().highlight(code, { + result = hljsApi().highlight(code, { language: state.lang, ignoreIllegals: true, }) diff --git a/src/utils/cliHighlight.ts b/src/utils/cliHighlight.ts index e87663c1f..3b248a045 100644 --- a/src/utils/cliHighlight.ts +++ b/src/utils/cliHighlight.ts @@ -1,11 +1,13 @@ // highlight.js's type defs carry `/// `. SSETransport, // mcp/client, ssh, dumpPrompts use DOM types (TextDecodeOptions, RequestInfo) -// that only typecheck because this file's `typeof import('highlight.js')` pulls -// lib.dom in. tsconfig has lib: ["ESNext"] only — fixing the actual DOM-type -// deps is a separate sweep; this ref preserves the status quo. +// that only typecheck because the hljs import below pulls lib.dom in. +// tsconfig has lib: ["ESNext"] only — this ref preserves the status quo. /// import { extname } from 'path' +// Static import — dynamic import('highlight.js') fails in Bun --compile mode +// because module resolution points to the internal bunfs binary path. +import hljs from 'highlight.js' export type CliHighlight = { highlight: typeof import('cli-highlight').highlight @@ -13,9 +15,6 @@ export type CliHighlight = { } // One promise shared by Fallback.tsx, markdown.ts, events.ts, getLanguageName. -// The highlight.js import piggybacks: cli-highlight has already pulled it into -// the module cache, so the second import() is a cache hit — no extra bytes -// faulted in. let cliHighlightPromise: Promise | undefined let loadedGetLanguage: ((name: string) => { name: string } | undefined) | undefined @@ -23,9 +22,9 @@ let loadedGetLanguage: ((name: string) => { name: string } | undefined) | undefi async function loadCliHighlight(): Promise { try { const cliHighlight = await import('cli-highlight') - // cache hit — cli-highlight already loaded highlight.js - const highlightJs = await import('highlight.js') - loadedGetLanguage = (highlightJs as { getLanguage?: typeof loadedGetLanguage }).getLanguage + // highlight.js CJS interop: `export =` wraps in .default under ESM + const hljsMod = hljs as { getLanguage?: typeof loadedGetLanguage; default?: typeof hljs } + loadedGetLanguage = hljsMod.getLanguage ?? hljsMod.default?.getLanguage return { highlight: cliHighlight.highlight, supportsLanguage: cliHighlight.supportsLanguage,