mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-19 16:28:28 +00:00
perf(cli): code-split lowlight to cut startup V8 parse cost
Move the syntax-highlight engine out of the synchronously-parsed cli.js
entry into a separately-emitted chunk and load it via dynamic import on
the first code-block render. Until the chunk arrives, code blocks render
as plain text; the next React commit of the surrounding subtree picks up
the highlighted version, so users never see incorrect highlighting –
just an imperceptibly later transition for the very first code block.
Mechanics:
- esbuild config: switch entry to outdir + splitting:true so that
`await import('lowlight')` produces an actual on-disk chunk that's
only parsed by V8 when first needed.
- esbuild-shims: rename injected __dirname/__filename to qwen-prefixed
symbols + use `define` to redirect free references. Previous inject
collided with vendored libraries (yargs) that ship their own
`var __dirname` ESM-compat polyfill once splitting flattens chunks.
- prepare-package: include the new chunks/ directory in the published
package's files list.
- CodeColorizer: keep the public colorize{Code,Line} signatures and HAST
rendering identical; on first call when the chunk hasn't loaded it
returns the plain line and fires the dynamic import via a tiny
standalone loader module.
- lowlightLoader (new): isolates the lazy-load surface to a module with
zero transitive imports (no themeManager, settings, or core). This
lets test-setup prime the cache without dragging the whole UI module
graph into every test file, which was observed to perturb theme and
settings test outcomes when CodeColorizer was imported directly.
- test-setup: await loadLowlight() once via the standalone loader so
synchronous snapshot tests see the highlighted output deterministically.
Measurements (real $HOME, n=15 interleaved A/B vs main HEAD, macOS):
| Metric | Before (mean±sd ms) | After (mean±sd ms) | Δ | t | p |
| ------------------ | ------------------- | ------------------ | -------- | ------ | -------- |
| firstByte (wall) | 1633.5 ± 88.7 | 1475.8 ± 73.3 | -157.7 | 5.31 | 1.33e-5 |
| idle (wall) | 2048.7 ± 93.6 | 1902.3 ± 80.2 | -146.3 | 4.60 | 8.71e-5 |
| cli.js size | 25 MB | 6.9 MB | -18.1 MB | — | — |
Both metrics clear the +50ms-or-10% Welch's t-test bar by an order of
magnitude. cli.js drops 72%; total payload (cli.js + chunks/) is
similar but only cli.js is parsed at module-eval time, which is the
phase that dominates the user-visible startup gap.
How to validate:
npm run bundle
ls dist/ # cli.js + chunks/lowlight-*.js
node dist/cli.js -y # interactive UI still renders
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
51ee87539c
commit
c201ba2fcb
7 changed files with 107 additions and 17 deletions
|
|
@ -39,6 +39,7 @@ const TARGETS = new Map([
|
|||
const DIST_REQUIRED_PATHS = ['cli.js', 'vendor', 'bundled/qc-helper/docs'];
|
||||
const DIST_ALLOWED_ENTRIES = new Set([
|
||||
'cli.js',
|
||||
'chunks',
|
||||
'vendor',
|
||||
'bundled',
|
||||
'package.json',
|
||||
|
|
|
|||
|
|
@ -5,25 +5,27 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* Shims for esbuild ESM bundles to support require() calls
|
||||
* This file is injected into the bundle via esbuild's inject option
|
||||
* Shims for esbuild ESM bundles.
|
||||
*
|
||||
* With code-splitting enabled, the inject is applied per-chunk and the
|
||||
* exported bindings cannot collide with `var __dirname` polyfills that
|
||||
* vendored libraries (e.g. yargs) emit in their own ESM compat layers.
|
||||
* To stay collision-free, this file exposes prefixed names; the build
|
||||
* config uses esbuild `define` to rewrite free `__dirname` / `__filename`
|
||||
* references in source to these prefixed identifiers, while leaving
|
||||
* vendor-declared locals untouched.
|
||||
*/
|
||||
|
||||
import { createRequire } from 'node:module';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
|
||||
// Create require function for the current module and make it global
|
||||
const _require = createRequire(import.meta.url);
|
||||
|
||||
// Make require available globally for dynamic requires
|
||||
if (typeof globalThis.require === 'undefined') {
|
||||
globalThis.require = _require;
|
||||
}
|
||||
|
||||
// Export for esbuild injection
|
||||
export const require = _require;
|
||||
|
||||
// Setup __filename and __dirname for compatibility
|
||||
export const __filename = fileURLToPath(import.meta.url);
|
||||
export const __dirname = dirname(__filename);
|
||||
export const __qwen_filename = fileURLToPath(import.meta.url);
|
||||
export const __qwen_dirname = dirname(__qwen_filename);
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ const distPackageJson = {
|
|||
},
|
||||
files: [
|
||||
'cli.js',
|
||||
'chunks',
|
||||
'vendor',
|
||||
'*.sb',
|
||||
'README.md',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue