mirror of
https://github.com/hanzoai/esbuild.git
synced 2026-09-24 16:58:50 +00:00
463 lines
10 KiB
TypeScript
463 lines
10 KiB
TypeScript
// The JavaScript API communicates with the Go child process over stdin/stdout
|
|
// using this protocol. It's a very simple binary protocol that uses primitives
|
|
// and nested arrays and maps. It's basically JSON with UTF-8 encoding and an
|
|
// additional byte array primitive. You must send a response after receiving a
|
|
// request because the other end is blocking on the response coming back.
|
|
|
|
import type * as types from "./types"
|
|
|
|
export interface BuildRequest {
|
|
command: 'build'
|
|
key: number
|
|
entries: [string, string][]; // Use an array instead of a map to preserve order
|
|
flags: string[]
|
|
write: boolean
|
|
stdinContents: Uint8Array | null
|
|
stdinResolveDir: string | null
|
|
absWorkingDir: string
|
|
nodePaths: string[]
|
|
context: boolean
|
|
plugins?: BuildPlugin[]
|
|
mangleCache?: Record<string, string | false>
|
|
}
|
|
|
|
export interface ServeRequest {
|
|
command: 'serve'
|
|
key: number
|
|
onRequest: boolean
|
|
port?: number
|
|
host?: string
|
|
servedir?: string
|
|
keyfile?: string
|
|
certfile?: string
|
|
fallback?: string
|
|
corsOrigin?: string[]
|
|
}
|
|
|
|
export interface ServeResponse {
|
|
port: number
|
|
hosts: string[]
|
|
}
|
|
|
|
export interface BuildPlugin {
|
|
name: string
|
|
onStart: boolean
|
|
onEnd: boolean
|
|
onResolve: { id: number, filter: string, namespace: string }[]
|
|
onLoad: { id: number, filter: string, namespace: string }[]
|
|
}
|
|
|
|
export interface BuildResponse {
|
|
errors: types.Message[]
|
|
warnings: types.Message[]
|
|
outputFiles?: BuildOutputFile[]
|
|
metafile?: Uint8Array
|
|
mangleCache?: Record<string, string | false>
|
|
writeToStdout?: Uint8Array
|
|
}
|
|
|
|
export interface OnEndRequest extends BuildResponse {
|
|
command: 'on-end'
|
|
}
|
|
|
|
export interface OnEndResponse {
|
|
errors: types.Message[]
|
|
warnings: types.Message[]
|
|
}
|
|
|
|
export interface BuildOutputFile {
|
|
path: string
|
|
contents: Uint8Array
|
|
hash: string
|
|
}
|
|
|
|
export interface PingRequest {
|
|
command: 'ping'
|
|
}
|
|
|
|
export interface RebuildRequest {
|
|
command: 'rebuild'
|
|
key: number
|
|
}
|
|
|
|
export interface RebuildResponse {
|
|
errors: types.Message[]
|
|
warnings: types.Message[]
|
|
}
|
|
|
|
export interface DisposeRequest {
|
|
command: 'dispose'
|
|
key: number
|
|
}
|
|
|
|
export interface CancelRequest {
|
|
command: 'cancel'
|
|
key: number
|
|
}
|
|
|
|
export interface WatchRequest {
|
|
command: 'watch'
|
|
key: number
|
|
delay?: number
|
|
}
|
|
|
|
export interface OnServeRequest {
|
|
command: 'serve-request'
|
|
key: number
|
|
args: types.ServeOnRequestArgs
|
|
}
|
|
|
|
export interface TransformRequest {
|
|
command: 'transform'
|
|
flags: string[]
|
|
input: Uint8Array
|
|
inputFS: boolean
|
|
mangleCache?: Record<string, string | false>
|
|
}
|
|
|
|
export interface TransformResponse {
|
|
errors: types.Message[]
|
|
warnings: types.Message[]
|
|
|
|
code: string
|
|
codeFS: boolean
|
|
|
|
map: string
|
|
mapFS: boolean
|
|
|
|
legalComments?: string
|
|
mangleCache?: Record<string, string | false>
|
|
}
|
|
|
|
export interface FormatMsgsRequest {
|
|
command: 'format-msgs'
|
|
messages: types.Message[]
|
|
isWarning: boolean
|
|
color?: boolean
|
|
terminalWidth?: number
|
|
logStyle?: string
|
|
}
|
|
|
|
export interface FormatMsgsResponse {
|
|
messages: string[]
|
|
}
|
|
|
|
export interface AnalyzeMetafileRequest {
|
|
command: 'analyze-metafile'
|
|
metafile: string
|
|
color?: boolean
|
|
verbose?: boolean
|
|
}
|
|
|
|
export interface AnalyzeMetafileResponse {
|
|
result: string
|
|
}
|
|
|
|
export interface OnStartRequest {
|
|
command: 'on-start'
|
|
key: number
|
|
}
|
|
|
|
export interface OnStartResponse {
|
|
errors?: types.PartialMessage[]
|
|
warnings?: types.PartialMessage[]
|
|
}
|
|
|
|
export interface ResolveRequest {
|
|
command: 'resolve'
|
|
key: number
|
|
path: string
|
|
pluginName: string
|
|
importer?: string
|
|
namespace?: string
|
|
resolveDir?: string
|
|
kind?: string
|
|
pluginData?: number
|
|
with?: Record<string, string>
|
|
}
|
|
|
|
export interface ResolveResponse {
|
|
errors: types.Message[]
|
|
warnings: types.Message[]
|
|
|
|
path: string
|
|
external: boolean
|
|
sideEffects: boolean
|
|
namespace: string
|
|
suffix: string
|
|
pluginData: number
|
|
}
|
|
|
|
export interface OnResolveRequest {
|
|
command: 'on-resolve'
|
|
key: number
|
|
ids: number[]
|
|
path: string
|
|
importer: string
|
|
namespace: string
|
|
resolveDir: string
|
|
kind: types.ImportKind
|
|
pluginData: number
|
|
with: Record<string, string>
|
|
}
|
|
|
|
export interface OnResolveResponse {
|
|
id?: number
|
|
pluginName?: string
|
|
|
|
errors?: types.PartialMessage[]
|
|
warnings?: types.PartialMessage[]
|
|
|
|
path?: string
|
|
external?: boolean
|
|
sideEffects?: boolean
|
|
namespace?: string
|
|
suffix?: string
|
|
pluginData?: number
|
|
|
|
watchFiles?: string[]
|
|
watchDirs?: string[]
|
|
}
|
|
|
|
export interface OnLoadRequest {
|
|
command: 'on-load'
|
|
key: number
|
|
ids: number[]
|
|
path: string
|
|
namespace: string
|
|
suffix: string
|
|
pluginData: number
|
|
with: Record<string, string>
|
|
}
|
|
|
|
export interface OnLoadResponse {
|
|
id?: number
|
|
pluginName?: string
|
|
|
|
errors?: types.PartialMessage[]
|
|
warnings?: types.PartialMessage[]
|
|
|
|
contents?: Uint8Array
|
|
resolveDir?: string
|
|
loader?: string
|
|
pluginData?: number
|
|
|
|
watchFiles?: string[]
|
|
watchDirs?: string[]
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
export interface Packet {
|
|
id: number
|
|
isRequest: boolean
|
|
value: Value
|
|
}
|
|
|
|
export type Value =
|
|
| null
|
|
| boolean
|
|
| number
|
|
| string
|
|
| Uint8Array
|
|
| Value[]
|
|
| { [key: string]: Value }
|
|
|
|
export function encodePacket(packet: Packet): Uint8Array<ArrayBuffer> {
|
|
let visit = (value: Value) => {
|
|
if (value === null) {
|
|
bb.write8(0)
|
|
} else if (typeof value === 'boolean') {
|
|
bb.write8(1)
|
|
bb.write8(+value)
|
|
} else if (typeof value === 'number') {
|
|
bb.write8(2)
|
|
bb.write32(value | 0)
|
|
} else if (typeof value === 'string') {
|
|
bb.write8(3)
|
|
bb.write(encodeUTF8(value))
|
|
} else if (value instanceof Uint8Array) {
|
|
bb.write8(4)
|
|
bb.write(value)
|
|
} else if (value instanceof Array) {
|
|
bb.write8(5)
|
|
bb.write32(value.length)
|
|
for (let item of value) {
|
|
visit(item)
|
|
}
|
|
} else {
|
|
let keys = Object.keys(value)
|
|
bb.write8(6)
|
|
bb.write32(keys.length)
|
|
for (let key of keys) {
|
|
bb.write(encodeUTF8(key))
|
|
visit(value[key])
|
|
}
|
|
}
|
|
}
|
|
|
|
let bb = new ByteBuffer
|
|
bb.write32(0); // Reserve space for the length
|
|
bb.write32((packet.id << 1) | +!packet.isRequest)
|
|
visit(packet.value)
|
|
writeUInt32LE(bb.buf, bb.len - 4, 0); // Patch the length in
|
|
return bb.buf.subarray(0, bb.len)
|
|
}
|
|
|
|
export function decodePacket(bytes: Uint8Array<ArrayBuffer>): Packet {
|
|
let visit = (): Value => {
|
|
switch (bb.read8()) {
|
|
case 0: // null
|
|
return null
|
|
case 1: // boolean
|
|
return !!bb.read8()
|
|
case 2: // number
|
|
return bb.read32()
|
|
case 3: // string
|
|
return decodeUTF8(bb.read())
|
|
case 4: // Uint8Array
|
|
return bb.read()
|
|
case 5: { // Value[]
|
|
let count = bb.read32()
|
|
let value: Value[] = []
|
|
for (let i = 0; i < count; i++) {
|
|
value.push(visit())
|
|
}
|
|
return value
|
|
}
|
|
case 6: { // { [key: string]: Value }
|
|
let count = bb.read32()
|
|
let value: { [key: string]: Value } = {}
|
|
for (let i = 0; i < count; i++) {
|
|
value[decodeUTF8(bb.read())] = visit()
|
|
}
|
|
return value
|
|
}
|
|
default:
|
|
throw new Error('Invalid packet')
|
|
}
|
|
}
|
|
|
|
let bb = new ByteBuffer(bytes)
|
|
let id = bb.read32()
|
|
let isRequest = (id & 1) === 0
|
|
id >>>= 1
|
|
let value = visit()
|
|
if (bb.ptr !== bytes.length) {
|
|
throw new Error('Invalid packet')
|
|
}
|
|
return { id, isRequest, value }
|
|
}
|
|
|
|
class ByteBuffer {
|
|
len = 0
|
|
ptr = 0
|
|
|
|
constructor(public buf = new Uint8Array(1024)) {
|
|
}
|
|
|
|
private _write(delta: number): number {
|
|
if (this.len + delta > this.buf.length) {
|
|
let clone = new Uint8Array((this.len + delta) * 2)
|
|
clone.set(this.buf)
|
|
this.buf = clone
|
|
}
|
|
this.len += delta
|
|
return this.len - delta
|
|
}
|
|
|
|
write8(value: number): void {
|
|
let offset = this._write(1)
|
|
this.buf[offset] = value
|
|
}
|
|
|
|
write32(value: number): void {
|
|
let offset = this._write(4)
|
|
writeUInt32LE(this.buf, value, offset)
|
|
}
|
|
|
|
write(bytes: Uint8Array): void {
|
|
let offset = this._write(4 + bytes.length)
|
|
writeUInt32LE(this.buf, bytes.length, offset)
|
|
this.buf.set(bytes, offset + 4)
|
|
}
|
|
|
|
private _read(delta: number): number {
|
|
if (this.ptr + delta > this.buf.length) {
|
|
throw new Error('Invalid packet')
|
|
}
|
|
this.ptr += delta
|
|
return this.ptr - delta
|
|
}
|
|
|
|
read8(): number {
|
|
return this.buf[this._read(1)]
|
|
}
|
|
|
|
read32(): number {
|
|
return readUInt32LE(this.buf, this._read(4))
|
|
}
|
|
|
|
read(): Uint8Array {
|
|
let length = this.read32()
|
|
let bytes = new Uint8Array(length)
|
|
let ptr = this._read(bytes.length)
|
|
bytes.set(this.buf.subarray(ptr, ptr + length))
|
|
return bytes
|
|
}
|
|
}
|
|
|
|
export let encodeUTF8: (text: string) => Uint8Array
|
|
export let decodeUTF8: (bytes: Uint8Array) => string
|
|
let encodeInvariant: string
|
|
|
|
// For the browser and node 12.x
|
|
if (typeof TextEncoder !== 'undefined' && typeof TextDecoder !== 'undefined') {
|
|
let encoder = new TextEncoder()
|
|
let decoder = new TextDecoder()
|
|
encodeUTF8 = text => encoder.encode(text)
|
|
decodeUTF8 = bytes => decoder.decode(bytes)
|
|
encodeInvariant = 'new TextEncoder().encode("")'
|
|
}
|
|
|
|
// For node 10.x
|
|
else if (typeof Buffer !== 'undefined') {
|
|
encodeUTF8 = text => Buffer.from(text)
|
|
decodeUTF8 = bytes => {
|
|
let { buffer, byteOffset, byteLength } = bytes
|
|
return Buffer.from(buffer, byteOffset, byteLength).toString()
|
|
}
|
|
encodeInvariant = 'Buffer.from("")'
|
|
}
|
|
|
|
else {
|
|
throw new Error('No UTF-8 codec found')
|
|
}
|
|
|
|
// Throw an error early if this isn't true. The test framework called "Jest"
|
|
// has some bugs regarding this edge case, and letting esbuild proceed further
|
|
// leads to confusing errors that make it seem like esbuild itself has a bug.
|
|
if (!(encodeUTF8('') instanceof Uint8Array))
|
|
throw new Error(`Invariant violation: "${encodeInvariant} instanceof Uint8Array" is incorrectly false
|
|
|
|
This indicates that your JavaScript environment is broken. You cannot use
|
|
esbuild in this environment because esbuild relies on this invariant. This
|
|
is not a problem with esbuild. You need to fix your environment instead.
|
|
`)
|
|
|
|
export function readUInt32LE(buffer: Uint8Array, offset: number): number {
|
|
return (
|
|
buffer[offset++] |
|
|
(buffer[offset++] << 8) |
|
|
(buffer[offset++] << 16) |
|
|
(buffer[offset++] << 24)
|
|
) >>> 0
|
|
}
|
|
|
|
function writeUInt32LE(buffer: Uint8Array, value: number, offset: number): void {
|
|
buffer[offset++] = value
|
|
buffer[offset++] = value >> 8
|
|
buffer[offset++] = value >> 16
|
|
buffer[offset++] = value >> 24
|
|
}
|