mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-10-26 16:55:08 +00:00
fix(web): speed up lazy bundle loads in the Web preview
The Web preview Worker converted multi-megabyte combo scripts and source maps with the buffer package's JavaScript UTF-8 codec, and the page re-encoded each source map to Base64 in JavaScript before running the bundle. Use TextEncoder/TextDecoder for whole-buffer UTF-8 conversions and FileReader for the inline source-map data URL.
This commit is contained in:
1 parent
b04c1c3261
commit
4b1d4c814e
4 files changed
+98
-11
No files matched your search
@@ -109,16 +109,18 @@ const REFUSAL_STATUS = 500
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
const SOURCE_MAP_TRAILER = /\/\/# sourceMappingURL=([^\r\n]+)\s*$/
|
||||
const BASE64_CHUNK_BYTES = 32 * 1024
|
||||
|
||||
/** Encode UTF-8 text for an inline data URL without a call-stack-sized spread. */
|
||||
function base64(value: string): string {
|
||||
const bytes = encoder.encode(value)
|
||||
let binary = ''
|
||||
for (let offset = 0; offset < bytes.length; offset += BASE64_CHUNK_BYTES) {
|
||||
binary += String.fromCharCode(...bytes.subarray(offset, offset + BASE64_CHUNK_BYTES))
|
||||
}
|
||||
return btoa(binary)
|
||||
/**
|
||||
* Encode source-map bytes as a Base64 data URL with the browser's native encoder.
|
||||
* DevTools cannot load a `blob:` source map, so the map must be inline.
|
||||
*/
|
||||
function sourceMapDataUrl(bytes: ArrayBuffer): Promise<string> {
|
||||
const reader = new FileReader()
|
||||
return new Promise((resolve, reject) => {
|
||||
reader.addEventListener('load', () => { resolve(reader.result as string) }, { once: true })
|
||||
reader.addEventListener('error', () => { reject(reader.error ?? new Error('source map encoding failed')) }, { once: true })
|
||||
reader.readAsDataURL(new Blob([bytes], { type: 'application/json;charset=utf-8' }))
|
||||
})
|
||||
}
|
||||
|
||||
/** Replace a tunnel-only map reference with a self-contained Base64 data URL. */
|
||||
@@ -128,7 +130,7 @@ async function localizeSourceMap(source: string, bundleUrl: string, fetch: Tunne
|
||||
try {
|
||||
const response = await fetch(new URL(match[1], new URL(bundleUrl, globalThis.location.origin)))
|
||||
if (!response.ok) return source.replace(SOURCE_MAP_TRAILER, '')
|
||||
const dataUrl = `data:application/json;charset=utf-8;base64,${base64(await response.text())}`
|
||||
const dataUrl = await sourceMapDataUrl(await response.arrayBuffer())
|
||||
return source.replace(SOURCE_MAP_TRAILER, `//# sourceMappingURL=${dataUrl}`)
|
||||
} catch {
|
||||
// A source map is diagnostic-only; its transport failure must not prevent
|
||||
|
||||
+32
@@ -2,9 +2,41 @@
|
||||
* `node:buffer` for the worker, backed by the `buffer` npm package (feross), and
|
||||
* the matching `globalThis.Buffer` install. Node code treats Buffer as ambient,
|
||||
* so the global must exist before any host module evaluates.
|
||||
*
|
||||
* Whole-buffer UTF-8 conversions, `Buffer.from(string)` and `buf.toString()`,
|
||||
* use the browser's `TextEncoder` and `TextDecoder`: they apply Node's U+FFFD
|
||||
* replacement rules and run several times faster than the package's JavaScript
|
||||
* codec on multi-megabyte bundles and source maps. Other encodings and ranged
|
||||
* `toString` calls keep the package's implementation.
|
||||
*/
|
||||
import { Buffer, kMaxLength } from 'buffer'
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
// Node keeps a leading byte order mark in the decoded string.
|
||||
const decoder = new TextDecoder('utf-8', { ignoreBOM: true })
|
||||
const UTF8 = /^utf-?8$/i
|
||||
const packageFrom = Buffer.from.bind(Buffer)
|
||||
const bufferPrototype = Buffer.prototype as Buffer
|
||||
// oxlint-disable-next-line typescript/unbound-method -- Reflect.apply supplies the Buffer receiver.
|
||||
const packageToString = bufferPrototype.toString
|
||||
|
||||
Buffer.from = function from(value: unknown, encodingOrOffset?: unknown, length?: unknown): Buffer {
|
||||
if (typeof value === 'string' && (encodingOrOffset === undefined || (typeof encodingOrOffset === 'string' && UTF8.test(encodingOrOffset)))) {
|
||||
const bytes = encoder.encode(value)
|
||||
return packageFrom(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
||||
}
|
||||
return Reflect.apply(packageFrom, Buffer, [value, encodingOrOffset, length]) as Buffer
|
||||
} as typeof Buffer.from
|
||||
|
||||
bufferPrototype.toString = function toString(this: Buffer, encoding?: BufferEncoding, start?: number, end?: number): string {
|
||||
if (start === undefined && end === undefined && (encoding === undefined || UTF8.test(encoding))) {
|
||||
return decoder.decode(this)
|
||||
}
|
||||
return Reflect.apply(packageToString, this, [encoding, start, end])
|
||||
}
|
||||
// oxlint-disable-next-line typescript/unbound-method -- Installed as a Buffer method; callers supply the receiver.
|
||||
Reflect.set(bufferPrototype, 'toLocaleString', bufferPrototype.toString)
|
||||
|
||||
Object.defineProperty(globalThis, 'Buffer', { value: Buffer, writable: true, configurable: true })
|
||||
|
||||
export { Buffer, kMaxLength }
|
||||
|
||||
@@ -68,7 +68,7 @@ it('loads a combo map through the tunnel and embeds it in the blob script', asyn
|
||||
})
|
||||
await vi.waitFor(() => { expect(sent).toHaveLength(2) })
|
||||
expect(sent[1]?.url).toBe(`http://localhost:3000/plugins/${mapUrl}`)
|
||||
const map = '{"version":3,"sections":[]}'
|
||||
const map = '{"version":3,"names":["中文"],"sections":[]}'
|
||||
deliver({
|
||||
t: 'res',
|
||||
id: 2,
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Differential check of the worker's `node:buffer` UTF-8 conversions against Node.
|
||||
*/
|
||||
import { Buffer as NodeBuffer } from 'node:buffer'
|
||||
import { afterAll, expect, it } from 'vitest'
|
||||
|
||||
const ambient = globalThis.Buffer
|
||||
const { Buffer: WorkerBuffer } = await import('@deepseek-ai/dsh-experimental-webworker-runtime/src/node/builtin_modules/implemented/buffer.ts')
|
||||
afterAll(() => {
|
||||
Object.defineProperty(globalThis, 'Buffer', { value: ambient, writable: true, configurable: true })
|
||||
})
|
||||
|
||||
const STRINGS = ['', 'a', '中文字符串', '\uFEFFbom', 'lone \uD800 high', 'lone \uDC00 low', '😀 pair', 'x'.repeat(100_000)]
|
||||
const BYTES = [
|
||||
[0xEF, 0xBB, 0xBF, 0x61],
|
||||
[0xC3],
|
||||
[0xE4, 0xB8],
|
||||
[0xF0, 0x9F, 0x98],
|
||||
[0xED, 0xA0, 0x80],
|
||||
[0xC0, 0x80],
|
||||
[0xFF, 0xFE, 0x61],
|
||||
[0x61, 0xF4, 0x90, 0x80, 0x80, 0x62],
|
||||
]
|
||||
// Node matches encoding names case-insensitively; its type lists lower-case names only.
|
||||
const UTF8_NAMES = [undefined, 'utf8', 'utf-8', 'UTF-8'].map(name => name as BufferEncoding | undefined)
|
||||
|
||||
it('encodes UTF-8 strings to the bytes Node produces', () => {
|
||||
for (const value of STRINGS) {
|
||||
for (const encoding of UTF8_NAMES) {
|
||||
expect(WorkerBuffer.isBuffer(WorkerBuffer.from(value, encoding))).toBe(true)
|
||||
expect([...WorkerBuffer.from(value, encoding)]).toEqual([...NodeBuffer.from(value, encoding)])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('decodes UTF-8 bytes to the string Node produces', () => {
|
||||
for (const bytes of [...BYTES, ...STRINGS.map(value => [...NodeBuffer.from(value)])]) {
|
||||
const buffer = WorkerBuffer.from(bytes)
|
||||
for (const encoding of UTF8_NAMES) {
|
||||
expect(buffer.toString(encoding)).toBe(NodeBuffer.from(bytes).toString(encoding))
|
||||
}
|
||||
expect(buffer.toLocaleString()).toBe(NodeBuffer.from(bytes).toString())
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps the package conversions for other encodings and ranges', () => {
|
||||
expect([...WorkerBuffer.from('ÿa', 'latin1')]).toEqual([0xFF, 0x61])
|
||||
expect([...WorkerBuffer.from('ff61', 'hex')]).toEqual([0xFF, 0x61])
|
||||
expect(WorkerBuffer.from('中文').toString('hex')).toBe(NodeBuffer.from('中文').toString('hex'))
|
||||
expect(WorkerBuffer.from('a中b').toString('utf8', 1, 4)).toBe('中')
|
||||
const view = new Uint8Array([0x61, 0x62, 0x63]).buffer
|
||||
expect(WorkerBuffer.from(view, 1, 1).toString()).toBe('b')
|
||||
})
|
||||
Reference in new issue
Block a user