remove the deno package and tests

This commit is contained in:
Evan Wallace
2026-08-09 10:43:14 -04:00
parent fb59860aec
commit 56aecb6cf7
13 changed files with 9 additions and 1035 deletions
-98
View File
@@ -115,11 +115,6 @@ jobs:
with:
node-version: 18
- name: Setup Deno 1.40.0
uses: denoland/setup-deno@main
with:
deno-version: v1.40.0
- name: go test
env:
WT_SESSION: 1 # Ensure that FormatMessages output on Windows matches macOS
@@ -128,14 +123,6 @@ jobs:
- name: go vet
run: go vet ./cmd/... ./internal/... ./pkg/...
- name: Deno Tests (non-Windows)
if: matrix.os != 'windows-latest'
run: make test-deno
- name: Deno Tests (Windows)
if: matrix.os == 'windows-latest'
run: make test-deno-windows
- name: Test for path/filepath
if: matrix.os == 'ubuntu-latest'
run: make no-filepath
@@ -245,88 +232,3 @@ jobs:
- name: go test
run: go test ./internal/...
esbuild-old-deno-version:
name: esbuild CI (old Deno version)
runs-on: ${{ matrix.os }}
strategy:
matrix:
# Note: I'm excluding "macos-latest" here because GitHub recently
# changed their macOS CI VMs from x86_64 to aarch64 (i.e. from Intel
# to ARM) and it looks like old Deno versions have WASM bugs on ARM.
# Specifically, this test now crashes like this when run on macOS:
#
# #
# # Fatal error in , line 0
# # Check failed: RwxMemoryWriteScope::IsAllowed().
# #
# #
# #
# #FailureMessage Object: 0x16f282368
# ==== C stack trace ===============================
#
# 0 deno 0x0000000101bb8e78 v8::base::debug::StackTrace::StackTrace() + 24
# 1 deno 0x0000000101bbda84 v8::platform::(anonymous namespace)::PrintStackTrace() + 24
# 2 deno 0x0000000101bb6230 V8_Fatal(char const*, ...) + 268
# 3 deno 0x000000010227e468 v8::internal::wasm::WasmCodeManager::MemoryProtectionKeysEnabled() const + 0
# 4 deno 0x0000000102299994 v8::internal::wasm::WasmEngine::InitializeOncePerProcess() + 44
# 5 deno 0x0000000101e78fd0 v8::internal::V8::Initialize() + 1576
# 6 deno 0x0000000101c3b7d8 v8::V8::Initialize(int) + 32
# 7 deno 0x00000001011833dc _ZN3std4sync4once4Once9call_once28_$u7b$$u7b$closure$u7d$$u7d$17h2bbe74d315ab3e84E + 488
# 8 deno 0x00000001017f8854 std::sync::once::Once::call_inner::h70fbdd48fe002a01 + 724
# 9 deno 0x000000010115ca80 deno_core::runtime::JsRuntime::new::h9c5f1a9c910f1eed + 192
# 10 deno 0x00000001014d3b50 deno_runtime::worker::MainWorker::bootstrap_from_options::h91a0eaac48dfc18e + 4260
# 11 deno 0x0000000100ee692c deno::create_main_worker::h0d1622755821ae7f + 1608
# 12 deno 0x0000000100f6c688 _ZN97_$LT$core..future..from_generator..GenFuture$LT$T$GT$$u20$as$u20$core..future..future..Future$GT$4poll17h87ddfac9566887c8E + 492
# 13 deno 0x0000000100f6ba18 tokio::runtime::task::raw::poll::h7d51f1a7d5a61c15 + 1396
# 14 deno 0x0000000101917b98 std::sys_common::backtrace::__rust_begin_short_backtrace::hd384935dcffe6f2d + 332
# 15 deno 0x0000000101917954 _ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17he2755732d5d29cf0E + 124
# 16 deno 0x0000000101829684 std::sys::unix::thread::Thread::new::thread_start::h432bc30153e41f60 + 48
# 17 libsystem_pthread.dylib 0x000000018a436f94 _pthread_start + 136
# 18 libsystem_pthread.dylib 0x000000018a431d34 thread_start + 8
#
# Hopefully running this old Deno version on Linux is sufficiently
# close to running it on macOS. For reference, I believe this is the
# change that GitHub made which broke this test:
# https://github.blog/changelog/2023-10-02-github-actions-apple-silicon-m1-macos-runners-are-now-available-in-public-beta/
os: [ubuntu-latest, windows-latest]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Read go.version (non-Windows)
if: matrix.os != 'windows-latest'
run: |
echo "GO_VERSION=$(cat go.version)" >> $GITHUB_ENV
- name: Read go.version (Windows)
if: matrix.os == 'windows-latest'
run: |
echo "GO_VERSION=$(cat go.version)" >> $Env:GITHUB_ENV
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
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: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: 18
- name: Deno Tests (non-Windows)
if: matrix.os != 'windows-latest'
run: make test-deno
- name: Deno Tests (Windows)
if: matrix.os == 'windows-latest'
run: make test-deno-windows
-11
View File
@@ -20,13 +20,6 @@ jobs:
with:
node-version: 18
# 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
uses: denoland/setup-deno@main
with:
deno-version: v1.24.0
- name: Test npm
run: |
npm i -g npm@next-7
@@ -46,7 +39,3 @@ jobs:
run: |
npm i -g yarn@latest
time make test-e2e-yarn-berry
- name: Test deno
run: |
time make test-e2e-deno
-2
View File
@@ -3,11 +3,9 @@
.vscode/
/bench/
/demo/
/deno/
/esbuild
/github/
/go/
/lib/deno/lib.deno.d.ts
/npm/@esbuild/android-arm/esbuild.wasm
/npm/@esbuild/android-arm/wasm_exec_node.js
/npm/@esbuild/android-arm/wasm_exec.js
+4
View File
@@ -2,6 +2,10 @@
## Unreleased
* Stop publishing to https://deno.land/x/esbuild
Deno has made the https://deno.land/x package registry read-only, so new versions of esbuild can no longer be published to https://deno.land/x/esbuild. The last published version was [v0.28.1](https://deno.land/x/[email protected]). I have removed the parts of esbuild that publish a Deno-specific package. Instead you can install `npm:esbuild` to use esbuild in Deno (this wasn't the case when esbuild's Deno-specific package was first published).
* Emit an error when code splitting chunks would be merged ([#4411](https://github.com/evanw/esbuild/issues/4411))
It's possible to configure esbuild such that separate output files end up with the same output path. For example, you could name all code splitting chunks `chunk` via the `chunkNames` setting, which might generate multiple output files with the same path `chunk.js`. This does not happen by default since by default the chunk names include a hash to make sure they're unique (e.g. named something like `chunk-GX7G2SBE.js` instead).
+3 -29
View File
@@ -24,7 +24,7 @@ test-common: test-go vet-go no-filepath verify-source-map end-to-end-tests js-ap
# These tests are for release (the extra tests are not included in "test" because they are pretty slow)
test-all:
@$(MAKE) --no-print-directory -j6 test-common test-deno ts-type-tests test-wasm-node test-wasm-browser lib-typecheck test-yarnpnp
@$(MAKE) --no-print-directory -j6 test-common ts-type-tests test-wasm-node test-wasm-browser lib-typecheck test-yarnpnp
check-go-version:
@go version | grep -F " go$(GO_VERSION) " || (echo 'Please install Go version $(GO_VERSION)' && false)
@@ -98,16 +98,6 @@ test-wasm-node: esbuild
test-wasm-browser: platform-wasm | scripts/browser/node_modules
cd scripts/browser && node browser-tests.js
test-deno: esbuild platform-deno
ESBUILD_BINARY_PATH="$(shell pwd)/esbuild" deno test --allow-run --allow-env --allow-net --allow-read --allow-write --no-check scripts/deno-tests.js
@echo '✅ deno tests passed' # I couldn't find a Deno API for telling when tests have failed, so I'm doing this here instead
ESBUILD_BINARY_PATH="$(shell pwd)/esbuild" deno eval 'import { transform, stop } from "file://$(shell pwd)/deno/mod.js"; console.log((await transform("1+2")).code); stop()' | grep "1 + 2;"
ESBUILD_BINARY_PATH="$(shell pwd)/esbuild" deno eval 'import { transform, stop } from "file://$(shell pwd)/deno/wasm.js"; console.log((await transform("1+2")).code); stop()' | grep "1 + 2;"
ESBUILD_BINARY_PATH="$(shell pwd)/esbuild" deno run -A './deno/mod.js' # See: https://github.com/evanw/esbuild/pull/3917
test-deno-windows: esbuild platform-deno
ESBUILD_BINARY_PATH=./esbuild.exe deno test --allow-run --allow-env --allow-net --allow-read --allow-write --no-check scripts/deno-tests.js
register-test: version-go | scripts/node_modules
node scripts/esbuild.js npm/esbuild/package.json --version
node scripts/register-test.js
@@ -152,7 +142,7 @@ test-old-ts: | require/old-ts/node_modules
node-unref-tests: | scripts/node_modules
node scripts/node-unref-tests.js
lib-typecheck: lib-typecheck-node lib-typecheck-node-nolib lib-typecheck-deno
lib-typecheck: lib-typecheck-node lib-typecheck-node-nolib
lib-typecheck-node: | lib/node_modules
cd lib && node_modules/.bin/tsc -noEmit -p tsconfig.json
@@ -160,14 +150,8 @@ lib-typecheck-node: | lib/node_modules
lib-typecheck-node-nolib: | lib/node_modules
cd lib && node_modules/.bin/tsc -noEmit -p tsconfig-nolib.json
lib-typecheck-deno: lib/deno/lib.deno.d.ts | lib/node_modules
cd lib && node_modules/.bin/tsc -noEmit -p tsconfig-deno.json
lib/deno/lib.deno.d.ts:
deno types > lib/deno/lib.deno.d.ts
# End-to-end tests
test-e2e: test-e2e-npm test-e2e-pnpm test-e2e-yarn test-e2e-yarn-berry test-e2e-deno
test-e2e: test-e2e-npm test-e2e-pnpm test-e2e-yarn test-e2e-yarn-berry
test-e2e-npm:
# Test normal install
@@ -296,10 +280,6 @@ test-e2e-yarn-berry:
# Clean up
rm -fr e2e-yb
test-e2e-deno:
deno eval 'import { transform, stop } from "https://deno.land/x/esbuild@v$(ESBUILD_VERSION)/mod.js"; console.log((await transform("1+2")).code); stop()' | grep "1 + 2;"
deno eval 'import { transform, stop } from "https://deno.land/x/esbuild@v$(ESBUILD_VERSION)/wasm.js"; console.log((await transform("1+2")).code); stop()' | grep "1 + 2;"
test-yarnpnp: platform-wasm
node scripts/test-yarnpnp.js
@@ -320,7 +300,6 @@ version-go:
platform-all: \
platform-android-arm \
platform-android-x64 \
platform-deno \
platform-neutral \
platform-openharmony-arm64 \
platform-wasi-preview1 \
@@ -463,11 +442,6 @@ platform-wasm: esbuild go-compiler
$(GO_COMPILER) "$(NODE)" scripts/esbuild.js ./esbuild --wasm
@shasum -a 256 npm/esbuild-wasm/esbuild.wasm
platform-deno: platform-wasm go-compiler
@echo
@echo "# Build: deno"
$(GO_COMPILER) "$(NODE)" scripts/esbuild.js ./esbuild --deno
publish-all: check-go-version
# Make sure the npm directory is pristine (including .gitignored files) since it will be published
rm -fr npm && git checkout npm
-13
View File
@@ -23,19 +23,6 @@ Pushing a change to [`version.txt`](./version.txt) causes the following:
2. Create a tag for the release that looks like `v0.X.Y`
3. Publish a [GitHub Release](https://github.com/evanw/esbuild/releases) containing the release notes in [`CHANGELOG.md`](./CHANGELOG.md)
- The [`release.yml`](https://github.com/esbuild/deno-esbuild/blob/main/.github/workflows/release.yml)
workflow in the https://github.com/esbuild/deno-esbuild repo runs
occasionally. On the next run, it will notice the version change and:
1. Clone this repo
2. Run `make platform-deno`
3. Commit and push the new contents of the `deno` folder to the `deno-esbuild` repo
4. Create a tag for the release that looks like `v0.X.Y`
5. Post an event to the https://api.deno.land/webhook/gh/esbuild webhook
6. Deno will then add a new version to https://deno.land/x/esbuild
You can also manually trigger this workflow if you want it to happen immediately.
- The [`release.yml`](https://github.com/esbuild/esbuild.github.io/blob/main/.github/workflows/release.yml)
workflow in the https://github.com/esbuild/esbuild.github.io repo runs
occasionally. On the next run, it will notice the version change and:
-6
View File
@@ -1,6 +0,0 @@
declare module "https://deno.land/x/[email protected]/mod.ts" {
export function gunzip(input: Uint8Array): Uint8Array
}
// This is used by "worker.ts"
declare let onmessage: (event: any) => void
-435
View File
@@ -1,435 +0,0 @@
import type * as types from "../shared/types"
import * as common from "../shared/common"
import * as ourselves from "./mod"
import * as denoflate from "https://deno.land/x/[email protected]/mod.ts"
declare const ESBUILD_VERSION: string
declare const ESBUILD_BINARY_HASHES: Record<string, string>
export let version = ESBUILD_VERSION
export let build: typeof types.build = (options: types.BuildOptions) =>
ensureServiceIsRunning().then(service =>
service.build(options))
export const context: typeof types.context = (options: types.BuildOptions) =>
ensureServiceIsRunning().then(service =>
service.context(options))
export const transform: typeof types.transform = (input: string | Uint8Array, options?: types.TransformOptions) =>
ensureServiceIsRunning().then(service =>
service.transform(input, options))
export const formatMessages: typeof types.formatMessages = (messages, options) =>
ensureServiceIsRunning().then(service =>
service.formatMessages(messages, options))
export const analyzeMetafile: typeof types.analyzeMetafile = (metafile, options) =>
ensureServiceIsRunning().then(service =>
service.analyzeMetafile(metafile, options))
export const buildSync: typeof types.buildSync = () => {
throw new Error(`The "buildSync" API does not work in Deno`)
}
export const transformSync: typeof types.transformSync = () => {
throw new Error(`The "transformSync" API does not work in Deno`)
}
export const formatMessagesSync: typeof types.formatMessagesSync = () => {
throw new Error(`The "formatMessagesSync" API does not work in Deno`)
}
export const analyzeMetafileSync: typeof types.analyzeMetafileSync = () => {
throw new Error(`The "analyzeMetafileSync" API does not work in Deno`)
}
export const stop = async () => {
if (stopService) await stopService()
}
let initializeWasCalled = false
export const initialize: typeof types.initialize = async (options) => {
options = common.validateInitializeOptions(options || {})
if (options.wasmURL) throw new Error(`The "wasmURL" option only works in the browser`)
if (options.wasmModule) throw new Error(`The "wasmModule" option only works in the browser`)
if (options.worker) throw new Error(`The "worker" option only works in the browser`)
if (initializeWasCalled) throw new Error('Cannot call "initialize" more than once')
await ensureServiceIsRunning()
initializeWasCalled = true
}
async function binaryIntegrityCheck(pkg: string, subpath: string, bytes: Uint8Array): Promise<void> {
const hashBuffer = await crypto.subtle.digest('SHA-256', bytes as Uint8Array<ArrayBuffer>)
const hash = Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('')
const key = `${pkg}/${subpath}`
const expected = ESBUILD_BINARY_HASHES[key]
if (!expected) throw new Error(`Missing hash for "${key}"`)
if (hash !== expected) throw new Error(`"${hash.slice(0, 8)}..." doesn't match "${expected.slice(0, 8)}..." for "${pkg}"`)
}
async function installFromNPM(name: string, subpath: string): Promise<string> {
const { finalPath, finalDir } = getCachePath(name)
try {
await Deno.stat(finalPath)
return finalPath
} catch (e) {
}
const npmRegistry = Deno.env.get("NPM_CONFIG_REGISTRY") || "https://registry.npmjs.org"
const url = `${npmRegistry}/${name}/-/${name.replace("@esbuild/", "")}-${version}.tgz`
const buffer = await fetch(url).then(r => r.arrayBuffer())
const executable = extractFileFromTarGzip(new Uint8Array(buffer), subpath)
await binaryIntegrityCheck(name, subpath, executable)
await Deno.mkdir(finalDir, {
recursive: true,
mode: 0o700, // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
})
await Deno.writeFile(finalPath, executable, { mode: 0o755 })
return finalPath
}
function getCachePath(name: string): { finalPath: string, finalDir: string } {
let baseDir: string | undefined
switch (Deno.build.os) {
case 'darwin':
baseDir = Deno.env.get('HOME')
if (baseDir) baseDir += '/Library/Caches'
break
case 'windows':
baseDir = Deno.env.get('LOCALAPPDATA')
if (!baseDir) {
baseDir = Deno.env.get('USERPROFILE')
if (baseDir) baseDir += '/AppData/Local'
}
if (baseDir) baseDir += '/Cache'
break
case 'linux':
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
const xdg = Deno.env.get('XDG_CACHE_HOME')
if (xdg && xdg[0] === '/') baseDir = xdg
break
}
if (!baseDir) {
baseDir = Deno.env.get('HOME')
if (baseDir) baseDir += '/.cache'
}
if (!baseDir) throw new Error('Failed to find cache directory')
const finalDir = baseDir + `/esbuild/bin`
const finalPath = finalDir + `/${name.replace('/', '-')}@${version}`
return { finalPath, finalDir }
}
function extractFileFromTarGzip(buffer: Uint8Array, file: string): Uint8Array {
try {
buffer = denoflate.gunzip(buffer)
} catch (err: any) {
throw new Error(`Invalid gzip data in archive: ${err && err.message || err}`)
}
let str = (i: number, n: number) => String.fromCharCode(...buffer.subarray(i, i + n)).replace(/\0.*$/, '')
let offset = 0
file = `package/${file}`
while (offset < buffer.length) {
let name = str(offset, 100)
let size = parseInt(str(offset + 124, 12), 8)
offset += 512
if (!isNaN(size) && size >= 0) {
if (name === file) return buffer.subarray(offset, offset + size)
offset += (size + 511) & ~511
}
}
throw new Error(`Could not find ${JSON.stringify(file)} in archive`)
}
async function install(): Promise<string> {
const overridePath = Deno.env.get('ESBUILD_BINARY_PATH')
if (overridePath) return overridePath
const platformKey = Deno.build.target
const knownWindowsPackages: Record<string, string> = {
'x86_64-pc-windows-msvc': '@esbuild/win32-x64',
}
const knownUnixlikePackages: Record<string, string> = {
// These are the only platforms that Deno supports
'aarch64-apple-darwin': '@esbuild/darwin-arm64',
'aarch64-unknown-linux-gnu': '@esbuild/linux-arm64',
'x86_64-apple-darwin': '@esbuild/darwin-x64',
'x86_64-unknown-linux-gnu': '@esbuild/linux-x64',
// These platforms are not supported by Deno
'aarch64-linux-android': '@esbuild/android-arm64',
'x86_64-unknown-freebsd': '@esbuild/freebsd-x64',
"x86_64-alpine-linux-musl": '@esbuild/linux-x64',
}
// Pick a package to install
if (platformKey in knownWindowsPackages) {
return await installFromNPM(knownWindowsPackages[platformKey], 'esbuild.exe')
} else if (platformKey in knownUnixlikePackages) {
return await installFromNPM(knownUnixlikePackages[platformKey], 'bin/esbuild')
} else {
throw new Error(`Unsupported platform: ${platformKey}`)
}
}
interface Service {
build: typeof types.build
context: typeof types.context
transform: typeof types.transform
formatMessages: typeof types.formatMessages
analyzeMetafile: typeof types.analyzeMetafile
}
let defaultWD = Deno.cwd()
let longLivedService: Promise<Service> | undefined
let stopService: (() => Promise<void>) | undefined
// 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'
}) => {
write(bytes: Uint8Array): void
read(): Promise<Uint8Array | null>
close(): Promise<void> | 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()
// Note: Need to check for "piped" in Deno ≥1.31.0 to avoid a crash
const writer = stdin === 'piped' ? child.stdin.getWriter() : null
const reader = stdout === 'piped' ? child.stdout.getReader() : null
return {
write: writer ? bytes => writer.write(bytes) : () => Promise.resolve(),
read: reader ? () => reader.read().then(x => x.value || null) : () => Promise.resolve(null),
close: async () => {
// We can't call "kill()" because it doesn't seem to work. Tests will
// still fail with "A child process was opened during the test, but not
// closed during the test" even though we kill the child process.
//
// And we can't call both "writer.close()" and "kill()" because then
// there's a race as the child process exits when stdin is closed, and
// "kill()" fails when the child process has already been killed.
//
// So instead we just call "writer.close()" and then hope that this
// causes the child process to exit. It won't work if the stdin consumer
// thread in the child process is hung or busy, but that may be the best
// we can do.
//
// See this for more info: https://github.com/evanw/esbuild/pull/3611
if (writer) await writer.close()
if (reader) await reader.cancel()
// Wait for the process to exit. The new "kill()" API doesn't flag the
// process as having exited because processes can technically ignore the
// kill signal. Without this, Deno will fail tests that use esbuild with
// an error because the test spawned a process but didn't wait for it.
await child.status
},
status: () => child.status,
}
}
// 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 {
write: bytes => {
writeQueue.push(bytes)
startWriteFromQueueWorker()
},
read: () => child.stdout!.read(stdoutBuffer).then(n => n === null ? null : stdoutBuffer.subarray(0, n)),
close: () => {
child.stdin!.close()
child.stdout!.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.stderr.isTerminal
? Deno.stderr.isTerminal() // Deno ≥1.40
: Deno.isatty(Deno.stderr.rid) // Deno <1.40
const child = spawn(binPath, {
args: [`--service=${version}`],
stdin: 'piped',
stdout: 'piped',
stderr: 'inherit',
})
stopService = async () => {
// Close all resources related to the subprocess.
await child.close()
initializeWasCalled = false
longLivedService = undefined
stopService = undefined
}
const { readFromStdout, afterClose, service } = common.createChannel({
writeToStdin(bytes) {
child.write(bytes)
},
isSync: false,
hasFS: true,
esbuild: ourselves,
})
const readMoreStdout = () => child.read().then(buffer => {
if (buffer === null) {
afterClose(null)
} else {
readFromStdout(buffer)
readMoreStdout()
}
}).catch(e => {
if (e instanceof Deno.errors.Interrupted || e instanceof Deno.errors.BadResource) {
// ignore the error if read was interrupted (stdout was closed)
afterClose(e)
} else {
throw e
}
})
readMoreStdout()
return {
build: (options: types.BuildOptions) =>
new Promise<types.BuildResult>((resolve, reject) => {
service.buildOrContext({
callName: 'build',
refs: null,
options,
isTTY,
defaultWD,
callback: (err, res) => err ? reject(err) : resolve(res as types.BuildResult),
})
}),
context: (options: types.BuildOptions) =>
new Promise<types.BuildContext>((resolve, reject) =>
service.buildOrContext({
callName: 'context',
refs: null,
options,
isTTY,
defaultWD,
callback: (err, res) => err ? reject(err) : resolve(res as types.BuildContext),
})),
transform: (input: string | Uint8Array, options?: types.TransformOptions) =>
new Promise<types.TransformResult>((resolve, reject) =>
service.transform({
callName: 'transform',
refs: null,
input,
options: options || {},
isTTY,
fs: {
readFile(tempFile, callback) {
Deno.readFile(tempFile).then(
bytes => {
let text = new TextDecoder().decode(bytes)
try {
Deno.remove(tempFile)
} catch (e) {
}
callback(null, text)
},
err => callback(err, null),
)
},
writeFile(contents, callback) {
Deno.makeTempFile().then(
tempFile => Deno.writeFile(tempFile, typeof contents === 'string' ? new TextEncoder().encode(contents) : contents).then(
() => callback(tempFile),
() => callback(null)),
() => callback(null))
},
},
callback: (err, res) => err ? reject(err) : resolve(res!),
})),
formatMessages: (messages, options) =>
new Promise((resolve, reject) =>
service.formatMessages({
callName: 'formatMessages',
refs: null,
messages,
options,
callback: (err, res) => err ? reject(err) : resolve(res!),
})),
analyzeMetafile: (metafile, options) =>
new Promise((resolve, reject) =>
service.analyzeMetafile({
callName: 'analyzeMetafile',
refs: null,
metafile: typeof metafile === 'string' ? metafile : JSON.stringify(metafile),
options,
callback: (err, res) => err ? reject(err) : resolve(res!),
})),
}
})()
}
return longLivedService
}
// If we're called as the main script, forward the CLI to the underlying executable
if (import.meta.main) {
spawn(await install(), {
args: Deno.args,
stdin: 'inherit',
stdout: 'inherit',
stderr: 'inherit',
}).status().then(({ code }) => {
Deno.exit(code)
})
}
-203
View File
@@ -1,203 +0,0 @@
import type * as types from "../shared/types"
import * as common from "../shared/common"
import * as ourselves from "./wasm"
interface Go {
_scheduledTimeouts: Map<number, ReturnType<typeof setTimeout>>
}
declare const ESBUILD_VERSION: string
declare let WEB_WORKER_SOURCE_CODE: string
declare let WEB_WORKER_FUNCTION: (postMessage: (data: Uint8Array) => void) => (event: { data: Uint8Array | ArrayBuffer | WebAssembly.Module }) => Go
export let version = ESBUILD_VERSION
export let build: typeof types.build = (options: types.BuildOptions) =>
ensureServiceIsRunning().then(service =>
service.build(options))
export const context: typeof types.context = (options: types.BuildOptions) =>
ensureServiceIsRunning().then(service =>
service.context(options))
export const transform: typeof types.transform = (input: string | Uint8Array, options?: types.TransformOptions) =>
ensureServiceIsRunning().then(service =>
service.transform(input, options))
export const formatMessages: typeof types.formatMessages = (messages, options) =>
ensureServiceIsRunning().then(service =>
service.formatMessages(messages, options))
export const analyzeMetafile: typeof types.analyzeMetafile = (metafile, options) =>
ensureServiceIsRunning().then(service =>
service.analyzeMetafile(metafile, options))
export const buildSync: typeof types.buildSync = () => {
throw new Error(`The "buildSync" API does not work in Deno`)
}
export const transformSync: typeof types.transformSync = () => {
throw new Error(`The "transformSync" API does not work in Deno`)
}
export const formatMessagesSync: typeof types.formatMessagesSync = () => {
throw new Error(`The "formatMessagesSync" API does not work in Deno`)
}
export const analyzeMetafileSync: typeof types.analyzeMetafileSync = () => {
throw new Error(`The "analyzeMetafileSync" API does not work in Deno`)
}
export const stop = () => {
if (stopService) stopService()
return Promise.resolve()
}
interface Service {
build: typeof types.build
context: typeof types.context
transform: typeof types.transform
formatMessages: typeof types.formatMessages
analyzeMetafile: typeof types.analyzeMetafile
}
let initializePromise: Promise<Service> | undefined
let stopService: (() => void) | undefined
let ensureServiceIsRunning = (): Promise<Service> => {
return initializePromise || startRunningService('esbuild.wasm', undefined, true)
}
export const initialize: typeof types.initialize = async (options) => {
options = common.validateInitializeOptions(options || {})
let wasmURL = options.wasmURL
let wasmModule = options.wasmModule
let useWorker = options.worker !== false
if (initializePromise) throw new Error('Cannot call "initialize" more than once')
initializePromise = startRunningService(wasmURL || 'esbuild.wasm', wasmModule, useWorker)
initializePromise.catch(() => {
// Let the caller try again if this fails
initializePromise = void 0
})
await initializePromise
}
const startRunningService = async (wasmURL: string | URL, wasmModule: WebAssembly.Module | undefined, useWorker: boolean): Promise<Service> => {
let worker: {
onmessage?: ((event: any) => void) | null | undefined
postMessage: (data: Uint8Array | ArrayBuffer | WebAssembly.Module) => void
terminate: () => void
}
if (useWorker) {
// Run esbuild off the main thread
let blob = new Blob([`onmessage=${WEB_WORKER_SOURCE_CODE}(postMessage)`], { type: 'text/javascript' })
worker = new Worker(URL.createObjectURL(blob), { type: 'module' })
} else {
// Run esbuild on the main thread
let onmessage = WEB_WORKER_FUNCTION((data: Uint8Array) => worker.onmessage!({ data }))
let go: Go | undefined
worker = {
onmessage: null,
postMessage: data => setTimeout(() => go = onmessage({ data })),
terminate() {
if (go)
for (let timeout of go._scheduledTimeouts.values())
clearTimeout(timeout)
},
}
}
let firstMessageResolve: (value: void) => void
let firstMessageReject: (error: any) => void
const firstMessagePromise = new Promise((resolve, reject) => {
firstMessageResolve = resolve
firstMessageReject = reject
})
worker.onmessage = ({ data: error }) => {
worker.onmessage = ({ data }) => readFromStdout(data)
if (error) firstMessageReject(error)
else firstMessageResolve()
}
worker.postMessage(wasmModule || new URL(wasmURL, import.meta.url).toString())
let { readFromStdout, service } = common.createChannel({
writeToStdin(bytes) {
worker.postMessage(bytes)
},
isSync: false,
hasFS: false,
esbuild: ourselves,
})
// This will throw if WebAssembly module instantiation fails
await firstMessagePromise
stopService = () => {
worker.terminate()
initializePromise = undefined
stopService = undefined
}
return {
build: (options: types.BuildOptions) =>
new Promise<types.BuildResult>((resolve, reject) =>
service.buildOrContext({
callName: 'build',
refs: null,
options,
isTTY: false,
defaultWD: '/',
callback: (err, res) => err ? reject(err) : resolve(res as types.BuildResult),
})),
context: (options: types.BuildOptions) =>
new Promise<types.BuildContext>((resolve, reject) =>
service.buildOrContext({
callName: 'context',
refs: null,
options,
isTTY: false,
defaultWD: '/',
callback: (err, res) => err ? reject(err) : resolve(res as types.BuildContext),
})),
transform: (input: string | Uint8Array, options?: types.TransformOptions) =>
new Promise<types.TransformResult>((resolve, reject) =>
service.transform({
callName: 'transform',
refs: null,
input,
options: options || {},
isTTY: false,
fs: {
readFile(_, callback) { callback(new Error('Internal error'), null); },
writeFile(_, callback) { callback(null); },
},
callback: (err, res) => err ? reject(err) : resolve(res!),
})),
formatMessages: (messages, options) =>
new Promise((resolve, reject) =>
service.formatMessages({
callName: 'formatMessages',
refs: null,
messages,
options,
callback: (err, res) => err ? reject(err) : resolve(res!),
})),
analyzeMetafile: (metafile, options) =>
new Promise((resolve, reject) =>
service.analyzeMetafile({
callName: 'analyzeMetafile',
refs: null,
metafile: typeof metafile === 'string' ? metafile : JSON.stringify(metafile),
options,
callback: (err, res) => err ? reject(err) : resolve(res!),
})),
}
}
+2 -11
View File
@@ -686,23 +686,14 @@ export let version: string
// In node this happens automatically before the parent node process exits. So
// you only need to call this if you know you will not make any more esbuild
// API calls and you want to clean up resources.
//
// Unlike node, Deno lacks the necessary APIs to clean up child processes
// automatically. You must manually call stop() in Deno when you're done
// using esbuild or Deno will continue running forever.
//
// Another reason you might want to call this is if you are using esbuild from
// within a Deno test. Deno fails tests that create a child process without
// killing it before the test ends, so you have to call this function (and
// await the returned promise) in every Deno test that uses esbuild.
export declare function stop(): Promise<void>
// Note: These declarations exist to avoid type errors when you omit "dom" from
// "lib" in your "tsconfig.json" file. TypeScript confusingly declares the
// global "WebAssembly" type in "lib.dom.d.ts" even though it has nothing to do
// with the browser DOM and is present in many non-browser JavaScript runtimes
// (e.g. node and deno). Declaring it here allows esbuild's API to be used in
// these scenarios.
// (e.g. node). Declaring it here allows esbuild's API to be used in these
// scenarios.
//
// There's an open issue about getting this problem corrected (although these
// declarations will need to remain even if this is fixed for backward
-3
View File
@@ -8,7 +8,4 @@
"node"
],
},
"exclude": [
"deno",
],
}
-181
View File
@@ -1,181 +0,0 @@
// To run this, you must first build the Deno package with "make platform-deno"
import * as esbuildNative from '../deno/mod.js'
import * as esbuildWASM from '../deno/wasm.js'
import * as path from 'https://deno.land/[email protected]/path/mod.ts'
import * as asserts from 'https://deno.land/[email protected]/testing/asserts.ts'
const __dirname = path.dirname(path.fromFileUrl(import.meta.url))
const rootTestDir = path.join(__dirname, '.deno-tests')
const wasmModule = await WebAssembly.compile(await Deno.readFile(path.join(__dirname, '..', 'deno', 'esbuild.wasm')))
try {
Deno.removeSync(rootTestDir, { recursive: true })
} catch {
}
Deno.mkdirSync(rootTestDir, { recursive: true })
function test(name, backends, fn) {
// Note: Do not try to add a timeout for the tests below. It turns out that
// calling "setTimeout" from within "Deno.test" changes how Deno waits for
// promises in a way that masks problems that would otherwise occur. We want
// test coverage for the way that people will likely be using these API calls.
//
// Specifically tests that Deno would otherwise have failed with "error:
// Promise resolution is still pending but the event loop has already
// resolved" were being allowed to pass instead. See this issue for more
// information: https://github.com/evanw/esbuild/issues/3682
for (const backend of backends) {
switch (backend) {
case 'native':
Deno.test(name + '-native', async () => {
let testDir = path.join(rootTestDir, name + '-native')
await Deno.mkdir(testDir, { recursive: true })
try {
await fn({ esbuild: esbuildNative, testDir })
await Deno.remove(testDir, { recursive: true }).catch(() => null)
} finally {
await esbuildNative.stop()
}
})
break
case 'wasm-main':
Deno.test(name + '-wasm-main', async () => {
let testDir = path.join(rootTestDir, name + '-wasm-main')
await esbuildWASM.initialize({ wasmModule, worker: false })
await Deno.mkdir(testDir, { recursive: true })
try {
await fn({ esbuild: esbuildWASM, testDir })
await Deno.remove(testDir, { recursive: true }).catch(() => null)
} finally {
await esbuildWASM.stop()
}
})
break
case 'wasm-worker':
Deno.test(name + '-wasm-worker', async () => {
let testDir = path.join(rootTestDir, name + '-wasm-worker')
await esbuildWASM.initialize({ wasmModule, worker: true })
await Deno.mkdir(testDir, { recursive: true })
try {
await fn({ esbuild: esbuildWASM, testDir })
await Deno.remove(testDir, { recursive: true }).catch(() => null)
} finally {
await esbuildWASM.stop()
}
})
break
}
}
}
addEventListener("unload", (e) => {
try {
Deno.removeSync(rootTestDir, { recursive: true })
} catch {
// root test dir possibly already removed, so ignore
}
})
// This test doesn't run in WebAssembly because it requires file system access
test("basicBuild", ['native'], async ({ esbuild, testDir }) => {
const input = path.join(testDir, 'in.ts')
const dep = path.join(testDir, 'dep.ts')
const output = path.join(testDir, 'out.ts')
await Deno.writeTextFile(input, 'import dep from "./dep.ts"; export default dep === 123')
await Deno.writeTextFile(dep, 'export default 123')
await esbuild.build({
entryPoints: [input],
bundle: true,
outfile: output,
format: 'esm',
})
const result = await import(path.toFileUrl(output))
asserts.assertStrictEquals(result.default, true)
})
test("basicContext", ['native'], async ({ esbuild, testDir }) => {
const input = path.join(testDir, 'in.ts')
const dep = path.join(testDir, 'dep.ts')
const output = path.join(testDir, 'out.ts')
await Deno.writeTextFile(input, 'import dep from "./dep.ts"; export default dep === 123')
await Deno.writeTextFile(dep, 'export default 123')
const ctx = await esbuild.context({
entryPoints: ['in.ts'],
bundle: true,
outfile: output,
format: 'esm',
absWorkingDir: testDir,
})
const { errors, warnings } = await ctx.rebuild()
asserts.assertStrictEquals(errors.length, 0)
asserts.assertStrictEquals(warnings.length, 0)
await ctx.dispose()
const result = await import(path.toFileUrl(output))
asserts.assertStrictEquals(result.default, true)
})
test("basicPlugin", ['native', 'wasm-main', 'wasm-worker'], async ({ esbuild }) => {
const build = await esbuild.build({
entryPoints: ['<entry>'],
bundle: true,
format: 'esm',
write: false,
plugins: [{
name: 'plug',
setup(build) {
build.onResolve({ filter: /^<.*>$/ }, args => ({ path: args.path, namespace: '<>' }))
build.onLoad({ filter: /^<entry>$/ }, () => ({ contents: `import dep from "<dep>"; export default dep === 123` }))
build.onLoad({ filter: /^<dep>$/ }, () => ({ contents: `export default 123` }))
},
}],
})
const result = await import('data:application/javascript;base64,' + btoa(build.outputFiles[0].text))
asserts.assertStrictEquals(result.default, true)
})
test("basicTransform", ['native', 'wasm-main', 'wasm-worker'], async ({ esbuild }) => {
const ts = 'let x: number = 1+2'
const result = await esbuild.transform(ts, { loader: 'ts' })
asserts.assertStrictEquals(result.code, 'let x = 1 + 2;\n')
})
// This test doesn't run in WebAssembly because of a stack overflow
test("largeTransform", ['native'], async ({ esbuild }) => {
// This should be large enough to be bigger than Deno's write buffer
let x = '0'
for (let i = 0; i < 1000; i++) x += '+' + i
x += ','
let y = 'return['
for (let i = 0; i < 1000; i++) y += x
y += ']'
const result = await esbuild.build({
stdin: {
contents: y,
},
write: false,
minifyWhitespace: true,
})
asserts.assertStrictEquals(result.outputFiles[0].text, y.slice(0, -2) + '];\n')
})
test("analyzeMetafile", ['native', 'wasm-main', 'wasm-worker'], async ({ esbuild }) => {
const result = await esbuild.analyzeMetafile({
outputs: {
'out.js': {
bytes: 4096,
inputs: {
'in.js': {
bytesInOutput: 1024,
},
},
},
},
})
asserts.assertStrictEquals(result, `
out.js 4.0kb 100.0%
└ in.js 1.0kb 25.0%
`)
})
-43
View File
@@ -5,11 +5,9 @@ const fs = require('fs')
const os = require('os')
const repoDir = path.dirname(__dirname)
const denoDir = path.join(repoDir, 'deno')
const npmDir = path.join(repoDir, 'npm', 'esbuild')
const version = fs.readFileSync(path.join(repoDir, 'version.txt'), 'utf8').trim()
const nodeTarget = 'node10'; // See: https://nodejs.org/en/about/releases/
const denoTarget = 'deno1'; // See: https://docs.deno.com/runtime/fundamentals/stability_and_releases/
const umdBrowserTarget = 'es2015'; // Transpiles "async"
const esmBrowserTarget = 'es2017'; // Preserves "async"
@@ -271,46 +269,6 @@ exports.buildWasmLib = async (esbuildPath) => {
}
}
const buildDenoLib = async (esbuildPath) => {
// Generate "deno/mod.js"
const packageJSON = JSON.parse(fs.readFileSync(path.join(npmDir, 'package.json'), 'utf8'))
childProcess.execFileSync(esbuildPath, [
path.join(repoDir, 'lib', 'deno', 'mod.ts'),
'--bundle',
'--outfile=' + path.join(denoDir, 'mod.js'),
'--target=' + denoTarget,
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
'--define:ESBUILD_BINARY_HASHES=' + JSON.stringify(packageJSON['esbuild.binaryHashes'], null, 2),
'--platform=neutral',
'--log-level=warning',
'--banner:js=/// <reference types="./mod.d.ts" />',
], { cwd: repoDir })
// Generate "deno/wasm.js"
const GOROOT = childProcess.execFileSync('go', ['env', 'GOROOT']).toString().trim()
let wasm_exec_js = fs.readFileSync(path.join(GOROOT, 'lib', 'wasm', 'wasm_exec.js'), 'utf8')
const wasmWorkerCode = await generateWorkerCode({ esbuildPath, wasm_exec_js, minify: true, target: denoTarget })
const modWASM = childProcess.execFileSync(esbuildPath, [
path.join(repoDir, 'lib', 'deno', 'wasm.ts'),
'--bundle',
'--target=' + denoTarget,
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
'--define:WEB_WORKER_SOURCE_CODE=' + JSON.stringify(wasmWorkerCode),
'--platform=neutral',
'--log-level=warning',
'--banner:js=/// <reference types="./wasm.d.ts" />',
], { cwd: repoDir }).toString().replace('WEB_WORKER_FUNCTION', wasmWorkerCode)
fs.writeFileSync(path.join(denoDir, 'wasm.js'), modWASM)
// Generate "deno/mod.d.ts"
const types_ts = fs.readFileSync(path.join(repoDir, 'lib', 'shared', 'types.ts'), 'utf8')
fs.writeFileSync(path.join(denoDir, 'mod.d.ts'), types_ts)
fs.writeFileSync(path.join(denoDir, 'wasm.d.ts'), types_ts)
// And copy the WebAssembly file over to the Deno library as well
fs.copyFileSync(path.join(repoDir, 'npm', 'esbuild-wasm', 'esbuild.wasm'), path.join(repoDir, 'deno', 'esbuild.wasm'))
}
// Writing a file atomically is important for watch mode tests since we don't
// want to read the file after it has been truncated but before the new contents
// have been written.
@@ -402,7 +360,6 @@ exports.dirname = __dirname
// The main Makefile invokes this script before publishing
if (require.main === module) {
if (process.argv.indexOf('--wasm') >= 0) exports.buildWasmLib(process.argv[2])
else if (process.argv.indexOf('--deno') >= 0) buildDenoLib(process.argv[2])
else if (process.argv.indexOf('--version') >= 0) updateVersionPackageJSON(process.argv[2])
else if (process.argv.indexOf('--neutral') >= 0) buildNeutralLib(process.argv[2])
else if (process.argv.indexOf('--update-version-go') >= 0) updateVersionGo()