-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
enable using esbuild with deno and wasm (#2359)
- Loading branch information
Showing
10 changed files
with
343 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,21 @@ | |
|
||
## Unreleased | ||
|
||
* Enable using esbuild in Deno via WebAssembly ([#2323](https://github.com/evanw/esbuild/issues/2323)) | ||
|
||
The native implementation of esbuild is much faster than the WebAssembly version, but some people don't want to give Deno the `--allow-run` permission necessary to run esbuild and are ok waiting longer for their builds to finish when using the WebAssembly backend. With this release, you can now use esbuild via WebAssembly in Deno. To do this you will need to import from `wasm.js` instead of `mod.js`: | ||
|
||
```js | ||
import * as esbuild from 'https://deno.land/x/[email protected]/wasm.js' | ||
const ts = 'let test: boolean = true' | ||
const result = await esbuild.transform(ts, { loader: 'ts' }) | ||
console.log('result:', result) | ||
``` | ||
|
||
Make sure you run Deno with `--allow-net` so esbuild can download the WebAssembly module. Using esbuild like this starts up a worker thread that runs esbuild in parallel (unless you call `esbuild.initialize({ worker: false })` to tell esbuild to run on the main thread). If you want to, you can call `esbuild.stop()` to terminate the worker if you won't be using esbuild anymore and you want to reclaim the memory. | ||
|
||
Note that Deno appears to have a bug where background WebAssembly optimization can prevent the process from exiting for many seconds. If you are trying to use Deno and WebAssembly to run esbuild quickly, you may need to manually call `Deno.exit(0)` after your code has finished running. | ||
|
||
* Add support for font file MIME types ([#2337](https://github.com/evanw/esbuild/issues/2337)) | ||
|
||
This release adds support for font file MIME types to esbuild, which means they are now recognized by the built-in local web server and they are now used when a font file is loaded using the `dataurl` loader. The full set of newly-added file extension MIME type mappings is as follows: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -350,7 +350,7 @@ platform-neutral: esbuild | |
node scripts/esbuild.js npm/esbuild/package.json --version | ||
node scripts/esbuild.js ./esbuild --neutral | ||
|
||
platform-deno: esbuild | ||
platform-deno: platform-wasm | ||
node scripts/esbuild.js ./esbuild --deno | ||
|
||
publish-all: check-go-version | ||
|
@@ -489,7 +489,8 @@ publish-deno: | |
test -d deno/.git || (rm -fr deno && git clone [email protected]:esbuild/deno-esbuild.git deno) | ||
cd deno && git fetch && git checkout main && git reset --hard origin/main | ||
@$(MAKE) --no-print-directory platform-deno | ||
cd deno && git commit -am "publish $(ESBUILD_VERSION) to deno" | ||
cd deno && git add mod.js mod.d.ts wasm.js wasm.d.ts esbuild.wasm | ||
cd deno && git commit -m "publish $(ESBUILD_VERSION) to deno" | ||
cd deno && git tag "v$(ESBUILD_VERSION)" | ||
cd deno && git push origin main "v$(ESBUILD_VERSION)" | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import * as types from "../shared/types" | ||
import * as common from "../shared/common" | ||
import * as ourselves from "./wasm" | ||
|
||
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 }) => void | ||
|
||
export let version = ESBUILD_VERSION | ||
|
||
export let build: typeof types.build = (options: types.BuildOptions): Promise<any> => | ||
ensureServiceIsRunning().then(service => | ||
service.build(options)) | ||
|
||
export const serve: typeof types.serve = () => { | ||
throw new Error(`The "serve" API does not work in Deno via WebAssembly`) | ||
} | ||
|
||
export const transform: typeof types.transform = (input, options) => | ||
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() | ||
} | ||
|
||
interface Service { | ||
build: typeof types.build | ||
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('', 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 || '', wasmModule, useWorker); | ||
initializePromise.catch(() => { | ||
// Let the caller try again if this fails | ||
initializePromise = void 0; | ||
}); | ||
await initializePromise; | ||
} | ||
|
||
const startRunningService = async (wasmURL: string, wasmModule: WebAssembly.Module | undefined, useWorker: boolean): Promise<Service> => { | ||
let wasm: WebAssembly.Module; | ||
if (wasmModule) { | ||
wasm = wasmModule; | ||
} else { | ||
if (!wasmURL) wasmURL = new URL('esbuild.wasm', import.meta.url).href | ||
wasm = await WebAssembly.compileStreaming(fetch(wasmURL)) | ||
} | ||
|
||
let worker: { | ||
onmessage: ((event: any) => void) | null | ||
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 })) | ||
worker = { | ||
onmessage: null, | ||
postMessage: data => setTimeout(() => onmessage({ data })), | ||
terminate() { | ||
}, | ||
} | ||
} | ||
|
||
worker.postMessage(wasm) | ||
worker.onmessage = ({ data }) => readFromStdout(data) | ||
|
||
let { readFromStdout, service } = common.createChannel({ | ||
writeToStdin(bytes) { | ||
worker.postMessage(bytes) | ||
}, | ||
isSync: false, | ||
isWriteUnavailable: true, | ||
esbuild: ourselves, | ||
}) | ||
|
||
stopService = () => { | ||
worker.terminate() | ||
initializePromise = undefined | ||
stopService = undefined | ||
} | ||
|
||
return { | ||
build: (options: types.BuildOptions): Promise<any> => | ||
new Promise<types.BuildResult>((resolve, reject) => | ||
service.buildOrServe({ | ||
callName: 'build', | ||
refs: null, | ||
serveOptions: null, | ||
options, | ||
isTTY: false, | ||
defaultWD: '/', | ||
callback: (err, res) => err ? reject(err) : resolve(res as types.BuildResult), | ||
})), | ||
transform: (input, options) => | ||
new Promise((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!), | ||
})), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
Oops, something went wrong.