From 2f6c89990d4257019abe608c552bc24b31e2262b Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 31 Jul 2022 11:03:54 -0400 Subject: [PATCH] fix #2423: update `text` getter on output files --- CHANGELOG.md | 8 ++++++++ lib/shared/common.ts | 18 +++++++++++++++++- scripts/plugin-tests.js | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecc255f6270..43e4bc887c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +* Update the getter for `text` in build results ([#2423](https://github.com/evanw/esbuild/issues/2423)) + + Output files in build results returned from esbuild's JavaScript API have both a `contents` and a `text` property to return the contents of the output file. The `contents` property is a binary UTF-8 Uint8Array and the `text` property is a JavaScript UTF-16 string. The `text` property is a getter that does the UTF-8 to UTF-16 conversion only if it's needed for better performance. + + Previously if you mutate the build results object, you had to overwrite both `contents` and `text` since the value returned from the `text` getter is the original text returned by esbuild. Some people find this confusing so with this release, the getter for `text` has been updated to do the UTF-8 to UTF-16 conversion on the current value of the `contents` property instead of the original value. + ## 0.14.51 * Add support for React 17's `automatic` JSX transform ([#334](https://github.com/evanw/esbuild/issues/334), [#718](https://github.com/evanw/esbuild/issues/718), [#1172](https://github.com/evanw/esbuild/issues/1172), [#2318](https://github.com/evanw/esbuild/issues/2318), [#2349](https://github.com/evanw/esbuild/pull/2349)) diff --git a/lib/shared/common.ts b/lib/shared/common.ts index 662f43107de..9ce89074ee5 100644 --- a/lib/shared/common.ts +++ b/lib/shared/common.ts @@ -1689,12 +1689,28 @@ function sanitizeStringArray(values: any[], property: string): string[] { } function convertOutputFiles({ path, contents }: protocol.BuildOutputFile): types.OutputFile { + // The text is lazily-generated for performance reasons. If no one asks for + // it, then it never needs to be generated. let text: string | null = null; return { path, contents, get text() { - if (text === null) text = protocol.decodeUTF8(contents); + // People want to be able to set "contents" and have esbuild automatically + // derive "text" for them, so grab the contents off of this object instead + // of using our original value. + const binary = this.contents; + + // This deliberately doesn't do bidirectional derivation because that could + // result in the inefficiency. For example, if we did do this and then you + // set "contents" and "text" and then asked for "contents", the second + // setter for "text" will have erased our cached "contents" value so we'd + // need to regenerate it again. Instead, "contents" is unambiguously the + // primary value and "text" is unambiguously the derived value. + if (text === null || binary !== contents) { + contents = binary; + text = protocol.decodeUTF8(binary); + } return text; }, } diff --git a/scripts/plugin-tests.js b/scripts/plugin-tests.js index 182ba6e502d..986e6a78cfc 100644 --- a/scripts/plugin-tests.js +++ b/scripts/plugin-tests.js @@ -2768,6 +2768,44 @@ let syncTests = { result.rebuild.dispose() }, + async onEndCallbackMutateContents({ esbuild, testDir }) { + const input = path.join(testDir, 'in.js') + await writeFileAsync(input, `x=y`) + + let onEndTimes = 0 + + const result = await esbuild.build({ + entryPoints: [input], + write: false, + plugins: [ + { + name: 'some-plugin', + setup(build) { + build.onEnd(result => { + onEndTimes++ + + assert.deepStrictEqual(result.outputFiles[0].contents, new Uint8Array([120, 32, 61, 32, 121, 59, 10])) + assert.deepStrictEqual(result.outputFiles[0].text, 'x = y;\n') + + result.outputFiles[0].contents = new Uint8Array([120, 61, 121]) + assert.deepStrictEqual(result.outputFiles[0].contents, new Uint8Array([120, 61, 121])) + assert.deepStrictEqual(result.outputFiles[0].text, 'x=y') + + result.outputFiles[0].contents = new Uint8Array([121, 61, 120]) + assert.deepStrictEqual(result.outputFiles[0].contents, new Uint8Array([121, 61, 120])) + assert.deepStrictEqual(result.outputFiles[0].text, 'y=x') + }) + }, + }, + ], + }) + + assert.deepStrictEqual(onEndTimes, 1) + assert.deepStrictEqual(result.outputFiles.length, 1) + assert.deepStrictEqual(result.outputFiles[0].contents, new Uint8Array([121, 61, 120])) + assert.deepStrictEqual(result.outputFiles[0].text, 'y=x') + }, + async onStartOnEndWatchMode({ esbuild, testDir }) { const srcDir = path.join(testDir, 'src') const outfile = path.join(testDir, 'out.js')