From 418224141af22963c44e8ae57698e48beef68465 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Thu, 25 Jan 2024 23:10:53 -0500 Subject: [PATCH] work around api deprecations in deno 1.40.x --- .github/workflows/ci.yml | 16 +++-- CHANGELOG.md | 6 ++ lib/deno/mod.ts | 130 ++++++++++++++++++++++++++++++--------- 3 files changed, 119 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 633f48ebf72..98c3c25678e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,12 +89,10 @@ jobs: with: node-version: 16 - # The version of Deno is pinned because version 1.25.1 was causing test - # flakes due to random segfaults. - - name: Setup Deno 1.24.0 + - name: Setup Deno 1.40.0 uses: denoland/setup-deno@main with: - deno-version: v1.24.0 + deno-version: v1.40.0 - name: Check out code into the Go module directory uses: actions/checkout@v3 @@ -210,6 +208,13 @@ jobs: go-version: 1.13 id: go + # Make sure esbuild works with old versions of Deno. Note: It's important + # to test a version before 1.31.0, which introduced the "Deno.Command" API. + - name: Setup Deno 1.24.0 + uses: denoland/setup-deno@main + with: + deno-version: v1.24.0 + - name: Check out code into the Go module directory uses: actions/checkout@v3 @@ -221,3 +226,6 @@ jobs: - name: make test-old-ts run: make test-old-ts + + - name: Deno Tests + run: make test-deno diff --git a/CHANGELOG.md b/CHANGELOG.md index 926418dd088..1e5796c0c7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +* Work around API deprecations in Deno 1.40.x ([#3609](https://github.com/evanw/esbuild/issues/3609)) + + Deno 1.40.0 introduced run-time warnings about certain APIs that esbuild uses. With this release, esbuild will work around these run-time warnings by using other APIs if they are present, and falling back to the original APIs otherwise. This should avoid the warnings without breaking compatibility with older versions of Deno. + ## 0.19.12 * The "preserve" JSX mode now preserves JSX text verbatim ([#3605](https://github.com/evanw/esbuild/issues/3605)) diff --git a/lib/deno/mod.ts b/lib/deno/mod.ts index 7e24f389a74..255f6be090d 100644 --- a/lib/deno/mod.ts +++ b/lib/deno/mod.ts @@ -180,15 +180,105 @@ let defaultWD = Deno.cwd() let longLivedService: Promise | undefined let stopService: (() => void) | undefined -let ensureServiceIsRunning = (): Promise => { +// Declare a common subprocess API for the two implementations below +type SpawnFn = (cmd: string, options: { + args: string[] + stdin: 'piped' | 'inherit' + stdout: 'piped' | 'inherit' + stderr: 'inherit' +}) => { + stdin: { + write(bytes: Uint8Array): void + close(): void + } + stdout: { + read(): Promise + close(): void + } + close(): void + status(): Promise<{ code: number }> +} + +// Deno ≥1.40 +const spawnNew: SpawnFn = (cmd, { args, stdin, stdout, stderr }) => { + const child = new Deno.Command(cmd, { + args, + cwd: defaultWD, + stdin, + stdout, + stderr, + }).spawn() + const writer = child.stdin.getWriter() + const reader = child.stdout.getReader() + return { + stdin: { + write: bytes => writer.write(bytes), + close: () => writer.close(), + }, + stdout: { + read: () => reader.read().then(x => x.value || null), + close: () => reader.cancel(), + }, + close: () => child.kill(), + status: () => child.output(), + } +} + +// Deno <1.40 +const spawnOld: SpawnFn = (cmd, { args, stdin, stdout, stderr }) => { + const child = Deno.run({ + cmd: [cmd].concat(args), + cwd: defaultWD, + stdin, + stdout, + stderr, + }) + const stdoutBuffer = new Uint8Array(4 * 1024 * 1024) + let writeQueue: Uint8Array[] = [] + let isQueueLocked = false + + // We need to keep calling "write()" until it actually writes the data + const startWriteFromQueueWorker = () => { + if (isQueueLocked || writeQueue.length === 0) return + isQueueLocked = true + child.stdin!.write(writeQueue[0]).then(bytesWritten => { + isQueueLocked = false + if (bytesWritten === writeQueue[0].length) writeQueue.shift() + else writeQueue[0] = writeQueue[0].subarray(bytesWritten) + startWriteFromQueueWorker() + }) + } + + return { + stdin: { + write: bytes => { + writeQueue.push(bytes) + startWriteFromQueueWorker() + }, + close: () => child.stdin!.close(), + }, + stdout: { + read: () => child.stdout!.read(stdoutBuffer).then(n => n === null ? null : stdoutBuffer.subarray(0, n)), + close: () => child.stdout!.close(), + }, + close: () => child.close(), + status: () => child.status(), + } +} + +// This is a shim for "Deno.run" for newer versions of Deno +const spawn: SpawnFn = Deno.Command ? spawnNew : spawnOld + +const ensureServiceIsRunning = (): Promise => { if (!longLivedService) { longLivedService = (async (): Promise => { const binPath = await install() - const isTTY = Deno.isatty(Deno.stderr.rid) + const isTTY = Deno.stderr.isTerminal + ? Deno.stderr.isTerminal() // Deno ≥1.40 + : Deno.isatty(Deno.stderr.rid) // Deno <1.40 - const child = Deno.run({ - cmd: [binPath, `--service=${version}`], - cwd: defaultWD, + const child = spawn(binPath, { + args: [`--service=${version}`], stdin: 'piped', stdout: 'piped', stderr: 'inherit', @@ -204,37 +294,20 @@ let ensureServiceIsRunning = (): Promise => { stopService = undefined } - let writeQueue: Uint8Array[] = [] - let isQueueLocked = false - - // We need to keep calling "write()" until it actually writes the data - const startWriteFromQueueWorker = () => { - if (isQueueLocked || writeQueue.length === 0) return - isQueueLocked = true - child.stdin.write(writeQueue[0]).then(bytesWritten => { - isQueueLocked = false - if (bytesWritten === writeQueue[0].length) writeQueue.shift() - else writeQueue[0] = writeQueue[0].subarray(bytesWritten) - startWriteFromQueueWorker() - }) - } - const { readFromStdout, afterClose, service } = common.createChannel({ writeToStdin(bytes) { - writeQueue.push(bytes) - startWriteFromQueueWorker() + child.stdin.write(bytes) }, isSync: false, hasFS: true, esbuild: ourselves, }) - const stdoutBuffer = new Uint8Array(4 * 1024 * 1024) - const readMoreStdout = () => child.stdout.read(stdoutBuffer).then(n => { - if (n === null) { + const readMoreStdout = () => child.stdout.read().then(buffer => { + if (buffer === null) { afterClose(null) } else { - readFromStdout(stdoutBuffer.subarray(0, n)) + readFromStdout(buffer) readMoreStdout() } }).catch(e => { @@ -331,9 +404,8 @@ let ensureServiceIsRunning = (): Promise => { // If we're called as the main script, forward the CLI to the underlying executable if (import.meta.main) { - Deno.run({ - cmd: [await install()].concat(Deno.args), - cwd: defaultWD, + spawn(await install(), { + args: Deno.args, stdin: 'inherit', stdout: 'inherit', stderr: 'inherit',