Skip to content

Commit

Permalink
hack around node windows unicode bug (evanw#687)
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jan 26, 2021
1 parent 88c8523 commit 93423e5
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 8 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@

The workaround was to manually check for this case and then ignore the error in this specific case. With this release, it should now be possible to pipe something to the `esbuild` command on Windows.

* Fix stdout and stderr not supporting Unicode in the `esbuild-wasm` package on Windows ([#687](https://github.com/evanw/esbuild/issues/687))

Node's `fs.write` API is broken when writing Unicode to stdout and stderr on Windows, and this will never be fixed: [nodejs/node#24550](https://github.com/nodejs/node/issues/24550). This is problematic for Go's WebAssembly implementation because it uses this API for writing to all file descriptors.

The workaround is to manually intercept the file descriptors for stdout and stderr and redirect them to `process.stdout` and `process.stderr` respectively. Passing Unicode text to `write()` on these objects instead of on the `fs` API strangely works fine. So with this release, Unicode text should now display correctly when using esbuild's WebAssembly implementation on Windows (or at least, as correctly as the poor Unicode support in Windows Command Prompt allows).
* Add a hack for faster command-line execution for the WebAssembly module in certain cases
Node has an unfortunate bug where the node process is unnecessarily kept open while a WebAssembly module is being optimized: https://github.com/nodejs/node/issues/36616. This means cases where running `esbuild` should take a few milliseconds can end up taking many seconds instead.
Expand Down
40 changes: 32 additions & 8 deletions scripts/esbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,37 @@ exports.buildWasmLib = async (esbuildPath) => {
fs.mkdirSync(esmDir, { recursive: true })

// Generate "npm/esbuild-wasm/wasm_exec.js"
const toReplace = 'global.fs = fs;';
const GOROOT = childProcess.execFileSync('go', ['env', 'GOROOT']).toString().trim();
fs.copyFileSync(
path.join(GOROOT, 'misc', 'wasm', 'wasm_exec.js'),
path.join(npmWasmDir, 'wasm_exec.js'),
);
let wasm_exec_js = fs.readFileSync(path.join(GOROOT, 'misc', 'wasm', 'wasm_exec.js'), 'utf8');
let index = wasm_exec_js.indexOf(toReplace);
if (index === -1) throw new Error(`Failed to find ${JSON.stringify(toReplace)} in Go JS shim code`);
wasm_exec_js = wasm_exec_js.replace(toReplace, `
global.fs = Object.assign({}, fs, {
// Hack around a bug in node: https://github.com/nodejs/node/issues/24550
writeSync(fd, buf) {
if (fd === process.stdout.fd) return process.stdout.write(buf), buf.length;
if (fd === process.stderr.fd) return process.stderr.write(buf), buf.length;
return fs.writeSync(fd, buf);
},
write(fd, buf, offset, length, position, callback) {
if (offset === 0 && length === buf.length && position === null) {
if (fd === process.stdout.fd) {
try { process.stdout.write(buf); }
catch (err) { return callback(err, 0, null); }
return callback(null, length, buf);
}
if (fd === process.stderr.fd) {
try { process.stderr.write(buf); }
catch (err) { return callback(err, 0, null); }
return callback(null, length, buf);
}
}
fs.write(fd, buf, offset, length, position, callback);
},
});
`);
fs.writeFileSync(path.join(npmWasmDir, 'wasm_exec.js'), wasm_exec_js);

// Generate "npm/esbuild-wasm/lib/main.js"
childProcess.execFileSync(esbuildPath, [
Expand All @@ -80,13 +106,11 @@ exports.buildWasmLib = async (esbuildPath) => {
const minifyFlags = minify ? ['--minify'] : []

// Process "npm/esbuild-wasm/wasm_exec.js"
const wasm_exec_js = path.join(npmWasmDir, 'wasm_exec.js')
let wasmExecCode = fs.readFileSync(wasm_exec_js, 'utf8');
let wasmExecCode = wasm_exec_js;
if (minify) {
const wasmExecMin = childProcess.execFileSync(esbuildPath, [
wasm_exec_js,
'--target=es2015',
].concat(minifyFlags), { cwd: repoDir }).toString()
].concat(minifyFlags), { cwd: repoDir, input: wasmExecCode }).toString()
const commentLines = wasmExecCode.split('\n')
const firstNonComment = commentLines.findIndex(line => !line.startsWith('//'))
wasmExecCode = '\n' + commentLines.slice(0, firstNonComment).concat(wasmExecMin).join('\n')
Expand Down
34 changes: 34 additions & 0 deletions scripts/wasm-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,40 @@ const tests = {
assert.deepStrictEqual(exports.default, 3);
},

stdinStdoutUnicodeTest({ testDir, esbuildPath }) {
const stdout = child_process.execFileSync('node', [
esbuildPath,
'--format=cjs',
], {
stdio: ['pipe', 'pipe', 'inherit'],
cwd: testDir,
input: `export default ['π', '🍕']`,
}).toString();

// Check that the bundle is valid
const module = { exports: {} };
new Function('module', 'exports', stdout)(module, module.exports);
assert.deepStrictEqual(module.exports.default, ['π', '🍕']);
},

stdinOutfileUnicodeTest({ testDir, esbuildPath }) {
const outfile = path.join(testDir, 'out.js')
child_process.execFileSync('node', [
esbuildPath,
'--bundle',
'--format=cjs',
'--outfile=' + outfile,
], {
stdio: ['pipe', 'pipe', 'inherit'],
cwd: testDir,
input: `export default ['π', '🍕']`,
}).toString();

// Check that the bundle is valid
const exports = require(outfile);
assert.deepStrictEqual(exports.default, ['π', '🍕']);
},

importRelativeFileTest({ testDir, esbuildPath }) {
const outfile = path.join(testDir, 'out.js')
const packageJSON = path.join(__dirname, '..', 'npm', 'esbuild-wasm', 'package.json');
Expand Down

0 comments on commit 93423e5

Please sign in to comment.