Skip to content

Commit

Permalink
fix #2081: avoid "new Function" in "esbuild-wasm"
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Mar 4, 2022
1 parent fdf31de commit 536ebd3
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 34 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

* Avoid `new Function` in esbuild's library code ([#2081](https://github.com/evanw/esbuild/issues/2081))

Some JavaScript environments such as Cloudflare Workers or Deno Deploy don't allow `new Function` because they disallow dynamic JavaScript evaluation. Previously esbuild's WebAssembly-based library used this to construct the WebAssembly worker function. With this release, the code is now inlined without using `new Function` so it will be able to run even when this restriction is in place.

## 0.14.25

* Reduce minification of CSS transforms to avoid Safari bugs ([#2057](https://github.com/evanw/esbuild/issues/2057))
Expand Down
16 changes: 4 additions & 12 deletions lib/npm/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import * as common from "../shared/common"
import * as ourselves from "./browser"

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 }) => void
let webWorkerFunction = WEB_WORKER_FUNCTION

export let version = ESBUILD_VERSION;

Expand Down Expand Up @@ -74,14 +75,6 @@ const startRunningService = async (wasmURL: string, useWorker: boolean): Promise
let res = await fetch(wasmURL);
if (!res.ok) throw new Error(`Failed to download ${JSON.stringify(wasmURL)}`);
let wasm = await res.arrayBuffer();
let code = `{` +
`let global={};` +
`for(let o=self;o;o=Object.getPrototypeOf(o))` +
`for(let k of Object.getOwnPropertyNames(o))` +
`if(!(k in global))` +
`Object.defineProperty(global,k,{get:()=>self[k]});` +
WEB_WORKER_SOURCE_CODE +
`}`
let worker: {
onmessage: ((event: any) => void) | null
postMessage: (data: Uint8Array | ArrayBuffer) => void
Expand All @@ -90,12 +83,11 @@ const startRunningService = async (wasmURL: string, useWorker: boolean): Promise

if (useWorker) {
// Run esbuild off the main thread
let blob = new Blob([code], { type: 'text/javascript' })
let blob = new Blob([`onmessage=(${webWorkerFunction})(postMessage)`], { type: 'text/javascript' })
worker = new Worker(URL.createObjectURL(blob))
} else {
// Run esbuild on the main thread
let fn = new Function('postMessage', code + `var onmessage; return m => onmessage(m)`)
let onmessage = fn((data: Uint8Array) => worker.onmessage!({ data }))
let onmessage = webWorkerFunction((data: Uint8Array) => worker.onmessage!({ data }))
worker = {
onmessage: null,
postMessage: data => onmessage({ data }),
Expand Down
45 changes: 23 additions & 22 deletions scripts/esbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,26 +167,29 @@ exports.buildWasmLib = async (esbuildPath) => {
let wasmWorkerCode = {}

for (const [format, target] of Object.entries({ umd: umdBrowserTarget, esm: esmBrowserTarget })) {
// Process "npm/esbuild-wasm/wasm_exec.js"
let wasmExecCode = wasm_exec_js;
if (minify) {
const wasmExecMin = childProcess.execFileSync(esbuildPath, [
'--target=' + target,
].concat(minifyFlags), { cwd: repoDir, input: wasmExecCode }).toString()
const commentLines = wasmExecCode.split('\n')
const firstNonComment = commentLines.findIndex(line => !line.startsWith('//'))
wasmExecCode = '\n' + commentLines.slice(0, firstNonComment).concat(wasmExecMin).join('\n')
}

// Process "lib/worker.ts"
const workerCode = childProcess.execFileSync(esbuildPath, [
path.join(repoDir, 'lib', 'npm', 'worker.ts'),
// Process "npm/esbuild-wasm/wasm_exec.js" and "lib/worker.ts"
const input = `
let onmessage;
let global = {};
for (let o = self; o; o = Object.getPrototypeOf(o))
for (let k of Object.getOwnPropertyNames(o))
if (!(k in global))
Object.defineProperty(global, k, { get: () => self[k] });
${wasm_exec_js}
${fs.readFileSync(path.join(repoDir, 'lib', 'npm', 'worker.ts'), 'utf8')}
return m => onmessage(m)
`;
const wasmExecAndWorker = childProcess.execFileSync(esbuildPath, [
'--loader=ts',
'--target=' + target,
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
'--log-level=warning',
].concat(minifyFlags), { cwd: repoDir }).toString().trim()

wasmWorkerCode[format] = wasmExecCode + workerCode
].concat(minifyFlags), { cwd: repoDir, input }).toString().trim()
const commentLines = wasm_exec_js.split('\n')
const firstNonComment = commentLines.findIndex(line => !line.startsWith('//'))
const commentPrefix = '\n' + commentLines.slice(0, firstNonComment).join('\n') + '\n'
wasmWorkerCode[format] = minify
? `postMessage=>{${commentPrefix}${wasmExecAndWorker}}`
: `(postMessage) => {${(commentPrefix + wasmExecAndWorker).replace(/\n/g, '\n ')}\n}`
}

// Generate "npm/esbuild-wasm/lib/browser.*"
Expand All @@ -198,11 +201,10 @@ exports.buildWasmLib = async (esbuildPath) => {
'--target=' + umdBrowserTarget,
'--format=cjs',
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
'--define:WEB_WORKER_SOURCE_CODE=' + JSON.stringify(wasmWorkerCode.umd),
'--banner:js=' + umdPrefix,
'--footer:js=' + umdSuffix,
'--log-level=warning',
].concat(minifyFlags), { cwd: repoDir }).toString()
].concat(minifyFlags), { cwd: repoDir }).toString().replace('WEB_WORKER_FUNCTION', wasmWorkerCode.umd)
fs.writeFileSync(path.join(libDir, minify ? 'browser.min.js' : 'browser.js'), browserCJS)

// Generate "npm/esbuild-wasm/esm/browser.min.js"
Expand All @@ -212,9 +214,8 @@ exports.buildWasmLib = async (esbuildPath) => {
'--target=' + esmBrowserTarget,
'--format=esm',
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
'--define:WEB_WORKER_SOURCE_CODE=' + JSON.stringify(wasmWorkerCode.esm),
'--log-level=warning',
].concat(minifyFlags), { cwd: repoDir }).toString()
].concat(minifyFlags), { cwd: repoDir }).toString().replace('WEB_WORKER_FUNCTION', wasmWorkerCode.esm)
fs.writeFileSync(path.join(esmDir, minify ? 'browser.min.js' : 'browser.js'), browserESM)
}

Expand Down

0 comments on commit 536ebd3

Please sign in to comment.