Skip to content

Commit

Permalink
work around api deprecations in deno 1.40.x
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jan 26, 2024
1 parent f8ec300 commit 4182241
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 33 deletions.
16 changes: 12 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -221,3 +226,6 @@ jobs:

- name: make test-old-ts
run: make test-old-ts

- name: Deno Tests
run: make test-deno
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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))
Expand Down
130 changes: 101 additions & 29 deletions lib/deno/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,15 +180,105 @@ let defaultWD = Deno.cwd()
let longLivedService: Promise<Service> | undefined
let stopService: (() => void) | undefined

let ensureServiceIsRunning = (): Promise<Service> => {
// 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<Uint8Array | null>
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<Service> => {
if (!longLivedService) {
longLivedService = (async (): Promise<Service> => {
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',
Expand All @@ -204,37 +294,20 @@ let ensureServiceIsRunning = (): Promise<Service> => {
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 => {
Expand Down Expand Up @@ -331,9 +404,8 @@ let ensureServiceIsRunning = (): Promise<Service> => {

// 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',
Expand Down

0 comments on commit 4182241

Please sign in to comment.