Comparing version 0.1.26 to 0.2.0
@@ -6,14 +6,6 @@ export declare type Target = 'esnext' | 'es6' | 'es2015' | 'es2016' | 'es2017' | 'es2018' | 'es2019' | 'es2020'; | ||
export interface Options { | ||
name?: string; | ||
bundle?: boolean; | ||
outfile?: string; | ||
outdir?: string; | ||
interface CommonOptions { | ||
sourcemap?: boolean; | ||
errorLimit?: number; | ||
target?: Target; | ||
platform?: Platform; | ||
format?: Format; | ||
color?: boolean; | ||
external?: string[]; | ||
@@ -28,2 +20,13 @@ minify?: boolean; | ||
define?: { [key: string]: string }; | ||
} | ||
export interface BuildOptions extends CommonOptions { | ||
name?: string; | ||
bundle?: boolean; | ||
outfile?: string; | ||
outdir?: string; | ||
platform?: Platform; | ||
format?: Format; | ||
color?: boolean; | ||
external?: string[]; | ||
loader?: { [ext: string]: Loader }; | ||
@@ -48,3 +51,3 @@ | ||
export interface Success { | ||
export interface BuildResult { | ||
stderr: string; | ||
@@ -54,3 +57,3 @@ warnings: Message[]; | ||
export interface Failure extends Error { | ||
export interface BuildFailure extends Error { | ||
stderr: string; | ||
@@ -62,4 +65,4 @@ errors: Message[]; | ||
// This function invokes the "esbuild" command-line tool for you. It returns | ||
// a promise that is either resolved with a "Success" object or rejected with a | ||
// "Failure" object. | ||
// a promise that is either resolved with a "BuildResult" object or rejected | ||
// with a "BuildFailure" object. | ||
// | ||
@@ -86,2 +89,34 @@ // Example usage: | ||
// | ||
export declare function build(options: Options): Promise<Success>; | ||
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 { | ||
loader?: Loader; | ||
} | ||
export interface TransformResult { | ||
js?: string; | ||
jsSourceMap?: string; | ||
warnings: Message[]; | ||
} | ||
export interface TransformFailure extends Error { | ||
errors: Message[]; | ||
warnings: Message[]; | ||
} |
229
lib/main.js
@@ -5,16 +5,43 @@ const child_process = require('child_process'); | ||
function binPath() { | ||
if ((process.platform === 'linux' || process.platform === 'darwin') && os.arch() === 'x64') { | ||
return path.join(__dirname, '..', 'bin', 'esbuild'); | ||
} | ||
if (process.platform === 'win32' && os.arch() === 'x64') { | ||
return path.join(__dirname, '..', '.install', 'node_modules', 'esbuild-windows-64', 'esbuild.exe'); | ||
} | ||
throw new Error(`Unsupported platform: ${process.platform} ${os.arch()}`); | ||
} | ||
function pushCommonFlags(flags, options) { | ||
flags.push(`--error-limit=${options.errorLimit || 0}`); | ||
if (options.sourcemap) flags.push('--sourcemap'); | ||
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 summary = errors.length < 1 ? '' : ` with ${errors.length} error${errors.length < 2 ? '' : 's'}`; | ||
const error = new Error(`${text}${summary}`); | ||
error.errors = errors; | ||
error.warnings = warnings; | ||
return error; | ||
} | ||
exports.build = options => { | ||
return new Promise((resolve, reject) => { | ||
let binPath; | ||
if ((process.platform === 'linux' || process.platform === 'darwin') && os.arch() === 'x64') { | ||
binPath = path.join(__dirname, '..', 'bin', 'esbuild'); | ||
} else if (process.platform === 'win32' && os.arch() === 'x64') { | ||
binPath = path.join(__dirname, '..', '.install', 'node_modules', 'esbuild-windows-64', 'esbuild.exe'); | ||
} else { | ||
throw new Error(`Unsupported platform: ${process.platform} ${os.arch()}`); | ||
} | ||
const flags = [`--error-limit=${options.errorLimit || 0}`]; | ||
const stdio = options.stdio; | ||
const flags = []; | ||
pushCommonFlags(flags, options); | ||
@@ -25,4 +52,2 @@ if (options.name) flags.push(`--name=${options.name}`); | ||
if (options.outdir) flags.push(`--outdir=${options.outdir}`); | ||
if (options.sourcemap) flags.push('--sourcemap'); | ||
if (options.target) flags.push(`--target=${options.target}`); | ||
if (options.platform) flags.push(`--platform=${options.platform}`); | ||
@@ -32,11 +57,2 @@ if (options.format) flags.push(`--format=${options.format}`); | ||
if (options.external) for (const name of options.external) flags.push(`--external:${name}`); | ||
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]}`); | ||
if (options.loader) for (const ext in options.loader) flags.push(`--loader:${ext}=${options.loader[ext]}`); | ||
@@ -49,3 +65,3 @@ | ||
const child = child_process.spawn(binPath, flags, { cwd: process.cwd(), windowsHide: true, stdio }); | ||
const child = child_process.spawn(binPath(), flags, { cwd: process.cwd(), windowsHide: true, stdio }); | ||
child.on('error', error => reject(error)); | ||
@@ -85,8 +101,4 @@ | ||
else { | ||
// The error array will be empty if "stdio" is set to "inherit" | ||
const summary = errors.length < 1 ? '' : ` with ${errors.length} error${errors.length < 2 ? '' : 's'}`; | ||
const error = new Error(`Build failed${summary}`); | ||
const error = failureErrorWithLog('Build failed', errors, warnings); | ||
error.stderr = stderr; | ||
error.errors = errors; | ||
error.warnings = warnings; | ||
reject(error); | ||
@@ -97,1 +109,162 @@ } | ||
} | ||
exports.startService = () => { | ||
return new Promise((resolve, reject) => { | ||
const child = child_process.spawn(binPath(), ['--service'], { | ||
windowsHide: true, | ||
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); | ||
}); | ||
} | ||
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); | ||
} | ||
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; | ||
} | ||
// 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.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(); | ||
}, | ||
})); | ||
}); | ||
}; |
{ | ||
"name": "esbuild", | ||
"version": "0.1.26", | ||
"version": "0.2.0", | ||
"description": "An extremely fast JavaScript bundler and minifier.", | ||
@@ -5,0 +5,0 @@ "repository": "https://github.com/evanw/esbuild", |
@@ -5,28 +5,61 @@ # esbuild | ||
## JavaScript API | ||
## 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. The API is a single function called `build()`. | ||
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. | ||
Example usage: | ||
### 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 esbuild = require('esbuild') | ||
const fs = require('fs') | ||
const { build } = require('esbuild') | ||
esbuild.build({ | ||
entryPoints: ['./example.ts'], | ||
const options = { | ||
stdio: 'inherit', | ||
entryPoints: ['./src/main.ts'], | ||
outfile: './dist/main.js', | ||
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 }) | ||
} | ||
) | ||
} | ||
build(options).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. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
17481
382
65