Skip to content

Commit

Permalink
merge node and browser apis, throw errors instead
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jul 12, 2020
1 parent 92cfbd1 commit 6e3ef52
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 34 deletions.
2 changes: 1 addition & 1 deletion docs/js-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Example usage:
```ts
(async () => {
// Start the esbuild web worker once
const { startService } = require('esbuild-wasm/lib/browser')
const { startService } = require('esbuild-wasm')
const wasmURL = 'node_modules/esbuild-wasm/esbuild.wasm'
const service = await startService({ wasmURL })

Expand Down
32 changes: 30 additions & 2 deletions lib/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,25 @@ import * as common from "./common"

declare let WEB_WORKER_SOURCE_CODE: string

export let startService = (options: types.BrowserServiceOptions): Promise<types.Service> => {
let build: typeof types.build = options => {
throw new Error(`The "build" API only works in node`);
};

let transform: typeof types.transform = (input, options) => {
throw new Error(`The "transform" API only works in node`);
};

let buildSync: typeof types.buildSync = options => {
throw new Error(`The "buildSync" API only works in node`);
};

let transformSync: typeof types.transformSync = (input, options) => {
throw new Error(`The "transformSync" API only works in node`);
};

let startService: typeof types.startService = options => {
if (!options) throw new Error('Must provide an options object to "startService"');
if (!options.wasmURL) throw new Error('Must provide the "wasmURL" option');
return fetch(options.wasmURL).then(r => r.arrayBuffer()).then(wasm => {
let code = `{` +
`let global={};` +
Expand Down Expand Up @@ -45,7 +63,7 @@ export let startService = (options: types.BrowserServiceOptions): Promise<types.

return {
build(options) {
throw new Error('Not implemented yet')
throw new Error(`The "build" API only works in node`)
},
transform: (input, options) =>
new Promise((resolve, reject) =>
Expand All @@ -58,3 +76,13 @@ export let startService = (options: types.BrowserServiceOptions): Promise<types.
}
})
}

let api: typeof types = {
build,
buildSync,
transform,
transformSync,
startService,
};

module.exports = api;
24 changes: 19 additions & 5 deletions lib/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,23 @@ let esbuildCommandAndArgs = (): [string, string[]] => {
// Return true if stderr is a TTY
let isTTY = () => isatty(2);

export let build: typeof types.build = options => {
let build: typeof types.build = options => {
return startService().then(service => {
let promise = service.build(options);
promise.then(service.stop, service.stop); // Kill the service afterwards
return promise;
});
};

export let transform: typeof types.transform = (input, options) => {
let transform: typeof types.transform = (input, options) => {
return startService().then(service => {
let promise = service.transform(input, options);
promise.then(service.stop, service.stop); // Kill the service afterwards
return promise;
});
};

export let buildSync: typeof types.buildSync = options => {
let buildSync: typeof types.buildSync = options => {
let result: types.BuildResult;
runServiceSync(service => service.build(options, isTTY(), (err, res) => {
if (err) throw err;
Expand All @@ -48,7 +48,7 @@ export let buildSync: typeof types.buildSync = options => {
return result!;
};

export let transformSync: typeof types.transformSync = (input, options) => {
let transformSync: typeof types.transformSync = (input, options) => {
let result: types.TransformResult;
runServiceSync(service => service.transform(input, options, isTTY(), (err, res) => {
if (err) throw err;
Expand All @@ -57,7 +57,11 @@ export let transformSync: typeof types.transformSync = (input, options) => {
return result!;
};

export let startService = (): Promise<types.Service> => {
let startService: typeof types.startService = options => {
if (options) {
if (options.wasmURL) throw new Error(`The "wasmURL" option only works in the browser`)
if (options.worker) throw new Error(`The "worker" option only works in the browser`)
}
let [command, args] = esbuildCommandAndArgs();
let child = child_process.spawn(command, args.concat('--service'), {
cwd: process.cwd(),
Expand Down Expand Up @@ -110,3 +114,13 @@ let runServiceSync = (callback: (service: common.StreamService) => void): void =
readFromStdout(stdout);
afterClose();
};

let api: typeof types = {
build,
buildSync,
transform,
transformSync,
startService,
};

module.exports = api;
40 changes: 28 additions & 12 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,34 +127,50 @@ export interface Service {
stop(): void;
}

////////////////////////////////////////////////////////////////////////////////
// Node API

// 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.
//
// Works in node: yes
// Works in browser: no
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.
//
// Works in node: yes
// Works in browser: no
export declare function transform(input: string, options: TransformOptions): Promise<TransformResult>;

// A synchronous version of "build".
//
// Works in node: yes
// Works in browser: no
export declare function buildSync(options: BuildOptions): BuildResult;

// A synchronous version of "transform".
//
// Works in node: yes
// Works in browser: no
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>;

////////////////////////////////////////////////////////////////////////////////
// Browser API

export interface BrowserServiceOptions {
wasmURL: string
//
// Works in node: yes
// Works in browser: yes ("options" is required)
export declare function startService(options?: ServiceOptions): Promise<Service>;

export interface ServiceOptions {
// The URL of the "esbuild.wasm" file. This must be provided when running
// esbuild in the browser.
wasmURL?: string

// By default esbuild runs the WebAssembly-based browser API in a web worker
// to avoid blocking the UI thread. This can be disabled by setting "worker"
// to false.
worker?: boolean
}

export declare function startService(options: BrowserServiceOptions): Promise<Service>;
21 changes: 7 additions & 14 deletions scripts/esbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,6 @@ const fs = require('fs')
const repoDir = path.dirname(__dirname)
const npmDir = path.join(repoDir, 'npm', 'esbuild')

function buildTypeDefinitions(isBrowser) {
const types_ts = fs.readFileSync(path.join(repoDir, 'lib', 'types.ts'), 'utf8')
const nodeAPI = /\/+\r?\n\/\/ Node API/.exec(types_ts).index
const browserAPI = /\/+\r?\n\/\/ Browser API/.exec(types_ts).index
const common = types_ts.slice(0, Math.min(browserAPI, nodeAPI));
if (isBrowser) return common + types_ts.slice(browserAPI, nodeAPI > browserAPI ? nodeAPI : types_ts.length);
else return common + types_ts.slice(nodeAPI, browserAPI > nodeAPI ? browserAPI : types_ts.length);
}

function buildNativeLib(esbuildPath) {
const libDir = path.join(npmDir, 'lib')
try {
Expand All @@ -33,7 +24,8 @@ function buildNativeLib(esbuildPath) {
], { cwd: repoDir })

// Generate "npm/esbuild/lib/main.d.ts"
fs.writeFileSync(path.join(libDir, 'main.d.ts'), buildTypeDefinitions(false))
const types_ts = fs.readFileSync(path.join(repoDir, 'lib', 'types.ts'), 'utf8')
fs.writeFileSync(path.join(libDir, 'main.d.ts'), types_ts)
}

function buildWasmLib(esbuildPath) {
Expand All @@ -55,8 +47,9 @@ function buildWasmLib(esbuildPath) {
], { cwd: repoDir })

// Generate "npm/esbuild-wasm/lib/main.d.ts" and "npm/esbuild-wasm/lib/browser.d.ts"
fs.writeFileSync(path.join(libDir, 'main.d.ts'), buildTypeDefinitions(false))
fs.writeFileSync(path.join(libDir, 'browser.d.ts'), buildTypeDefinitions(true))
const types_ts = fs.readFileSync(path.join(repoDir, 'lib', 'types.ts'), 'utf8')
fs.writeFileSync(path.join(libDir, 'main.d.ts'), types_ts)
fs.writeFileSync(path.join(libDir, 'browser.d.ts'), types_ts)

// Minify "npm/esbuild-wasm/wasm_exec.js"
const wasm_exec_js = path.join(npmWasmDir, 'wasm_exec.js')
Expand All @@ -75,8 +68,8 @@ function buildWasmLib(esbuildPath) {
], { cwd: repoDir }).toString().trim()

// Generate "npm/esbuild-wasm/browser.js"
const umdPrefix = `(exports=>{`
const umdSuffix = `})(typeof exports==="object"?exports:(typeof self!=="undefined"?self:this).esbuild={});\n`
const umdPrefix = `Object.assign(typeof exports==="object"?exports:(typeof self!=="undefined"?self:this).esbuild={},(module=>{`
const umdSuffix = `return module})({}).exports);\n`
const browserJs = childProcess.execFileSync(esbuildPath, [
path.join(repoDir, 'lib', 'browser.ts'),
'--bundle',
Expand Down

0 comments on commit 6e3ef52

Please sign in to comment.