esbuild
Advanced tools
Comparing version 0.4.14 to 0.5.0
@@ -5,8 +5,6 @@ export declare type Target = 'esnext' | 'es6' | 'es2015' | 'es2016' | 'es2017' | 'es2018' | 'es2019' | 'es2020'; | ||
export declare type Loader = 'js' | 'jsx' | 'ts' | 'tsx' | 'json' | 'text' | 'base64' | 'file' | 'dataurl'; | ||
export declare type LogLevel = 'info' | 'warning' | 'error'; | ||
export declare type LogLevel = 'info' | 'warning' | 'error' | 'silent'; | ||
interface CommonOptions { | ||
export interface CommonOptions { | ||
sourcemap?: boolean | 'inline' | 'external'; | ||
sourcefile?: string; | ||
errorLimit?: number; | ||
target?: Target; | ||
@@ -22,6 +20,10 @@ | ||
define?: { [key: string]: string }; | ||
color?: boolean; | ||
logLevel?: LogLevel; | ||
errorLimit?: number; | ||
} | ||
export interface BuildOptions extends CommonOptions { | ||
name?: string; | ||
globalName?: string; | ||
bundle?: boolean; | ||
@@ -36,11 +38,5 @@ outfile?: string; | ||
loader?: { [ext: string]: Loader }; | ||
logLevel?: LogLevel; | ||
resolveExtensions?: string[]; | ||
entryPoints: string[]; | ||
// This defaults to "pipe" which exposes a property called "stderr" on the | ||
// result. This can be set to "inherit" instead to forward the stderr of the | ||
// esbuild process to the current process's stderr. | ||
stdio?: 'pipe' | 'ignore' | 'inherit' | ('pipe' | 'ignore' | 'inherit' | number | null | undefined)[]; | ||
} | ||
@@ -52,4 +48,6 @@ | ||
file: string; | ||
line: string; | ||
column: string; | ||
line: number; // 1-based | ||
column: number; // 0-based, in bytes | ||
length: number; // in bytes | ||
lineText: string; | ||
}; | ||
@@ -59,3 +57,2 @@ } | ||
export interface BuildResult { | ||
stderr: string; | ||
warnings: Message[]; | ||
@@ -65,3 +62,2 @@ } | ||
export interface BuildFailure extends Error { | ||
stderr: string; | ||
errors: Message[]; | ||
@@ -71,46 +67,4 @@ warnings: Message[]; | ||
// This function invokes the "esbuild" command-line tool for you. It returns | ||
// a promise that is either resolved with a "BuildResult" object or rejected | ||
// with a "BuildFailure" object. | ||
// | ||
// Example usage: | ||
// | ||
// const esbuild = require('esbuild') | ||
// const fs = require('fs') | ||
// | ||
// esbuild.build({ | ||
// entryPoints: ['./example.ts'], | ||
// minify: true, | ||
// bundle: true, | ||
// outfile: './example.min.js', | ||
// }).then( | ||
// ({ stderr, warnings }) => { | ||
// const output = fs.readFileSync('./example.min.js', 'utf8') | ||
// console.log('success', { output, stderr, warnings }) | ||
// }, | ||
// ({ stderr, errors, warnings }) => { | ||
// console.error('failure', { stderr, errors, warnings }) | ||
// } | ||
// ) | ||
// | ||
export declare function build(options: BuildOptions): Promise<BuildResult>; | ||
// This starts "esbuild" as a long-lived child process that is then reused, so | ||
// you can call methods on the service many times without the overhead of | ||
// starting up a new child process each time. | ||
export declare function startService(): Promise<Service>; | ||
interface Service { | ||
// This function transforms a single JavaScript file. It can be used to minify | ||
// JavaScript, convert TypeScript/JSX to JavaScript, or convert newer JavaScript | ||
// to older JavaScript. It returns a promise that is either resolved with a | ||
// "TransformResult" object or rejected with a "TransformFailure" object. | ||
transform(file: string, options: TransformOptions): Promise<TransformResult>; | ||
// This stops the service, which kills the long-lived child process. Any | ||
// pending requests will be aborted. | ||
stop(): void; | ||
} | ||
export interface TransformOptions extends CommonOptions { | ||
sourcefile?: string; | ||
loader?: Loader; | ||
@@ -120,4 +74,4 @@ } | ||
export interface TransformResult { | ||
js?: string; | ||
jsSourceMap?: string; | ||
js: string; | ||
jsSourceMap: string; | ||
warnings: Message[]; | ||
@@ -152,1 +106,29 @@ } | ||
} | ||
// This function invokes the "esbuild" command-line tool for you. It returns a | ||
// promise that either resolves with a "BuildResult" object or rejects with a | ||
// "BuildFailure" object. | ||
export declare function build(options: BuildOptions): Promise<BuildResult>; | ||
// This function transforms a single JavaScript file. It can be used to minify | ||
// JavaScript, convert TypeScript/JSX to JavaScript, or convert newer JavaScript | ||
// to older JavaScript. It returns a promise that is either resolved with a | ||
// "TransformResult" object or rejected with a "TransformFailure" object. | ||
export declare function transform(input: string, options: TransformOptions): Promise<TransformResult>; | ||
export declare function buildSync(options: BuildOptions): BuildResult; | ||
export declare function transformSync(input: string, options: TransformOptions): TransformResult; | ||
// This starts "esbuild" as a long-lived child process that is then reused, so | ||
// you can call methods on the service many times without the overhead of | ||
// starting up a new child process each time. | ||
export declare function startService(): Promise<Service>; | ||
export interface Service { | ||
build(options: BuildOptions): Promise<BuildResult>; | ||
transform(input: string, options: TransformOptions): Promise<TransformResult>; | ||
// This stops the service, which kills the long-lived child process. Any | ||
// pending requests will be aborted. | ||
stop(): void; | ||
} |
640
lib/main.js
@@ -1,77 +0,261 @@ | ||
const child_process = require('child_process'); | ||
const path = require('path'); | ||
const os = require('os'); | ||
let __defineProperty = Object.defineProperty; | ||
let __hasOwnProperty = Object.prototype.hasOwnProperty; | ||
let __markAsModule = (target) => { | ||
return __defineProperty(target, "__esModule", {value: true}); | ||
}; | ||
let __export = (target, all) => { | ||
__markAsModule(target); | ||
for (let name in all) | ||
__defineProperty(target, name, {get: all[name], enumerable: true}); | ||
}; | ||
let __exportStar = (target, module2) => { | ||
__markAsModule(target); | ||
for (let key in module2) | ||
if (__hasOwnProperty.call(module2, key) && !__hasOwnProperty.call(target, key) && key !== "default") | ||
__defineProperty(target, key, {get: () => module2[key], enumerable: true}); | ||
return target; | ||
}; | ||
let __toModule = (module2) => { | ||
if (module2 && module2.__esModule) | ||
return module2; | ||
return __exportStar(__defineProperty({}, "default", {value: module2, enumerable: true}), module2); | ||
}; | ||
// This file is used for both the "esbuild" package and the "esbuild-wasm" | ||
// package. The publishing script copies this file into the "esbuild-wasm" | ||
// package and then sets this variable to true. | ||
const WASM = false; | ||
function esbuildSpawn({ flags, stdio }) { | ||
if ((process.platform === 'linux' || process.platform === 'darwin') && os.arch() === 'x64') { | ||
return child_process.spawn(path.join(__dirname, '..', 'bin', 'esbuild'), flags, { | ||
cwd: process.cwd(), | ||
stdio, | ||
}); | ||
// lib/api-common.ts | ||
function pushCommonFlags(flags, options, isTTY2, logLevelDefault) { | ||
if (options.target) | ||
flags.push(`--target=${options.target}`); | ||
if (options.minify) | ||
flags.push("--minify"); | ||
if (options.minifySyntax) | ||
flags.push("--minify-syntax"); | ||
if (options.minifyWhitespace) | ||
flags.push("--minify-whitespace"); | ||
if (options.minifyIdentifiers) | ||
flags.push("--minify-identifiers"); | ||
if (options.jsxFactory) | ||
flags.push(`--jsx-factory=${options.jsxFactory}`); | ||
if (options.jsxFragment) | ||
flags.push(`--jsx-fragment=${options.jsxFragment}`); | ||
if (options.define) | ||
for (let key in options.define) | ||
flags.push(`--define:${key}=${options.define[key]}`); | ||
if (options.color) | ||
flags.push(`--color=${options.color}`); | ||
else if (isTTY2) | ||
flags.push(`--color=true`); | ||
flags.push(`--log-level=${options.logLevel || logLevelDefault}`); | ||
flags.push(`--error-limit=${options.errorLimit || 0}`); | ||
} | ||
function flagsForBuildOptions(options, isTTY2) { | ||
let flags = []; | ||
pushCommonFlags(flags, options, isTTY2, "info"); | ||
if (options.sourcemap) | ||
flags.push(`--sourcemap${options.sourcemap === true ? "" : `=${options.sourcemap}`}`); | ||
if (options.globalName) | ||
flags.push(`--global-name=${options.globalName}`); | ||
if (options.bundle) | ||
flags.push("--bundle"); | ||
if (options.metafile) | ||
flags.push(`--metafile=${options.metafile}`); | ||
if (options.outfile) | ||
flags.push(`--outfile=${options.outfile}`); | ||
if (options.outdir) | ||
flags.push(`--outdir=${options.outdir}`); | ||
if (options.platform) | ||
flags.push(`--platform=${options.platform}`); | ||
if (options.format) | ||
flags.push(`--format=${options.format}`); | ||
if (options.resolveExtensions) | ||
flags.push(`--resolve-extensions=${options.resolveExtensions.join(",")}`); | ||
if (options.external) | ||
for (let name of options.external) | ||
flags.push(`--external:${name}`); | ||
if (options.loader) | ||
for (let ext in options.loader) | ||
flags.push(`--loader:${ext}=${options.loader[ext]}`); | ||
for (let entryPoint of options.entryPoints) { | ||
if (entryPoint.startsWith("-")) | ||
throw new Error(`Invalid entry point: ${entryPoint}`); | ||
flags.push(entryPoint); | ||
} | ||
if (process.platform === 'linux' && os.arch() === 'arm64') { | ||
return child_process.spawn(path.join(__dirname, '..', 'bin', 'esbuild'), flags, { | ||
cwd: process.cwd(), | ||
stdio, | ||
return flags; | ||
} | ||
function flagsForTransformOptions(options, isTTY2) { | ||
let flags = []; | ||
pushCommonFlags(flags, options, isTTY2, "silent"); | ||
if (options.sourcemap) | ||
flags.push(`--sourcemap=${options.sourcemap === true ? "external" : options.sourcemap}`); | ||
if (options.sourcefile) | ||
flags.push(`--sourcefile=${options.sourcefile}`); | ||
if (options.loader) | ||
flags.push(`--loader=${options.loader}`); | ||
return flags; | ||
} | ||
function createChannel(options) { | ||
let requests = new Map(); | ||
let encoder = new TextEncoder(); | ||
let decoder = new TextDecoder(); | ||
let isClosed = false; | ||
let nextID = 0; | ||
let stdout = new Uint8Array(4096); | ||
let stdoutUsed = 0; | ||
let readFromStdout = (chunk) => { | ||
let limit = stdoutUsed + chunk.length; | ||
if (limit > stdout.length) { | ||
let swap = new Uint8Array(limit * 2); | ||
swap.set(stdout); | ||
stdout = swap; | ||
} | ||
stdout.set(chunk, stdoutUsed); | ||
stdoutUsed += chunk.length; | ||
let offset = 0; | ||
while (offset + 4 <= stdoutUsed) { | ||
let length = readUInt32LE(stdout, offset); | ||
if (offset + 4 + length > stdoutUsed) { | ||
break; | ||
} | ||
offset += 4; | ||
handleResponse(stdout.slice(offset, offset + length)); | ||
offset += length; | ||
} | ||
if (offset > 0) { | ||
stdout.set(stdout.slice(offset)); | ||
stdoutUsed -= offset; | ||
} | ||
}; | ||
let afterClose = () => { | ||
isClosed = true; | ||
for (let callback of requests.values()) { | ||
callback("The service was stopped", {}); | ||
} | ||
requests.clear(); | ||
}; | ||
let sendRequest = (request, callback) => { | ||
if (isClosed) | ||
return callback("The service is no longer running", {}); | ||
let id = (nextID++).toString(); | ||
requests.set(id, callback); | ||
let argBuffers = [encoder.encode(id)]; | ||
let length = 4 + 4 + 4 + argBuffers[0].length; | ||
for (let arg of request) { | ||
let argBuffer = encoder.encode(arg); | ||
argBuffers.push(argBuffer); | ||
length += 4 + argBuffer.length; | ||
} | ||
let bytes = new Uint8Array(length); | ||
let offset = 0; | ||
let writeUint32 = (value) => { | ||
writeUInt32LE(bytes, value, offset); | ||
offset += 4; | ||
}; | ||
writeUint32(length - 4); | ||
writeUint32(argBuffers.length); | ||
for (let argBuffer of argBuffers) { | ||
writeUint32(argBuffer.length); | ||
bytes.set(argBuffer, offset); | ||
offset += argBuffer.length; | ||
} | ||
options.writeToStdin(bytes); | ||
}; | ||
let handleResponse = (bytes) => { | ||
let offset = 0; | ||
let eat = (n) => { | ||
offset += n; | ||
if (offset > bytes.length) | ||
throw new Error("Invalid message"); | ||
return offset - n; | ||
}; | ||
let count = readUInt32LE(bytes, eat(4)); | ||
let response = {}; | ||
let id; | ||
for (let i = 0; i < count; i++) { | ||
let keyLength = readUInt32LE(bytes, eat(4)); | ||
let key = decoder.decode(bytes.slice(offset, eat(keyLength) + keyLength)); | ||
let valueLength = readUInt32LE(bytes, eat(4)); | ||
let value = decoder.decode(bytes.slice(offset, eat(valueLength) + valueLength)); | ||
if (key === "id") | ||
id = value; | ||
else | ||
response[key] = value; | ||
} | ||
if (!id) | ||
throw new Error("Invalid message"); | ||
let callback = requests.get(id); | ||
requests.delete(id); | ||
if (response.error) | ||
callback(response.error, {}); | ||
else | ||
callback(null, response); | ||
}; | ||
return { | ||
readFromStdout, | ||
afterClose, | ||
service: { | ||
build(options2, isTTY2, callback) { | ||
let flags = flagsForBuildOptions(options2, isTTY2); | ||
sendRequest(["build"].concat(flags), (error, response) => { | ||
if (error) | ||
return callback(failureErrorWithLog(error, [], []), null); | ||
let errors = jsonToMessages(response.errors); | ||
let warnings = jsonToMessages(response.warnings); | ||
if (errors.length > 0) | ||
return callback(failureErrorWithLog("Build failed", errors, warnings), null); | ||
callback(null, {warnings}); | ||
}); | ||
}, | ||
transform(input, options2, isTTY2, callback) { | ||
let flags = flagsForTransformOptions(options2, isTTY2); | ||
sendRequest(["transform", input].concat(flags), (error, response) => { | ||
if (error) | ||
return callback(failureErrorWithLog(error, [], []), null); | ||
let errors = jsonToMessages(response.errors); | ||
let warnings = jsonToMessages(response.warnings); | ||
if (errors.length > 0) | ||
return callback(failureErrorWithLog("Transform failed", errors, warnings), null); | ||
callback(null, {warnings, js: response.js, jsSourceMap: response.jsSourceMap}); | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
function readUInt32LE(buffer, offset) { | ||
return buffer[offset++] | buffer[offset++] << 8 | buffer[offset++] << 16 | buffer[offset++] << 24; | ||
} | ||
function writeUInt32LE(buffer, value, offset) { | ||
buffer[offset++] = value; | ||
buffer[offset++] = value >> 8; | ||
buffer[offset++] = value >> 16; | ||
buffer[offset++] = value >> 24; | ||
} | ||
function jsonToMessages(json) { | ||
let parts = JSON.parse(json); | ||
let messages = []; | ||
for (let i = 0; i < parts.length; i += 6) { | ||
messages.push({ | ||
text: parts[i], | ||
location: parts[i + 1] < 0 ? null : { | ||
length: parts[i + 1], | ||
file: parts[i + 2], | ||
line: parts[i + 3], | ||
column: parts[i + 4], | ||
lineText: parts[i + 5] | ||
} | ||
}); | ||
} | ||
if (process.platform === 'linux' && os.arch() === 'ppc64' && os.endianness() === 'LE') { | ||
return child_process.spawn(path.join(__dirname, '..', 'bin', 'esbuild'), flags, { | ||
cwd: process.cwd(), | ||
stdio, | ||
}); | ||
} | ||
if (process.platform === 'win32' && os.arch() === 'x64') { | ||
if (WASM) { | ||
return child_process.spawn('node', [path.join(__dirname, '..', 'bin', 'esbuild')].concat(flags), { | ||
cwd: process.cwd(), | ||
windowsHide: true, | ||
stdio, | ||
}); | ||
} else { | ||
return child_process.spawn(path.join(__dirname, '..', 'esbuild.exe'), flags, { | ||
cwd: process.cwd(), | ||
windowsHide: true, | ||
stdio, | ||
}); | ||
} | ||
} | ||
throw new Error(`Unsupported platform: ${process.platform} ${os.arch()}`); | ||
return messages; | ||
} | ||
function pushCommonFlags(flags, options) { | ||
flags.push(`--error-limit=${options.errorLimit || 0}`); | ||
if (options.sourcefile) flags.push(`--sourcefile=${options.sourcefile}`); | ||
if (options.target) flags.push(`--target=${options.target}`); | ||
if (options.minify) flags.push('--minify'); | ||
if (options.minifySyntax) flags.push('--minify-syntax'); | ||
if (options.minifyWhitespace) flags.push('--minify-whitespace'); | ||
if (options.minifyIdentifiers) flags.push('--minify-identifiers'); | ||
if (options.jsxFactory) flags.push(`--jsx-factory=${options.jsxFactory}`); | ||
if (options.jsxFragment) flags.push(`--jsx-fragment=${options.jsxFragment}`); | ||
if (options.define) for (const key in options.define) flags.push(`--define:${key}=${options.define[key]}`); | ||
} | ||
function failureErrorWithLog(text, errors, warnings) { | ||
const limit = 5 | ||
const summary = errors.length < 1 ? '' : ` with ${errors.length} error${errors.length < 2 ? '' : 's'}:` + | ||
errors.slice(0, limit + 1).map((e, i) => { | ||
if (i === limit) return '\n...' | ||
if (!e.location) return `\nerror: ${e.text}`; | ||
const { file, line, column } = e.location; | ||
return `\n${file}:${line}:${column}: error: ${e.text}`; | ||
}).join(''); | ||
const error = new Error(`${text}${summary}`); | ||
let limit = 5; | ||
let summary = errors.length < 1 ? "" : ` with ${errors.length} error${errors.length < 2 ? "" : "s"}:` + errors.slice(0, limit + 1).map((e, i) => { | ||
if (i === limit) | ||
return "\n..."; | ||
if (!e.location) | ||
return ` | ||
error: ${e.text}`; | ||
let {file, line, column} = e.location; | ||
return ` | ||
${file}:${line}:${column}: error: ${e.text}`; | ||
}).join(""); | ||
let error = new Error(`${text}${summary}`); | ||
error.errors = errors; | ||
@@ -82,230 +266,102 @@ error.warnings = warnings; | ||
exports.build = options => { | ||
return new Promise((resolve, reject) => { | ||
const stdio = options.stdio; | ||
const flags = []; | ||
pushCommonFlags(flags, options); | ||
if (options.sourcemap) flags.push(`--sourcemap${options.sourcemap === true ? '' : `=${options.sourcemap}`}`); | ||
if (options.name) flags.push(`--name=${options.name}`); | ||
if (options.bundle) flags.push('--bundle'); | ||
if (options.metafile) flags.push(`--metafile=${options.metafile}`); | ||
if (options.outfile) flags.push(`--outfile=${options.outfile}`); | ||
if (options.outdir) flags.push(`--outdir=${options.outdir}`); | ||
if (options.platform) flags.push(`--platform=${options.platform}`); | ||
if (options.format) flags.push(`--format=${options.format}`); | ||
if (options.color) flags.push(`--color=${options.color}`); | ||
if (options.logLevel) flags.push(`--log-level=${options.logLevel}`); | ||
if (options.resolveExtensions) flags.push(`--resolve-extensions=${options.resolveExtensions.join(',')}`); | ||
if (options.external) for (const name of options.external) flags.push(`--external:${name}`); | ||
if (options.loader) for (const ext in options.loader) flags.push(`--loader:${ext}=${options.loader[ext]}`); | ||
for (const entryPoint of options.entryPoints) { | ||
if (entryPoint.startsWith('-')) throw new Error(`Invalid entry point: ${entryPoint}`); | ||
flags.push(entryPoint); | ||
// lib/api-node.ts | ||
__export(exports, { | ||
build: () => build, | ||
buildSync: () => buildSync, | ||
startService: () => startService, | ||
transform: () => transform, | ||
transformSync: () => transformSync | ||
}); | ||
const child_process = __toModule(require("child_process")); | ||
const path = __toModule(require("path")); | ||
const os = __toModule(require("os")); | ||
const tty = __toModule(require("tty")); | ||
let esbuildCommandAndArgs = () => { | ||
let platform = process.platform; | ||
let arch2 = os.arch(); | ||
if (platform === "darwin" && arch2 === "x64" || platform === "linux" && (arch2 === "x64" || arch2 === "arm64" || arch2 === "ppc64" && os.endianness() === "LE")) { | ||
return [path.join(__dirname, "..", "bin", "esbuild"), []]; | ||
} | ||
if (platform === "win32" && arch2 === "x64") { | ||
if (false) { | ||
return ["node", [path.join(__dirname, "..", "bin", "esbuild")]]; | ||
} else { | ||
return [path.join(__dirname, "..", "esbuild.exe"), []]; | ||
} | ||
const child = esbuildSpawn({ flags, stdio }); | ||
child.on('error', error => reject(error)); | ||
// The stderr pipe won't be available if "stdio" is set to "inherit" | ||
const stderrChunks = []; | ||
if (child.stderr) child.stderr.on('data', chunk => stderrChunks.push(chunk)); | ||
child.on('close', code => { | ||
const fullRegex = /^(.+):(\d+):(\d+): (warning|error): (.+)$/; | ||
const smallRegex = /^(warning|error): (.+)$/; | ||
const errors = []; | ||
const warnings = []; | ||
const stderr = Buffer.concat(stderrChunks).toString(); | ||
for (const line of stderr.split('\n')) { | ||
let match = fullRegex.exec(line); | ||
if (match) { | ||
const [, file, line, column, kind, text] = match; | ||
(kind === 'error' ? errors : warnings).push({ text, location: { file, line: +line, column: +column } }); | ||
} | ||
else { | ||
match = smallRegex.exec(line); | ||
if (match) { | ||
const [, kind, text] = match; | ||
(kind === 'error' ? errors : warnings).push({ text, location: null }); | ||
} | ||
} | ||
} | ||
if (errors.length === 0 && code === 0) { | ||
resolve({ stderr, warnings }); | ||
} | ||
else { | ||
const error = failureErrorWithLog('Build failed', errors, warnings); | ||
error.stderr = stderr; | ||
reject(error); | ||
} | ||
}); | ||
} | ||
throw new Error(`Unsupported platform: ${platform} ${arch2}`); | ||
}; | ||
let isTTY = () => tty.isatty(2); | ||
let build = (options) => { | ||
return startService().then((service) => { | ||
let promise = service.build(options); | ||
promise.then(service.stop, service.stop); | ||
return promise; | ||
}); | ||
} | ||
exports.startService = () => { | ||
return new Promise((resolve, reject) => { | ||
const child = esbuildSpawn({ | ||
flags: ['--service'], | ||
stdio: ['pipe', 'pipe', 'inherit'], | ||
}); | ||
child.on('error', error => reject(error)); | ||
const requests = new Map(); | ||
let isClosed = false; | ||
let nextID = 0; | ||
// Use a long-lived buffer to store stdout data | ||
let stdout = Buffer.alloc(4096); | ||
let stdoutUsed = 0; | ||
child.stdout.on('data', chunk => { | ||
// Append the chunk to the stdout buffer, growing it as necessary | ||
const limit = stdoutUsed + chunk.length; | ||
if (limit > stdout.length) { | ||
let swap = Buffer.alloc(limit * 2); | ||
swap.set(stdout); | ||
stdout = swap; | ||
} | ||
stdout.set(chunk, stdoutUsed); | ||
stdoutUsed += chunk.length; | ||
// Process all complete (i.e. not partial) responses | ||
let offset = 0; | ||
while (offset + 4 <= stdoutUsed) { | ||
const length = stdout.readUInt32LE(offset); | ||
if (offset + 4 + length > stdoutUsed) { | ||
break; | ||
} | ||
offset += 4; | ||
handleResponse(stdout.slice(offset, offset + length)); | ||
offset += length; | ||
} | ||
if (offset > 0) { | ||
stdout.set(stdout.slice(offset)); | ||
stdoutUsed -= offset; | ||
} | ||
}); | ||
child.on('close', () => { | ||
// When the process is closed, fail all pending requests | ||
isClosed = true; | ||
for (const { reject } of requests.values()) { | ||
reject(new Error('The service was stopped')); | ||
} | ||
requests.clear(); | ||
}); | ||
function sendRequest(request) { | ||
return new Promise((resolve, reject) => { | ||
if (isClosed) throw new Error('The service is no longer running'); | ||
// Allocate an id for this request | ||
const id = (nextID++).toString(); | ||
requests.set(id, { resolve, reject }); | ||
// Figure out how long the request will be | ||
const argBuffers = [Buffer.from(id)]; | ||
let length = 4 + 4 + 4 + argBuffers[0].length; | ||
for (const arg of request) { | ||
const argBuffer = Buffer.from(arg); | ||
argBuffers.push(argBuffer); | ||
length += 4 + argBuffer.length; | ||
} | ||
// Write out the request message | ||
const bytes = Buffer.alloc(length); | ||
let offset = 0; | ||
const writeUint32 = value => { | ||
bytes.writeUInt32LE(value, offset); | ||
offset += 4; | ||
}; | ||
writeUint32(length - 4); | ||
writeUint32(argBuffers.length); | ||
for (const argBuffer of argBuffers) { | ||
writeUint32(argBuffer.length); | ||
bytes.set(argBuffer, offset); | ||
offset += argBuffer.length; | ||
} | ||
child.stdin.write(bytes); | ||
}); | ||
}; | ||
let transform = (input, options) => { | ||
return startService().then((service) => { | ||
let promise = service.transform(input, options); | ||
promise.then(service.stop, service.stop); | ||
return promise; | ||
}); | ||
}; | ||
let buildSync = (options) => { | ||
let result; | ||
runServiceSync((service) => service.build(options, isTTY(), (err, res) => { | ||
if (err) | ||
throw err; | ||
result = res; | ||
})); | ||
return result; | ||
}; | ||
let transformSync = (input, options) => { | ||
let result; | ||
runServiceSync((service) => service.transform(input, options, isTTY(), (err, res) => { | ||
if (err) | ||
throw err; | ||
result = res; | ||
})); | ||
return result; | ||
}; | ||
let startService = () => { | ||
let [command, args] = esbuildCommandAndArgs(); | ||
let child = child_process.spawn(command, args.concat("--service"), { | ||
cwd: process.cwd(), | ||
windowsHide: true, | ||
stdio: ["pipe", "pipe", "inherit"] | ||
}); | ||
let {readFromStdout, afterClose, service} = createChannel({ | ||
writeToStdin(bytes) { | ||
child.stdin.write(bytes); | ||
} | ||
function handleResponse(bytes) { | ||
let offset = 0; | ||
const eat = n => { | ||
offset += n; | ||
if (offset > bytes.length) throw new Error('Invalid message'); | ||
return offset - n; | ||
}; | ||
const count = bytes.readUInt32LE(eat(4)); | ||
const response = {}; | ||
let id; | ||
// Parse the response into a map | ||
for (let i = 0; i < count; i++) { | ||
const keyLength = bytes.readUInt32LE(eat(4)); | ||
const key = bytes.slice(offset, eat(keyLength) + keyLength).toString(); | ||
const valueLength = bytes.readUInt32LE(eat(4)); | ||
const value = bytes.slice(offset, eat(valueLength) + valueLength); | ||
if (key === 'id') id = value.toString(); | ||
else response[key] = value.toString(); | ||
} | ||
// Dispatch the response | ||
if (!id) throw new Error('Invalid message'); | ||
const { resolve, reject } = requests.get(id); | ||
requests.delete(id); | ||
if (response.error) reject(new Error(response.error)); | ||
else resolve(response); | ||
}); | ||
child.stdout.on("data", readFromStdout); | ||
child.stdout.on("end", afterClose); | ||
return Promise.resolve({ | ||
build: (options) => new Promise((resolve, reject) => service.build(options, isTTY(), (err, res) => err ? reject(err) : resolve(res))), | ||
transform: (input, options) => new Promise((resolve, reject) => service.transform(input, options, isTTY(), (err, res) => err ? reject(err) : resolve(res))), | ||
stop() { | ||
child.kill(); | ||
} | ||
function jsonToMessages(json) { | ||
const parts = JSON.parse(json); | ||
const messages = []; | ||
for (let i = 0; i < parts.length; i += 4) { | ||
messages.push({ | ||
text: parts[i], | ||
location: parts[i + 1] === '' ? null : { | ||
file: parts[i + 1], | ||
line: parts[i + 2], | ||
column: parts[i + 3], | ||
}, | ||
}); | ||
} | ||
return messages; | ||
}); | ||
}; | ||
let runServiceSync = (callback) => { | ||
let [command, args] = esbuildCommandAndArgs(); | ||
let stdin = new Uint8Array(); | ||
let {readFromStdout, afterClose, service} = createChannel({ | ||
writeToStdin(bytes) { | ||
if (stdin.length !== 0) | ||
throw new Error("Must run at most one command"); | ||
stdin = bytes; | ||
} | ||
// Send an initial ping before resolving with the service object to make | ||
// sure the service is up and running | ||
sendRequest(['ping']).then(() => resolve({ | ||
async transform(file, options) { | ||
const loader = options.loader || 'js'; | ||
const name = `input.${loader}`; | ||
const flags = ['build', `/${name}`, file, '--', name, '--outfile=/output.js']; | ||
pushCommonFlags(flags, options); | ||
if (options.sourcemap) flags.push(`--sourcemap${options.sourcemap === true ? '=external' : `=${options.sourcemap}`}`); | ||
if (options.loader) flags.push(`--loader:.${loader}=${options.loader}`); | ||
const response = await sendRequest(flags); | ||
// Check for failure | ||
const errors = response.errors ? jsonToMessages(response.errors) : []; | ||
const warnings = response.warnings ? jsonToMessages(response.warnings) : []; | ||
if (errors.length > 0) throw failureErrorWithLog('Transform failed', errors, warnings); | ||
// Return the result | ||
const result = { warnings }; | ||
if ('/output.js' in response) result.js = response['/output.js']; | ||
if ('/output.js.map' in response) result.jsSourceMap = response['/output.js.map']; | ||
return result; | ||
}, | ||
stop() { | ||
child.kill(); | ||
}, | ||
}), error => reject(error)); | ||
}); | ||
callback(service); | ||
let stdout = child_process.execFileSync(command, args.concat("--service"), { | ||
cwd: process.cwd(), | ||
windowsHide: true, | ||
input: stdin | ||
}); | ||
readFromStdout(stdout); | ||
afterClose(); | ||
}; |
{ | ||
"name": "esbuild", | ||
"version": "0.4.14", | ||
"version": "0.5.0", | ||
"description": "An extremely fast JavaScript bundler and minifier.", | ||
@@ -5,0 +5,0 @@ "repository": "https://github.com/evanw/esbuild", |
# esbuild | ||
This is a JavaScript bundler and minifier. See https://github.com/evanw/esbuild for details. | ||
## JavaScript API usage | ||
In addition to exposing the `esbuild` command-line tool, this package also exposes a JavaScript API that can be used to invoke the command-line tool from JavaScript. | ||
### Running a build | ||
The `build()` API is the same as invoking the command-line tool. It reads from files on disk and writes back to files on disk. Using this API can be more convenient than managing a lot of command-line flags and also works on all platforms, unlike shell scripts. This is similar to "config files" from other bundlers. | ||
Example build script: | ||
```js | ||
const { build } = require('esbuild') | ||
build({ | ||
stdio: 'inherit', | ||
entryPoints: ['./src/main.ts'], | ||
outfile: './dist/main.js', | ||
minify: true, | ||
bundle: true, | ||
}).catch(() => process.exit(1)) | ||
``` | ||
See [the TypeScript type definitions](https://github.com/evanw/esbuild/blob/master/npm/esbuild/lib/main.d.ts) for the complete set of options. | ||
### Transforming a file | ||
The `transform()` API transforms a single file in memory. It can be used to minify JavaScript, convert TypeScript/JSX to JavaScript, or convert newer JavaScript to older JavaScript. It's roughly equivalent to running `build()` on a single file with `bundle: false`. | ||
To access this API you need to start a service, which is a long-lived `esbuild` child process that is then reused. You can use the service to transform many files without the overhead of starting up a new child process each time. | ||
Example usage: | ||
```js | ||
(async () => { | ||
const jsx = ` | ||
import * as React from 'react' | ||
import * as ReactDOM from 'react-dom' | ||
ReactDOM.render( | ||
<h1>Hello, world!</h1>, | ||
document.getElementById('root') | ||
); | ||
` | ||
// Start the esbuild child process once | ||
const esbuild = require('esbuild') | ||
const service = await esbuild.startService() | ||
// This can be called many times without the overhead of starting a service | ||
const { js } = await service.transform(jsx, { loader: 'jsx' }) | ||
console.log(js) | ||
// The child process can be explicitly killed when it's no longer needed | ||
service.stop() | ||
})() | ||
``` | ||
See [the TypeScript type definitions](https://github.com/evanw/esbuild/blob/master/npm/esbuild/lib/main.d.ts) for the complete set of options. | ||
This is a JavaScript bundler and minifier. See https://github.com/evanw/esbuild and the [JavaScript API documentation](https://github.com/evanw/esbuild/blob/master/docs/js-api.md) for details. |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
548
7
19309
4
3