mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-19 06:45:50 +00:00
fix: 将 highlight.js 改为静态导入以兼容 Bun --compile 模式
- cliHighlight.ts: 使用静态 import 替换 dynamic import('highlight.js'),
因为编译模式下模块解析指向内部 bunfs 路径导致无法找到
- color-diff-napi/src/index.ts: 同样改为静态导入,移除 createRequire 延迟加载
This commit is contained in:
@@ -17,32 +17,21 @@
|
|||||||
* getSyntaxTheme always returns the default for the given Claude theme.
|
* getSyntaxTheme always returns the default for the given Claude theme.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createRequire } from 'node:module'
|
|
||||||
import { diffArrays } from 'diff'
|
import { diffArrays } from 'diff'
|
||||||
import type * as hljsNamespace from 'highlight.js'
|
import hljs from 'highlight.js'
|
||||||
import { basename, extname } from 'path'
|
import { basename, extname } from 'path'
|
||||||
|
|
||||||
// createRequire works in both Bun and Node.js ESM contexts.
|
// Static import — createRequire(import.meta.url) fails in Bun --compile mode
|
||||||
// Needed because this package is "type": "module" but uses require() for
|
// because the resolved path points to the internal bunfs binary path where
|
||||||
// lazy loading — bare require is not available in Node.js ESM.
|
// node_modules cannot be found. A top-level import ensures the module is
|
||||||
const nodeRequire = createRequire(import.meta.url)
|
// bundled and accessible at runtime.
|
||||||
|
type HLJSApi = typeof hljs
|
||||||
// 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
|
|
||||||
let cachedHljs: HLJSApi | null = null
|
let cachedHljs: HLJSApi | null = null
|
||||||
function hljs(): HLJSApi {
|
function hljsApi(): HLJSApi {
|
||||||
if (cachedHljs) return cachedHljs
|
if (cachedHljs) return cachedHljs
|
||||||
const mod = nodeRequire('highlight.js')
|
|
||||||
// highlight.js uses `export =` (CJS). Under bun/ESM the interop wraps it
|
// 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.
|
// 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
|
cachedHljs = 'default' in mod && mod.default ? mod.default : mod
|
||||||
return cachedHljs!
|
return cachedHljs!
|
||||||
}
|
}
|
||||||
@@ -441,9 +430,9 @@ function detectLanguage(
|
|||||||
// Filename-based lookup (handles Dockerfile, Makefile, CMakeLists.txt, etc.)
|
// Filename-based lookup (handles Dockerfile, Makefile, CMakeLists.txt, etc.)
|
||||||
const stem = base.split('.')[0] ?? ''
|
const stem = base.split('.')[0] ?? ''
|
||||||
const byName = FILENAME_LANGS[base] ?? FILENAME_LANGS[stem]
|
const byName = FILENAME_LANGS[base] ?? FILENAME_LANGS[stem]
|
||||||
if (byName && hljs().getLanguage(byName)) return byName
|
if (byName && hljsApi().getLanguage(byName)) return byName
|
||||||
if (ext) {
|
if (ext) {
|
||||||
const lang = hljs().getLanguage(ext)
|
const lang = hljsApi().getLanguage(ext)
|
||||||
if (lang) return ext
|
if (lang) return ext
|
||||||
}
|
}
|
||||||
// Shebang / first-line detection (strip UTF-8 BOM)
|
// Shebang / first-line detection (strip UTF-8 BOM)
|
||||||
@@ -525,7 +514,7 @@ function highlightLine(
|
|||||||
}
|
}
|
||||||
let result
|
let result
|
||||||
try {
|
try {
|
||||||
result = hljs().highlight(code, {
|
result = hljsApi().highlight(code, {
|
||||||
language: state.lang,
|
language: state.lang,
|
||||||
ignoreIllegals: true,
|
ignoreIllegals: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
// highlight.js's type defs carry `/// <reference lib="dom" />`. SSETransport,
|
// highlight.js's type defs carry `/// <reference lib="dom" />`. SSETransport,
|
||||||
// mcp/client, ssh, dumpPrompts use DOM types (TextDecodeOptions, RequestInfo)
|
// mcp/client, ssh, dumpPrompts use DOM types (TextDecodeOptions, RequestInfo)
|
||||||
// that only typecheck because this file's `typeof import('highlight.js')` pulls
|
// that only typecheck because the hljs import below pulls lib.dom in.
|
||||||
// lib.dom in. tsconfig has lib: ["ESNext"] only — fixing the actual DOM-type
|
// tsconfig has lib: ["ESNext"] only — this ref preserves the status quo.
|
||||||
// deps is a separate sweep; this ref preserves the status quo.
|
|
||||||
/// <reference lib="dom" />
|
/// <reference lib="dom" />
|
||||||
|
|
||||||
import { extname } from 'path'
|
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 = {
|
export type CliHighlight = {
|
||||||
highlight: typeof import('cli-highlight').highlight
|
highlight: typeof import('cli-highlight').highlight
|
||||||
@@ -13,9 +15,6 @@ export type CliHighlight = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// One promise shared by Fallback.tsx, markdown.ts, events.ts, getLanguageName.
|
// 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<CliHighlight | null> | undefined
|
let cliHighlightPromise: Promise<CliHighlight | null> | undefined
|
||||||
|
|
||||||
let loadedGetLanguage: ((name: string) => { name: string } | undefined) | 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<CliHighlight | null> {
|
async function loadCliHighlight(): Promise<CliHighlight | null> {
|
||||||
try {
|
try {
|
||||||
const cliHighlight = await import('cli-highlight')
|
const cliHighlight = await import('cli-highlight')
|
||||||
// cache hit — cli-highlight already loaded highlight.js
|
// highlight.js CJS interop: `export =` wraps in .default under ESM
|
||||||
const highlightJs = await import('highlight.js')
|
const hljsMod = hljs as { getLanguage?: typeof loadedGetLanguage; default?: typeof hljs }
|
||||||
loadedGetLanguage = (highlightJs as { getLanguage?: typeof loadedGetLanguage }).getLanguage
|
loadedGetLanguage = hljsMod.getLanguage ?? hljsMod.default?.getLanguage
|
||||||
return {
|
return {
|
||||||
highlight: cliHighlight.highlight,
|
highlight: cliHighlight.highlight,
|
||||||
supportsLanguage: cliHighlight.supportsLanguage,
|
supportsLanguage: cliHighlight.supportsLanguage,
|
||||||
|
|||||||
Reference in New Issue
Block a user