mirror of
https://github.com/hanzoai/esbuild.git
synced 2026-09-24 16:58:50 +00:00
230 lines
7.6 KiB
TypeScript
230 lines
7.6 KiB
TypeScript
import type * as types from "../shared/types"
|
|
import * as common from "../shared/common"
|
|
|
|
// @ts-ignore TS2303: Circular definition of import alias 'ourselves'.
|
|
import * as ourselves from "./browser"
|
|
|
|
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().build(options)
|
|
|
|
export let context: typeof types.context = (options: types.BuildOptions) =>
|
|
ensureServiceIsRunning().context(options)
|
|
|
|
export const transform: typeof types.transform = (input: string | Uint8Array, options?: types.TransformOptions) =>
|
|
ensureServiceIsRunning().transform(input, options)
|
|
|
|
export const formatMessages: typeof types.formatMessages = (messages, options) =>
|
|
ensureServiceIsRunning().formatMessages(messages, options)
|
|
|
|
export const analyzeMetafile: typeof types.analyzeMetafile = (metafile, options) =>
|
|
ensureServiceIsRunning().analyzeMetafile(metafile, options)
|
|
|
|
export const buildSync: typeof types.buildSync = () => {
|
|
throw new Error(`The "buildSync" API only works in node`)
|
|
}
|
|
|
|
export const transformSync: typeof types.transformSync = () => {
|
|
throw new Error(`The "transformSync" API only works in node`)
|
|
}
|
|
|
|
export const formatMessagesSync: typeof types.formatMessagesSync = () => {
|
|
throw new Error(`The "formatMessagesSync" API only works in node`)
|
|
}
|
|
|
|
export const analyzeMetafileSync: typeof types.analyzeMetafileSync = () => {
|
|
throw new Error(`The "analyzeMetafileSync" API only works in node`)
|
|
}
|
|
|
|
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<void> | undefined
|
|
let stopService: (() => void) | undefined
|
|
let longLivedService: Service | undefined
|
|
|
|
let ensureServiceIsRunning = (): Service => {
|
|
if (longLivedService) return longLivedService
|
|
if (initializePromise) throw new Error('You need to wait for the promise returned from "initialize" to be resolved before calling this')
|
|
throw new Error('You need to call "initialize" before calling this')
|
|
}
|
|
|
|
export const initialize: typeof types.initialize = options => {
|
|
options = common.validateInitializeOptions(options || {})
|
|
let wasmURL = options.wasmURL
|
|
let wasmModule = options.wasmModule
|
|
let useWorker = options.worker !== false
|
|
if (!wasmURL && !wasmModule) throw new Error('Must provide either the "wasmURL" option or the "wasmModule" option')
|
|
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
|
|
})
|
|
return initializePromise
|
|
}
|
|
|
|
const startRunningService = async (wasmURL: string | URL, wasmModule: WebAssembly.Module | undefined, useWorker: boolean): Promise<void> => {
|
|
let worker: {
|
|
onmessage: ((event: any) => void) | null
|
|
postMessage: (data: Uint8Array | ArrayBuffer | WebAssembly.Module) => void
|
|
terminate: () => void
|
|
}
|
|
|
|
let rejectAllWith: (error: unknown) => void
|
|
const rejectAllPromise = new Promise(resolve => rejectAllWith = resolve)
|
|
|
|
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))
|
|
} 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(() => {
|
|
try {
|
|
go = onmessage({ data })
|
|
} catch (error) {
|
|
rejectAllWith(error) // Catch strange crashes (e.g. stack overflow)
|
|
}
|
|
}),
|
|
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, location.href).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
|
|
longLivedService = undefined
|
|
}
|
|
|
|
longLivedService = {
|
|
build: (options: types.BuildOptions) =>
|
|
new Promise<types.BuildResult>((resolve, reject) => {
|
|
rejectAllPromise.then(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) => {
|
|
rejectAllPromise.then(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) => {
|
|
rejectAllPromise.then(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) => {
|
|
rejectAllPromise.then(reject)
|
|
service.formatMessages({
|
|
callName: 'formatMessages',
|
|
refs: null,
|
|
messages,
|
|
options,
|
|
callback: (err, res) => err ? reject(err) : resolve(res!),
|
|
})
|
|
}),
|
|
|
|
analyzeMetafile: (metafile, options) =>
|
|
new Promise((resolve, reject) => {
|
|
rejectAllPromise.then(reject)
|
|
service.analyzeMetafile({
|
|
callName: 'analyzeMetafile',
|
|
refs: null,
|
|
metafile: typeof metafile === 'string' ? metafile : JSON.stringify(metafile),
|
|
options,
|
|
callback: (err, res) => err ? reject(err) : resolve(res!),
|
|
})
|
|
}),
|
|
}
|
|
}
|
|
|
|
// Export this module's exports as an export named "default" to try to work
|
|
// around problems due to the "default" import mess. See the comment in
|
|
// "node.ts" for more detail.
|
|
export default ourselves
|