diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b5a6665412..6615ecdf03a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,10 @@ The primary branch for this repository was previously called `master` but is now called `main`. This change mirrors a similar change in many other projects. +* Throw an early error if decoded UTF-8 text isn't a `Uint8Array` ([#2532](https://github.com/evanw/esbuild/issues/2532)) + + If you run esbuild's JavaScript API in a broken JavaScript environment where `new TextEncoder().encode("") instanceof Uint8Array` is false, then esbuild's API will fail with a confusing serialization error message that makes it seem like esbuild has a bug even though the real problem is that the JavaScript environment itself is broken. This can happen when using the test framework called "Jest". With this release, esbuild's API will now throw earlier when it detects that the environment is unable to encode UTF-8 text correctly with an error message that makes it more clear that this is not a problem with esbuild. + ## Unreleased * Fix an obscure npm package installation issue with `--omit=optional` ([#2558](https://github.com/evanw/esbuild/issues/2558)) diff --git a/lib/shared/stdio_protocol.ts b/lib/shared/stdio_protocol.ts index 932eb19d6d1..ea38d67db7c 100644 --- a/lib/shared/stdio_protocol.ts +++ b/lib/shared/stdio_protocol.ts @@ -394,6 +394,7 @@ class ByteBuffer { export let encodeUTF8: (text: string) => Uint8Array export let decodeUTF8: (bytes: Uint8Array) => string +let encodeInvariant: string // For the browser and node 12.x if (typeof TextEncoder !== 'undefined' && typeof TextDecoder !== 'undefined') { @@ -401,37 +402,34 @@ if (typeof TextEncoder !== 'undefined' && typeof TextDecoder !== 'undefined') { let decoder = new TextDecoder(); encodeUTF8 = text => encoder.encode(text); decodeUTF8 = bytes => decoder.decode(bytes); + encodeInvariant = 'new TextEncoder().encode("")' } // For node 10.x else if (typeof Buffer !== 'undefined') { - encodeUTF8 = text => { - let buffer: Uint8Array = Buffer.from(text); - - // The test framework called "Jest" breaks node's Buffer API. Normally - // instances of Buffer are also instances of Uint8Array, but not when - // esbuild is run inside of whatever weird environment Jest uses. More - // info: https://github.com/facebook/jest/issues/4422. - if (!(buffer instanceof Uint8Array)) { - // Construct a new Uint8Array with the contents of the buffer to force - // it to be a Uint8Array instance. This is wasteful since it's slower - // than just using the Buffer, but this should only happen when esbuild - // is run inside of Jest. - buffer = new Uint8Array(buffer); - } - - return buffer; - }; + encodeUTF8 = text => Buffer.from(text); decodeUTF8 = bytes => { let { buffer, byteOffset, byteLength } = bytes; return Buffer.from(buffer, byteOffset, byteLength).toString(); } + encodeInvariant = 'Buffer.from("")' } else { throw new Error('No UTF-8 codec found'); } +// Throw an error early if this isn't true. The test framework called "Jest" +// has some bugs regarding this edge case, and letting esbuild proceed further +// leads to confusing errors that make it seem like esbuild itself has a bug. +if (!(encodeUTF8('') instanceof Uint8Array)) + throw new Error(`Invariant violation: "${encodeInvariant} instanceof Uint8Array" is incorrectly false + +This indicates that your JavaScript environment is broken. You cannot use +esbuild in this environment because esbuild relies on this invariant. This +is not a problem with esbuild. You need to fix your environment instead. +`) + export function readUInt32LE(buffer: Uint8Array, offset: number): number { return buffer[offset++] | (buffer[offset++] << 8) |