const { installForTests, removeRecursiveSync, writeFileAtomic } = require('./esbuild') const { SourceMapConsumer } = require('source-map') const assert = require('assert') const path = require('path') const http = require('http') const fs = require('fs') const vm = require('vm') const readFileAsync = fs.promises.readFile const writeFileAsync = fs.promises.writeFile const mkdirAsync = fs.promises.mkdir const repoDir = path.dirname(__dirname) const rootTestDir = path.join(repoDir, 'scripts', '.js-api-tests') let buildTests = { async errorIfEntryPointsNotArray({ esbuild }) { try { await esbuild.build({ entryPoints: 'this is not an array', logLevel: 'silent', }) throw new Error('Expected build failure'); } catch (e) { if (!e.errors || !e.errors[0] || e.errors[0].text !== '"entryPoints" must be an array or an object') { throw e; } } }, async errorIfBadWorkingDirectory({ esbuild }) { try { await esbuild.build({ absWorkingDir: 'what is this? certainly not an absolute path', logLevel: 'silent', write: false, }) throw new Error('Expected build failure'); } catch (e) { if (e.message !== 'Build failed with 1 error:\nerror: The working directory ' + '"what is this? certainly not an absolute path" is not an absolute path') { throw e; } } }, async errorIfGlob({ esbuild }) { try { await esbuild.build({ entryPoints: ['./src/*.js'], logLevel: 'silent', write: false, }) throw new Error('Expected build failure'); } catch (e) { if (!e.errors || !e.errors[0] || e.errors[0].text !== 'Could not resolve "./src/*.js" ' + '(glob syntax must be expanded first before passing the paths to esbuild)') { throw e; } } }, async workingDirTest({ esbuild, testDir }) { let aDir = path.join(testDir, 'a'); let bDir = path.join(testDir, 'b'); let aFile = path.join(aDir, 'a-in.js'); let bFile = path.join(bDir, 'b-in.js'); let aOut = path.join(aDir, 'a-out.js'); let bOut = path.join(bDir, 'b-out.js'); fs.mkdirSync(aDir, { recursive: true }); fs.mkdirSync(bDir, { recursive: true }); fs.writeFileSync(aFile, 'exports.x = true'); fs.writeFileSync(bFile, 'exports.y = true'); await Promise.all([ esbuild.build({ entryPoints: [path.basename(aFile)], outfile: path.basename(aOut), absWorkingDir: aDir, }), esbuild.build({ entryPoints: [path.basename(bFile)], outfile: path.basename(bOut), absWorkingDir: bDir, }), ]); assert.strictEqual(require(aOut).x, true) assert.strictEqual(require(bOut).y, true) }, async pathResolverEACCS({ esbuild, testDir }) { let outerDir = path.join(testDir, 'outer'); let innerDir = path.join(outerDir, 'inner'); let pkgDir = path.join(testDir, 'node_modules', 'pkg'); let entry = path.join(innerDir, 'entry.js'); let sibling = path.join(innerDir, 'sibling.js'); let index = path.join(pkgDir, 'index.js'); let outfile = path.join(innerDir, 'out.js'); fs.mkdirSync(pkgDir, { recursive: true }); fs.mkdirSync(innerDir, { recursive: true }); fs.writeFileSync(entry, ` import a from "./sibling.js" import b from "pkg" export default {a, b} `); fs.writeFileSync(sibling, `export default 'sibling'`); fs.writeFileSync(index, `export default 'pkg'`); fs.chmodSync(outerDir, 0o111); try { await esbuild.build({ entryPoints: [entry], bundle: true, outfile, format: 'cjs', }); const result = require(outfile); assert.deepStrictEqual(result.default, { a: 'sibling', b: 'pkg' }); } finally { // Restore permission when the test ends so test cleanup works fs.chmodSync(outerDir, 0o755); } }, async nodePathsTest({ esbuild, testDir }) { let srcDir = path.join(testDir, 'src'); let pkgDir = path.join(testDir, 'pkg'); let outfile = path.join(testDir, 'out.js'); let entry = path.join(srcDir, 'entry.js'); let other = path.join(pkgDir, 'other.js'); fs.mkdirSync(srcDir, { recursive: true }); fs.mkdirSync(pkgDir, { recursive: true }); fs.writeFileSync(entry, `export {x} from 'other'`); fs.writeFileSync(other, `export let x = 123`); await esbuild.build({ entryPoints: [entry], outfile, bundle: true, nodePaths: [pkgDir], format: 'cjs', }) assert.strictEqual(require(outfile).x, 123) }, // A local "node_modules" path should be preferred over "NODE_PATH". // See: https://github.com/evanw/esbuild/issues/1117 async nodePathsLocalPreferredTestIssue1117({ esbuild, testDir }) { let srcDir = path.join(testDir, 'src'); let srcOtherDir = path.join(testDir, 'src', 'node_modules', 'other'); let pkgDir = path.join(testDir, 'pkg'); let outfile = path.join(testDir, 'out.js'); let entry = path.join(srcDir, 'entry.js'); let srcOther = path.join(srcOtherDir, 'index.js'); let pkgOther = path.join(pkgDir, 'other.js'); fs.mkdirSync(srcDir, { recursive: true }); fs.mkdirSync(srcOtherDir, { recursive: true }); fs.mkdirSync(pkgDir, { recursive: true }); fs.writeFileSync(entry, `export {x} from 'other'`); fs.writeFileSync(srcOther, `export let x = 234`); fs.writeFileSync(pkgOther, `export let x = 123`); await esbuild.build({ entryPoints: [entry], outfile, bundle: true, nodePaths: [pkgDir], format: 'cjs', }) assert.strictEqual(require(outfile).x, 234) }, async es6_to_cjs({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') await writeFileAsync(input, 'export default 123') const value = await esbuild.build({ entryPoints: [input], bundle: true, outfile: output, format: 'cjs', }) assert.strictEqual(value.outputFiles, void 0) const result = require(output) assert.strictEqual(result.default, 123) assert.strictEqual(result.__esModule, true) }, // Test recursive directory creation async recursiveMkdir({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'a/b/c/d/out.js') await writeFileAsync(input, 'export default 123') await esbuild.build({ entryPoints: [input], bundle: true, outfile: output, format: 'cjs', }) const result = require(output) assert.strictEqual(result.default, 123) assert.strictEqual(result.__esModule, true) }, async outExtensionJS({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'in.mjs') await writeFileAsync(input, 'console.log("test")') await esbuild.build({ entryPoints: [input], outdir: testDir, outExtension: { '.js': '.mjs' }, }) const mjs = await readFileAsync(output, 'utf8') assert.strictEqual(mjs, 'console.log("test");\n') }, async outExtensionCSS({ esbuild, testDir }) { const input = path.join(testDir, 'in.css') const output = path.join(testDir, 'in.notcss') await writeFileAsync(input, 'body {}') await esbuild.build({ entryPoints: [input], outdir: testDir, outExtension: { '.css': '.notcss' }, }) const notcss = await readFileAsync(output, 'utf8') assert.strictEqual(notcss, 'body {\n}\n') }, async sourceMap({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') const content = 'exports.foo = 123' await writeFileAsync(input, content) await esbuild.build({ entryPoints: [input], outfile: output, sourcemap: true, }) const result = require(output) assert.strictEqual(result.foo, 123) const outputFile = await readFileAsync(output, 'utf8') const match = /\/\/# sourceMappingURL=(.*)/.exec(outputFile) assert.strictEqual(match[1], 'out.js.map') const resultMap = await readFileAsync(output + '.map', 'utf8') const json = JSON.parse(resultMap) assert.strictEqual(json.version, 3) assert.strictEqual(json.sources[0], path.basename(input)) assert.strictEqual(json.sourcesContent[0], content) }, async sourceMapExternal({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') const content = 'exports.foo = 123' await writeFileAsync(input, content) await esbuild.build({ entryPoints: [input], outfile: output, sourcemap: 'external', }) const result = require(output) assert.strictEqual(result.foo, 123) const outputFile = await readFileAsync(output, 'utf8') const match = /\/\/# sourceMappingURL=(.*)/.exec(outputFile) assert.strictEqual(match, null) const resultMap = await readFileAsync(output + '.map', 'utf8') const json = JSON.parse(resultMap) assert.strictEqual(json.version, 3) assert.strictEqual(json.sources[0], path.basename(input)) assert.strictEqual(json.sourcesContent[0], content) }, async sourceMapInline({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') const content = 'exports.foo = 123' await writeFileAsync(input, content) await esbuild.build({ entryPoints: [input], outfile: output, sourcemap: 'inline', }) const result = require(output) assert.strictEqual(result.foo, 123) const outputFile = await readFileAsync(output, 'utf8') const match = /\/\/# sourceMappingURL=data:application\/json;base64,(.*)/.exec(outputFile) const json = JSON.parse(Buffer.from(match[1], 'base64').toString()) assert.strictEqual(json.version, 3) assert.strictEqual(json.sources[0], path.basename(input)) assert.strictEqual(json.sourcesContent[0], content) }, async sourceMapBoth({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') const content = 'exports.foo = 123' await writeFileAsync(input, content) await esbuild.build({ entryPoints: [input], outfile: output, sourcemap: 'both', }) const result = require(output) assert.strictEqual(result.foo, 123) const outputFile = await readFileAsync(output, 'utf8') const match = /\/\/# sourceMappingURL=data:application\/json;base64,(.*)/.exec(outputFile) const json = JSON.parse(Buffer.from(match[1], 'base64').toString()) assert.strictEqual(json.version, 3) assert.strictEqual(json.sources[0], path.basename(input)) assert.strictEqual(json.sourcesContent[0], content) const outputFileMap = await readFileAsync(output + '.map', 'utf8') assert.strictEqual(Buffer.from(match[1], 'base64').toString(), outputFileMap) }, async sourceMapIncludeSourcesContent({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') const content = 'exports.foo = 123' await writeFileAsync(input, content) await esbuild.build({ entryPoints: [input], outfile: output, sourcemap: true, sourcesContent: true, }) const result = require(output) assert.strictEqual(result.foo, 123) const outputFile = await readFileAsync(output, 'utf8') const match = /\/\/# sourceMappingURL=(.*)/.exec(outputFile) assert.strictEqual(match[1], 'out.js.map') const resultMap = await readFileAsync(output + '.map', 'utf8') const json = JSON.parse(resultMap) assert.strictEqual(json.version, 3) assert.strictEqual(json.sources[0], path.basename(input)) assert.strictEqual(json.sourcesContent[0], content) }, async sourceMapExcludeSourcesContent({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') const content = 'exports.foo = 123' await writeFileAsync(input, content) await esbuild.build({ entryPoints: [input], outfile: output, sourcemap: true, sourcesContent: false, }) const result = require(output) assert.strictEqual(result.foo, 123) const outputFile = await readFileAsync(output, 'utf8') const match = /\/\/# sourceMappingURL=(.*)/.exec(outputFile) assert.strictEqual(match[1], 'out.js.map') const resultMap = await readFileAsync(output + '.map', 'utf8') const json = JSON.parse(resultMap) assert.strictEqual(json.version, 3) assert.strictEqual(json.sources[0], path.basename(input)) assert.strictEqual(json.sourcesContent, void 0) }, async sourceMapSourceRoot({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') const content = 'exports.foo = 123' await writeFileAsync(input, content) await esbuild.build({ entryPoints: [input], outfile: output, sourcemap: true, sourceRoot: 'https://example.com/' }) const result = require(output) assert.strictEqual(result.foo, 123) const outputFile = await readFileAsync(output, 'utf8') const match = /\/\/# sourceMappingURL=(.*)/.exec(outputFile) assert.strictEqual(match[1], 'out.js.map') const resultMap = await readFileAsync(output + '.map', 'utf8') const json = JSON.parse(resultMap) assert.strictEqual(json.version, 3) assert.strictEqual(json.sources[0], path.basename(input)) assert.strictEqual(json.sourceRoot, 'https://example.com/') }, async sourceMapWithDisabledFile({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const disabled = path.join(testDir, 'disabled.js') const packageJSON = path.join(testDir, 'package.json') const output = path.join(testDir, 'out.js') const content = 'exports.foo = require("./disabled")' await writeFileAsync(input, content) await writeFileAsync(disabled, 'module.exports = 123') await writeFileAsync(packageJSON, `{"browser": {"./disabled.js": false}}`) await esbuild.build({ entryPoints: [input], outfile: output, sourcemap: true, bundle: true, }) const result = require(output) assert.strictEqual(result.foo, void 0) const resultMap = await readFileAsync(output + '.map', 'utf8') const json = JSON.parse(resultMap) assert.strictEqual(json.version, 3) assert.strictEqual(json.sources[0], 'disabled') assert.strictEqual(json.sources[1], path.basename(input)) assert.strictEqual(json.sourcesContent[0], '') assert.strictEqual(json.sourcesContent[1], content) }, async sourceMapWithDisabledModule({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const disabled = path.join(testDir, 'node_modules', 'disabled', 'index.js') const packageJSON = path.join(testDir, 'package.json') const output = path.join(testDir, 'out.js') const content = 'exports.foo = require("disabled")' await mkdirAsync(path.dirname(disabled), { recursive: true }) await writeFileAsync(input, content) await writeFileAsync(disabled, 'module.exports = 123') await writeFileAsync(packageJSON, `{"browser": {"disabled": false}}`) await esbuild.build({ entryPoints: [input], outfile: output, sourcemap: true, bundle: true, }) const result = require(output) assert.strictEqual(result.foo, void 0) const resultMap = await readFileAsync(output + '.map', 'utf8') const json = JSON.parse(resultMap) assert.strictEqual(json.version, 3) assert.strictEqual(json.sources[0], path.relative(testDir, disabled).split(path.sep).join('/')) assert.strictEqual(json.sources[1], path.basename(input)) assert.strictEqual(json.sourcesContent[0], '') assert.strictEqual(json.sourcesContent[1], content) }, async resolveExtensionOrder({ esbuild, testDir }) { const input = path.join(testDir, 'in.js'); const inputBare = path.join(testDir, 'module.js') const inputSomething = path.join(testDir, 'module.something.js') const output = path.join(testDir, 'out.js') await writeFileAsync(input, 'exports.result = require("./module").foo') await writeFileAsync(inputBare, 'exports.foo = 321') await writeFileAsync(inputSomething, 'exports.foo = 123') await esbuild.build({ entryPoints: [input], outfile: output, format: 'cjs', bundle: true, resolveExtensions: ['.something.js', '.js'], }) assert.strictEqual(require(output).result, 123) }, async defineObject({ esbuild, testDir }) { const input = path.join(testDir, 'in.js'); const output = path.join(testDir, 'out.js') await writeFileAsync(input, 'export default {abc, xyz}') await esbuild.build({ entryPoints: [input], outfile: output, format: 'cjs', bundle: true, define: { abc: '["a", "b", "c"]', xyz: '{"x": 1, "y": 2, "z": 3}', }, }) assert.deepStrictEqual(require(output).default, { abc: ['a', 'b', 'c'], xyz: { x: 1, y: 2, z: 3 }, }) }, async inject({ esbuild, testDir }) { const input = path.join(testDir, 'in.js'); const inject = path.join(testDir, 'inject.js') const output = path.join(testDir, 'out.js') await writeFileAsync(input, 'export default foo * 10 + 4') await writeFileAsync(inject, 'export let foo = 123') await esbuild.build({ entryPoints: [input], outfile: output, format: 'cjs', bundle: true, inject: [inject], }) assert.strictEqual(require(output).default, 1234) }, async mainFields({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') const mainFieldsDir = path.join(testDir, 'node_modules', 'main-fields-test') const mainFieldsA = path.join(mainFieldsDir, 'a.js') const mainFieldsB = path.join(mainFieldsDir, 'b.js') const mainFieldsPackage = path.join(mainFieldsDir, 'package.json') await mkdirAsync(mainFieldsDir, { recursive: true }) await writeFileAsync(input, 'export * from "main-fields-test"') await writeFileAsync(mainFieldsA, 'export let foo = "a"') await writeFileAsync(mainFieldsB, 'export let foo = "b"') await writeFileAsync(mainFieldsPackage, '{ "a": "./a.js", "b": "./b.js", "c": "./c.js" }') await esbuild.build({ entryPoints: [input], outfile: output, bundle: true, format: 'cjs', mainFields: ['c', 'b', 'a'], }) const result = require(output) assert.strictEqual(result.foo, 'b') }, async requireAbsolutePath({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const dependency = path.join(testDir, 'dep.js') const output = path.join(testDir, 'out.js') await writeFileAsync(input, `import value from ${JSON.stringify(dependency)}; export default value`) await writeFileAsync(dependency, `export default 123`) const value = await esbuild.build({ entryPoints: [input], bundle: true, outfile: output, format: 'cjs', }) assert.strictEqual(value.outputFiles, void 0) const result = require(output) assert.strictEqual(result.default, 123) assert.strictEqual(result.__esModule, true) }, async fileLoader({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const data = path.join(testDir, 'data.bin') const output = path.join(testDir, 'out.js') await writeFileAsync(input, `export {default as value} from ${JSON.stringify(data)}`) await writeFileAsync(data, `stuff`) const value = await esbuild.build({ entryPoints: [input], bundle: true, outfile: output, format: 'cjs', loader: { '.bin': 'file' }, }) assert.strictEqual(value.outputFiles, void 0) const result = require(output) assert.strictEqual(result.value, './data-BYATPJRB.bin') assert.strictEqual(result.__esModule, true) }, async splittingPublicPath({ esbuild, testDir }) { const input1 = path.join(testDir, 'a', 'in1.js') const input2 = path.join(testDir, 'b', 'in2.js') const shared = path.join(testDir, 'c', 'shared.js') const outdir = path.join(testDir, 'out') await mkdirAsync(path.dirname(input1), { recursive: true }) await mkdirAsync(path.dirname(input2), { recursive: true }) await mkdirAsync(path.dirname(shared), { recursive: true }) await writeFileAsync(input1, `export {default as input1} from ${JSON.stringify(shared)}`) await writeFileAsync(input2, `export {default as input2} from ${JSON.stringify(shared)}`) await writeFileAsync(shared, `export default function foo() { return 123 }`) const value = await esbuild.build({ entryPoints: [input1, input2], bundle: true, outdir, format: 'esm', splitting: true, publicPath: 'https://www.example.com/assets', write: false, }) assert.deepStrictEqual(value.outputFiles.length, 3) assert.deepStrictEqual(value.outputFiles[0].path, path.join(outdir, 'a', 'in1.js')) assert.deepStrictEqual(value.outputFiles[1].path, path.join(outdir, 'b', 'in2.js')) assert.deepStrictEqual(value.outputFiles[2].path, path.join(outdir, 'chunk-JFWYZSOB.js')) assert.deepStrictEqual(value.outputFiles[0].text, `import { foo } from "https://www.example.com/assets/chunk-JFWYZSOB.js"; export { foo as input1 }; `) assert.deepStrictEqual(value.outputFiles[1].text, `import { foo } from "https://www.example.com/assets/chunk-JFWYZSOB.js"; export { foo as input2 }; `) assert.deepStrictEqual(value.outputFiles[2].text, `// scripts/.js-api-tests/splittingPublicPath/c/shared.js function foo() { return 123; } export { foo }; `) }, async fileLoaderPublicPath({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const data = path.join(testDir, 'data.bin') const output = path.join(testDir, 'out.js') await writeFileAsync(input, `export {default as value} from ${JSON.stringify(data)}`) await writeFileAsync(data, `stuff`) const value = await esbuild.build({ entryPoints: [input], bundle: true, outfile: output, format: 'cjs', loader: { '.bin': 'file' }, publicPath: 'https://www.example.com/assets', }) assert.strictEqual(value.outputFiles, void 0) const result = require(output) assert.strictEqual(result.value, 'https://www.example.com/assets/data-BYATPJRB.bin') assert.strictEqual(result.__esModule, true) }, async fileLoaderCSS({ esbuild, testDir }) { const input = path.join(testDir, 'in.css') const data = path.join(testDir, 'data.bin') const output = path.join(testDir, 'out.css') await writeFileAsync(input, `body { background: url(${JSON.stringify(data)}) }`) await writeFileAsync(data, `stuff`) const value = await esbuild.build({ entryPoints: [input], bundle: true, outfile: output, loader: { '.bin': 'file' }, publicPath: 'https://www.example.com/assets', }) assert.strictEqual(value.outputFiles, void 0) assert.strictEqual(await readFileAsync(output, 'utf8'), `/* scripts/.js-api-tests/fileLoaderCSS/in.css */ body { background: url(https://www.example.com/assets/data-BYATPJRB.bin); } `) }, async fileLoaderWithAssetPath({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const data = path.join(testDir, 'data.bin') const outdir = path.join(testDir, 'out') await writeFileAsync(input, `export {default as value} from ${JSON.stringify(data)}`) await writeFileAsync(data, `stuff`) const value = await esbuild.build({ entryPoints: [input], bundle: true, outdir, format: 'cjs', loader: { '.bin': 'file' }, assetNames: 'assets/name=[name]/hash=[hash]', }) assert.strictEqual(value.outputFiles, void 0) const result = require(path.join(outdir, path.basename(input))) assert.strictEqual(result.value, './assets/name=data/hash=BYATPJRB.bin') assert.strictEqual(result.__esModule, true) const stuff = fs.readFileSync(path.join(outdir, result.value), 'utf8') assert.strictEqual(stuff, 'stuff') }, async fileLoaderWithAssetPathAndPublicPath({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const data = path.join(testDir, 'data.bin') const outdir = path.join(testDir, 'out') await writeFileAsync(input, `export {default as value} from ${JSON.stringify(data)}`) await writeFileAsync(data, `stuff`) const value = await esbuild.build({ entryPoints: [input], bundle: true, outdir, format: 'cjs', loader: { '.bin': 'file' }, assetNames: 'assets/name=[name]/hash=[hash]', publicPath: 'https://www.example.com', }) assert.strictEqual(value.outputFiles, void 0) const result = require(path.join(outdir, path.basename(input))) assert.strictEqual(result.value, 'https://www.example.com/assets/name=data/hash=BYATPJRB.bin') assert.strictEqual(result.__esModule, true) const stuff = fs.readFileSync(path.join(outdir, 'assets', 'name=data', 'hash=BYATPJRB.bin'), 'utf8') assert.strictEqual(stuff, 'stuff') }, async fileLoaderBinaryVsText({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const valid = path.join(testDir, 'valid.bin') const invalid = path.join(testDir, 'invalid.bin') const output = path.join(testDir, 'out.js') await writeFileAsync(input, ` import valid from ${JSON.stringify(valid)} import invalid from ${JSON.stringify(invalid)} console.log(valid, invalid) `) await writeFileAsync(valid, Buffer.from([0xCF, 0x80])) await writeFileAsync(invalid, Buffer.from([0x80, 0xCF])) const value = await esbuild.build({ entryPoints: [input], bundle: true, outfile: output, format: 'cjs', loader: { '.bin': 'file' }, write: false, }) assert.strictEqual(value.outputFiles.length, 3) // Valid UTF-8 should decode correctly assert.deepEqual(value.outputFiles[0].contents, new Uint8Array([207, 128])) assert.strictEqual(value.outputFiles[0].text, 'π') // Invalid UTF-8 should be preserved as bytes but should be replaced by the U+FFFD replacement character when decoded assert.deepEqual(value.outputFiles[1].contents, new Uint8Array([128, 207])) assert.strictEqual(value.outputFiles[1].text, '\uFFFD\uFFFD') }, async metafile({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const imported = path.join(testDir, 'imported.js') const text = path.join(testDir, 'text.txt') const css = path.join(testDir, 'example.css') const outputJS = path.join(testDir, 'out.js') const outputCSS = path.join(testDir, 'out.css') await writeFileAsync(entry, ` import x from "./imported" const y = require("./text.txt") import * as z from "./example.css" console.log(x, y, z) `) await writeFileAsync(imported, 'export default 123') await writeFileAsync(text, 'some text') await writeFileAsync(css, 'body { some: css; }') const result = await esbuild.build({ entryPoints: [entry], bundle: true, outfile: outputJS, metafile: true, sourcemap: true, loader: { '.txt': 'file' }, }) const json = result.metafile assert.strictEqual(Object.keys(json.inputs).length, 4) assert.strictEqual(Object.keys(json.outputs).length, 4) const cwd = process.cwd() const makePath = absPath => path.relative(cwd, absPath).split(path.sep).join('/') // Check inputs assert.deepStrictEqual(json.inputs[makePath(entry)].bytes, 144) assert.deepStrictEqual(json.inputs[makePath(entry)].imports, [ { path: makePath(imported), kind: 'import-statement' }, { path: makePath(css), kind: 'import-statement' }, { path: makePath(text), kind: 'require-call' }, ]) assert.deepStrictEqual(json.inputs[makePath(imported)].bytes, 18) assert.deepStrictEqual(json.inputs[makePath(imported)].imports, []) assert.deepStrictEqual(json.inputs[makePath(text)].bytes, 9) assert.deepStrictEqual(json.inputs[makePath(text)].imports, []) assert.deepStrictEqual(json.inputs[makePath(css)].bytes, 19) assert.deepStrictEqual(json.inputs[makePath(css)].imports, []) // Check outputs assert.strictEqual(typeof json.outputs[makePath(outputJS)].bytes, 'number') assert.strictEqual(typeof json.outputs[makePath(outputJS) + '.map'].bytes, 'number') assert.strictEqual(json.outputs[makePath(outputJS)].entryPoint, makePath(entry)) assert.strictEqual(json.outputs[makePath(outputCSS)].entryPoint, undefined) // This is deliberately undefined assert.deepStrictEqual(json.outputs[makePath(outputJS) + '.map'].imports, []) assert.deepStrictEqual(json.outputs[makePath(outputJS) + '.map'].exports, []) assert.deepStrictEqual(json.outputs[makePath(outputJS) + '.map'].inputs, {}) // Check inputs for main output const outputInputs = json.outputs[makePath(outputJS)].inputs assert.strictEqual(Object.keys(outputInputs).length, 4) assert.strictEqual(typeof outputInputs[makePath(entry)].bytesInOutput, 'number') assert.strictEqual(typeof outputInputs[makePath(imported)].bytesInOutput, 'number') assert.strictEqual(typeof outputInputs[makePath(text)].bytesInOutput, 'number') assert.strictEqual(typeof outputInputs[makePath(css)].bytesInOutput, 'number') }, async metafileSplitting({ esbuild, testDir }) { const entry1 = path.join(testDir, 'entry1.js') const entry2 = path.join(testDir, 'entry2.js') const imported = path.join(testDir, 'imported.js') const outdir = path.join(testDir, 'out') await writeFileAsync(entry1, ` import x, {f1} from "./${path.basename(imported)}" console.log(1, x, f1()) export {x} `) await writeFileAsync(entry2, ` import x, {f2} from "./${path.basename(imported)}" console.log('entry 2', x, f2()) export {x as y} `) await writeFileAsync(imported, ` export default 123 export function f1() {} export function f2() {} console.log('shared') `) const result = await esbuild.build({ entryPoints: [entry1, entry2], bundle: true, outdir, metafile: true, splitting: true, format: 'esm', }) const json = result.metafile assert.strictEqual(Object.keys(json.inputs).length, 3) assert.strictEqual(Object.keys(json.outputs).length, 3) const cwd = process.cwd() const makeOutPath = basename => path.relative(cwd, path.join(outdir, basename)).split(path.sep).join('/') const makeInPath = pathname => path.relative(cwd, pathname).split(path.sep).join('/') // Check metafile const inEntry1 = makeInPath(entry1); const inEntry2 = makeInPath(entry2); const inImported = makeInPath(imported); const chunk = 'chunk-LJJNJUOL.js'; const outEntry1 = makeOutPath(path.basename(entry1)); const outEntry2 = makeOutPath(path.basename(entry2)); const outChunk = makeOutPath(chunk); assert.deepStrictEqual(json.inputs[inEntry1], { bytes: 94, imports: [{ path: inImported, kind: 'import-statement' }] }) assert.deepStrictEqual(json.inputs[inEntry2], { bytes: 107, imports: [{ path: inImported, kind: 'import-statement' }] }) assert.deepStrictEqual(json.inputs[inImported], { bytes: 118, imports: [] }) assert.deepStrictEqual(json.outputs[outEntry1].imports, [{ path: makeOutPath(chunk), kind: 'import-statement' }]) assert.deepStrictEqual(json.outputs[outEntry2].imports, [{ path: makeOutPath(chunk), kind: 'import-statement' }]) assert.deepStrictEqual(json.outputs[outChunk].imports, []) assert.deepStrictEqual(json.outputs[outEntry1].exports, ['x']) assert.deepStrictEqual(json.outputs[outEntry2].exports, ['y']) assert.deepStrictEqual(json.outputs[outChunk].exports, ['f1', 'f2', 'imported_default']) assert.deepStrictEqual(json.outputs[outEntry1].inputs, { [inEntry1]: { bytesInOutput: 40 } }) assert.deepStrictEqual(json.outputs[outEntry2].inputs, { [inEntry2]: { bytesInOutput: 48 } }) assert.deepStrictEqual(json.outputs[outChunk].inputs, { [inImported]: { bytesInOutput: 87 } }) }, async metafileSplittingPublicPath({ esbuild, testDir }) { const entry1 = path.join(testDir, 'entry1.js') const entry2 = path.join(testDir, 'entry2.js') const imported = path.join(testDir, 'imported.js') const outdir = path.join(testDir, 'out') await writeFileAsync(entry1, ` import x, {f1} from "./${path.basename(imported)}" console.log(1, x, f1()) export {x} `) await writeFileAsync(entry2, ` import x, {f2} from "./${path.basename(imported)}" console.log('entry 2', x, f2()) export {x as y} `) await writeFileAsync(imported, ` export default 123 export function f1() {} export function f2() {} console.log('shared') `) const result = await esbuild.build({ entryPoints: [entry1, entry2], bundle: true, outdir, metafile: true, splitting: true, format: 'esm', publicPath: 'public', }) const json = result.metafile assert.strictEqual(Object.keys(json.inputs).length, 3) assert.strictEqual(Object.keys(json.outputs).length, 3) const cwd = process.cwd() const makeOutPath = basename => path.relative(cwd, path.join(outdir, basename)).split(path.sep).join('/') const makeInPath = pathname => path.relative(cwd, pathname).split(path.sep).join('/') // Check metafile const inEntry1 = makeInPath(entry1); const inEntry2 = makeInPath(entry2); const inImported = makeInPath(imported); const chunk = 'chunk-GIKHG2FZ.js'; const outEntry1 = makeOutPath(path.basename(entry1)); const outEntry2 = makeOutPath(path.basename(entry2)); const outChunk = makeOutPath(chunk); assert.deepStrictEqual(json.inputs[inEntry1], { bytes: 94, imports: [{ path: inImported, kind: 'import-statement' }] }) assert.deepStrictEqual(json.inputs[inEntry2], { bytes: 107, imports: [{ path: inImported, kind: 'import-statement' }] }) assert.deepStrictEqual(json.inputs[inImported], { bytes: 118, imports: [] }) assert.deepStrictEqual(json.outputs[outEntry1].imports, [{ path: makeOutPath(chunk), kind: 'import-statement' }]) assert.deepStrictEqual(json.outputs[outEntry2].imports, [{ path: makeOutPath(chunk), kind: 'import-statement' }]) assert.deepStrictEqual(json.outputs[outChunk].imports, []) assert.deepStrictEqual(json.outputs[outEntry1].exports, ['x']) assert.deepStrictEqual(json.outputs[outEntry2].exports, ['y']) assert.deepStrictEqual(json.outputs[outChunk].exports, ['f1', 'f2', 'imported_default']) assert.deepStrictEqual(json.outputs[outEntry1].inputs, { [inEntry1]: { bytesInOutput: 40 } }) assert.deepStrictEqual(json.outputs[outEntry2].inputs, { [inEntry2]: { bytesInOutput: 48 } }) assert.deepStrictEqual(json.outputs[outChunk].inputs, { [inImported]: { bytesInOutput: 87 } }) }, async metafileSplittingDoubleDynamicImport({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const importDir = path.join(testDir, 'import-dir') const import1 = path.join(importDir, 'import1.js') const import2 = path.join(importDir, 'import2.js') const shared = path.join(testDir, 'shared.js') const outdir = path.join(testDir, 'out') const makeImportPath = (importing, imported) => JSON.stringify('./' + path.relative(path.dirname(importing), imported).split(path.sep).join('/')) await mkdirAsync(importDir) await writeFileAsync(entry, ` import ${makeImportPath(entry, shared)} import(${makeImportPath(entry, import1)}) import(${makeImportPath(entry, import2)}) `) await writeFileAsync(import1, ` import ${makeImportPath(import1, shared)} `) await writeFileAsync(import2, ` import ${makeImportPath(import2, shared)} `) await writeFileAsync(shared, ` console.log('side effect') `) const result = await esbuild.build({ entryPoints: [entry], bundle: true, outdir, metafile: true, splitting: true, format: 'esm', }) const json = result.metafile assert.strictEqual(Object.keys(json.inputs).length, 4) assert.strictEqual(Object.keys(json.outputs).length, 4) const cwd = process.cwd() const makeOutPath = basename => path.relative(cwd, path.join(outdir, basename)).split(path.sep).join('/') const makeInPath = pathname => path.relative(cwd, pathname).split(path.sep).join('/') // Check metafile const inEntry = makeInPath(entry); const inImport1 = makeInPath(import1); const inImport2 = makeInPath(import2); const inShared = makeInPath(shared); const chunk = 'chunk-M3UIZNA6.js'; const outEntry = makeOutPath(path.relative(testDir, entry)); const outImport1 = makeOutPath('import1-HDYVZ7NF.js'); const outImport2 = makeOutPath('import2-NBRXROVG.js'); const outChunk = makeOutPath(chunk); assert.deepStrictEqual(json.inputs[inEntry], { bytes: 112, imports: [ { path: inShared, kind: 'import-statement' }, { path: inImport1, kind: 'dynamic-import' }, { path: inImport2, kind: 'dynamic-import' }, ] }) assert.deepStrictEqual(json.inputs[inImport1], { bytes: 35, imports: [ { path: inShared, kind: 'import-statement' }, ] }) assert.deepStrictEqual(json.inputs[inImport2], { bytes: 35, imports: [ { path: inShared, kind: 'import-statement' }, ] }) assert.deepStrictEqual(json.inputs[inShared], { bytes: 38, imports: [] }) assert.deepStrictEqual(Object.keys(json.outputs), [ outEntry, outImport1, outImport2, outChunk, ]) assert.deepStrictEqual(json.outputs[outEntry].imports, [ { path: outImport1, kind: 'dynamic-import' }, { path: outImport2, kind: 'dynamic-import' }, { path: outChunk, kind: 'import-statement' }, ]) assert.deepStrictEqual(json.outputs[outImport1].imports, [{ path: outChunk, kind: 'import-statement' }]) assert.deepStrictEqual(json.outputs[outImport2].imports, [{ path: outChunk, kind: 'import-statement' }]) assert.deepStrictEqual(json.outputs[outChunk].imports, []) assert.deepStrictEqual(json.outputs[outEntry].exports, []) assert.deepStrictEqual(json.outputs[outImport1].exports, []) assert.deepStrictEqual(json.outputs[outImport2].exports, []) assert.deepStrictEqual(json.outputs[outChunk].exports, []) assert.deepStrictEqual(json.outputs[outEntry].inputs, { [inEntry]: { bytesInOutput: 72 } }) assert.deepStrictEqual(json.outputs[outImport1].inputs, { [inImport1]: { bytesInOutput: 0 } }) assert.deepStrictEqual(json.outputs[outImport2].inputs, { [inImport2]: { bytesInOutput: 0 } }) assert.deepStrictEqual(json.outputs[outChunk].inputs, { [inShared]: { bytesInOutput: 28 } }) }, async metafileCJSInFormatIIFE({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const outfile = path.join(testDir, 'out.js') await writeFileAsync(entry, `module.exports = {}`) const result = await esbuild.build({ entryPoints: [entry], outfile, metafile: true, format: 'iife', }) const cwd = process.cwd() const makePath = pathname => path.relative(cwd, pathname).split(path.sep).join('/') const json = result.metafile assert.deepStrictEqual(json.inputs[makePath(entry)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].exports, []) }, async metafileCJSInFormatCJS({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const outfile = path.join(testDir, 'out.js') await writeFileAsync(entry, `module.exports = {}`) const result = await esbuild.build({ entryPoints: [entry], outfile, metafile: true, format: 'cjs', }) const cwd = process.cwd() const makePath = pathname => path.relative(cwd, pathname).split(path.sep).join('/') const json = result.metafile assert.deepStrictEqual(json.inputs[makePath(entry)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].exports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].entryPoint, makePath(entry)) }, async metafileCJSInFormatESM({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const outfile = path.join(testDir, 'out.js') await writeFileAsync(entry, `module.exports = {}`) const result = await esbuild.build({ entryPoints: [entry], outfile, metafile: true, format: 'esm', }) const cwd = process.cwd() const makePath = pathname => path.relative(cwd, pathname).split(path.sep).join('/') const json = result.metafile assert.deepStrictEqual(json.inputs[makePath(entry)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].exports, ['default']) }, async metafileESMInFormatIIFE({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const outfile = path.join(testDir, 'out.js') await writeFileAsync(entry, `export let a = 1, b = 2`) const result = await esbuild.build({ entryPoints: [entry], outfile, metafile: true, format: 'iife', }) const cwd = process.cwd() const makePath = pathname => path.relative(cwd, pathname).split(path.sep).join('/') const json = result.metafile assert.deepStrictEqual(json.inputs[makePath(entry)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].exports, []) }, async metafileESMInFormatCJS({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const outfile = path.join(testDir, 'out.js') await writeFileAsync(entry, `export let a = 1, b = 2`) const result = await esbuild.build({ entryPoints: [entry], outfile, metafile: true, format: 'cjs', }) const cwd = process.cwd() const makePath = pathname => path.relative(cwd, pathname).split(path.sep).join('/') const json = result.metafile assert.deepStrictEqual(json.inputs[makePath(entry)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].exports, []) }, async metafileESMInFormatESM({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const outfile = path.join(testDir, 'out.js') await writeFileAsync(entry, `export let a = 1, b = 2`) const result = await esbuild.build({ entryPoints: [entry], outfile, metafile: true, format: 'esm', }) const cwd = process.cwd() const makePath = pathname => path.relative(cwd, pathname).split(path.sep).join('/') const json = result.metafile assert.deepStrictEqual(json.inputs[makePath(entry)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].exports, ['a', 'b']) }, async metafileNestedExportNames({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const nested1 = path.join(testDir, 'nested1.js') const nested2 = path.join(testDir, 'nested2.js') const nested3 = path.join(testDir, 'nested3.js') const outfile = path.join(testDir, 'out.js') await writeFileAsync(entry, ` export {nested1} from ${JSON.stringify(nested1)} export * from ${JSON.stringify(nested2)} export let topLevel = 0 `) await writeFileAsync(nested1, ` import {nested3} from ${JSON.stringify(nested3)} export default 1 export let nested1 = nested3 `) await writeFileAsync(nested2, ` export default 'nested2' export let nested2 = 2 `) await writeFileAsync(nested3, ` export let nested3 = 3 `) const result = await esbuild.build({ entryPoints: [entry], bundle: true, outfile, metafile: true, format: 'esm', }) const cwd = process.cwd() const makePath = pathname => path.relative(cwd, pathname).split(path.sep).join('/') const json = result.metafile assert.deepStrictEqual(json.inputs[makePath(entry)].imports, [ { path: makePath(nested1), kind: 'import-statement' }, { path: makePath(nested2), kind: 'import-statement' }, ]) assert.deepStrictEqual(json.inputs[makePath(nested1)].imports, [ { path: makePath(nested3), kind: 'import-statement' }, ]) assert.deepStrictEqual(json.inputs[makePath(nested2)].imports, []) assert.deepStrictEqual(json.inputs[makePath(nested3)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].imports, []) assert.deepStrictEqual(json.outputs[makePath(outfile)].exports, ['nested1', 'nested2', 'topLevel']) assert.deepStrictEqual(json.outputs[makePath(outfile)].entryPoint, makePath(entry)) }, async metafileCSS({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.css') const imported = path.join(testDir, 'imported.css') const image = path.join(testDir, 'example.png') const output = path.join(testDir, 'out.css') await writeFileAsync(entry, ` @import "./imported"; body { background: url(https://example.com/external.png) } `) await writeFileAsync(imported, ` a { background: url(./example.png) } `) await writeFileAsync(image, 'an image') const result = await esbuild.build({ entryPoints: [entry], bundle: true, outfile: output, metafile: true, sourcemap: true, loader: { '.png': 'dataurl' }, }) const json = result.metafile assert.strictEqual(Object.keys(json.inputs).length, 3) assert.strictEqual(Object.keys(json.outputs).length, 1) const cwd = process.cwd() const makePath = absPath => path.relative(cwd, absPath).split(path.sep).join('/') // Check inputs assert.deepStrictEqual(json, { inputs: { [makePath(entry)]: { bytes: 98, imports: [{ path: makePath(imported), kind: 'import-rule' }] }, [makePath(image)]: { bytes: 8, imports: [] }, [makePath(imported)]: { bytes: 48, imports: [{ path: makePath(image), kind: 'url-token' }] }, }, outputs: { [makePath(output)]: { bytes: 227, entryPoint: makePath(entry), imports: [], inputs: { [makePath(entry)]: { bytesInOutput: 62 }, [makePath(imported)]: { bytesInOutput: 61 }, }, }, }, }) }, async metafileLoaderFileMultipleEntry({ esbuild, testDir }) { const entry1 = path.join(testDir, 'entry1.js') const entry2 = path.join(testDir, 'entry2.js') const file = path.join(testDir, 'x.file') const outdir = path.join(testDir, 'out') await writeFileAsync(entry1, ` export {default} from './x.file' `) await writeFileAsync(entry2, ` import z from './x.file' console.log(z) `) await writeFileAsync(file, `This is a file`) const result = await esbuild.build({ entryPoints: [entry1, entry2], bundle: true, loader: { '.file': 'file' }, outdir, metafile: true, format: 'cjs', }) const json = result.metafile const cwd = process.cwd() const makePath = pathname => path.relative(cwd, pathname).split(path.sep).join('/') const fileName = require(path.join(outdir, 'entry1.js')).default const fileKey = makePath(path.join(outdir, fileName)) assert.deepStrictEqual(json.outputs[fileKey].imports, []) assert.deepStrictEqual(json.outputs[fileKey].exports, []) assert.deepStrictEqual(json.outputs[fileKey].inputs, { [makePath(file)]: { bytesInOutput: 14 } }) }, // Test in-memory output files async writeFalse({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') const inputCode = 'console.log()' await writeFileAsync(input, inputCode) const value = await esbuild.build({ entryPoints: [input], bundle: true, outfile: output, sourcemap: true, format: 'esm', metafile: true, write: false, }) assert.strictEqual(fs.existsSync(output), false) assert.notStrictEqual(value.outputFiles, void 0) assert.strictEqual(value.outputFiles.length, 2) assert.strictEqual(value.outputFiles[0].path, output + '.map') assert.strictEqual(value.outputFiles[0].contents.constructor, Uint8Array) assert.strictEqual(value.outputFiles[1].path, output) assert.strictEqual(value.outputFiles[1].contents.constructor, Uint8Array) const sourceMap = JSON.parse(Buffer.from(value.outputFiles[0].contents).toString()) const js = Buffer.from(value.outputFiles[1].contents).toString() assert.strictEqual(sourceMap.version, 3) assert.strictEqual(js, `// scripts/.js-api-tests/writeFalse/in.js\nconsole.log();\n//# sourceMappingURL=out.js.map\n`) const cwd = process.cwd() const makePath = file => path.relative(cwd, file).split(path.sep).join('/') const meta = value.metafile assert.strictEqual(meta.inputs[makePath(input)].bytes, inputCode.length) assert.strictEqual(meta.outputs[makePath(output)].bytes, js.length) assert.strictEqual(meta.outputs[makePath(output + '.map')].bytes, value.outputFiles[0].contents.length) }, async allowOverwrite({ esbuild, testDir }) { const input = path.join(testDir, 'in.mjs') await writeFileAsync(input, `export default FOO`) // Fail without "allowOverwrite" try { await esbuild.build({ entryPoints: [input], outfile: input, logLevel: 'silent', }) throw new Error('Expected build failure'); } catch (e) { if (!e || !e.errors || !e.errors.length || !e.errors[0].text.includes('Refusing to overwrite input file')) throw e } // Succeed with "allowOverwrite" await esbuild.build({ entryPoints: [input], outfile: input, allowOverwrite: true, define: { FOO: '123' }, }) // This needs to use relative paths to avoid breaking on Windows. // Importing by absolute path doesn't work on Windows in node. const result = await import('./' + path.relative(__dirname, input)) assert.strictEqual(result.default, 123) }, async splittingRelativeSameDir({ esbuild, testDir }) { const inputA = path.join(testDir, 'a.js') const inputB = path.join(testDir, 'b.js') const inputCommon = path.join(testDir, 'common.js') await writeFileAsync(inputA, ` import x from "./${path.basename(inputCommon)}" console.log('a' + x) `) await writeFileAsync(inputB, ` import x from "./${path.basename(inputCommon)}" console.log('b' + x) `) await writeFileAsync(inputCommon, ` export default 'common' `) const outdir = path.join(testDir, 'out') const value = await esbuild.build({ entryPoints: [inputA, inputB], bundle: true, outdir, format: 'esm', splitting: true, write: false, }) assert.strictEqual(value.outputFiles.length, 3) // These should all use forward slashes, even on Windows const chunk = 'chunk-EUXSFQEB.js' assert.strictEqual(Buffer.from(value.outputFiles[0].contents).toString(), `import { common_default } from "./${chunk}"; // scripts/.js-api-tests/splittingRelativeSameDir/a.js console.log("a" + common_default); `) assert.strictEqual(Buffer.from(value.outputFiles[1].contents).toString(), `import { common_default } from "./${chunk}"; // scripts/.js-api-tests/splittingRelativeSameDir/b.js console.log("b" + common_default); `) assert.strictEqual(Buffer.from(value.outputFiles[2].contents).toString(), `// scripts/.js-api-tests/splittingRelativeSameDir/common.js var common_default = "common"; export { common_default }; `) assert.strictEqual(value.outputFiles[0].path, path.join(outdir, path.basename(inputA))) assert.strictEqual(value.outputFiles[1].path, path.join(outdir, path.basename(inputB))) assert.strictEqual(value.outputFiles[2].path, path.join(outdir, chunk)) }, async splittingRelativeNestedDir({ esbuild, testDir }) { const inputA = path.join(testDir, 'a/demo.js') const inputB = path.join(testDir, 'b/demo.js') const inputCommon = path.join(testDir, 'common.js') await mkdirAsync(path.dirname(inputA)).catch(x => x) await mkdirAsync(path.dirname(inputB)).catch(x => x) await writeFileAsync(inputA, ` import x from "../${path.basename(inputCommon)}" console.log('a' + x) `) await writeFileAsync(inputB, ` import x from "../${path.basename(inputCommon)}" console.log('b' + x) `) await writeFileAsync(inputCommon, ` export default 'common' `) const outdir = path.join(testDir, 'out') const value = await esbuild.build({ entryPoints: [inputA, inputB], bundle: true, outdir, format: 'esm', splitting: true, write: false, }) assert.strictEqual(value.outputFiles.length, 3) // These should all use forward slashes, even on Windows const chunk = 'chunk-O2E3MTYM.js' assert.strictEqual(Buffer.from(value.outputFiles[0].contents).toString(), `import { common_default } from "../${chunk}"; // scripts/.js-api-tests/splittingRelativeNestedDir/a/demo.js console.log("a" + common_default); `) assert.strictEqual(Buffer.from(value.outputFiles[1].contents).toString(), `import { common_default } from "../${chunk}"; // scripts/.js-api-tests/splittingRelativeNestedDir/b/demo.js console.log("b" + common_default); `) assert.strictEqual(Buffer.from(value.outputFiles[2].contents).toString(), `// scripts/.js-api-tests/splittingRelativeNestedDir/common.js var common_default = "common"; export { common_default }; `) assert.strictEqual(value.outputFiles[0].path, path.join(outdir, path.relative(testDir, inputA))) assert.strictEqual(value.outputFiles[1].path, path.join(outdir, path.relative(testDir, inputB))) assert.strictEqual(value.outputFiles[2].path, path.join(outdir, chunk)) }, async splittingWithChunkPath({ esbuild, testDir }) { const inputA = path.join(testDir, 'a/demo.js') const inputB = path.join(testDir, 'b/demo.js') const inputCommon = path.join(testDir, 'common.js') await mkdirAsync(path.dirname(inputA)).catch(x => x) await mkdirAsync(path.dirname(inputB)).catch(x => x) await writeFileAsync(inputA, ` import x from "../${path.basename(inputCommon)}" console.log('a' + x) `) await writeFileAsync(inputB, ` import x from "../${path.basename(inputCommon)}" console.log('b' + x) `) await writeFileAsync(inputCommon, ` export default 'common' `) const outdir = path.join(testDir, 'out') const value = await esbuild.build({ entryPoints: [inputA, inputB], bundle: true, outdir, format: 'esm', splitting: true, write: false, chunkNames: 'chunks/name=[name]/hash=[hash]', }) assert.strictEqual(value.outputFiles.length, 3) // These should all use forward slashes, even on Windows const chunk = 'chunks/name=chunk/hash=CHYWWIG3.js' assert.strictEqual(value.outputFiles[0].text, `import { common_default } from "../${chunk}"; // scripts/.js-api-tests/splittingWithChunkPath/a/demo.js console.log("a" + common_default); `) assert.strictEqual(value.outputFiles[1].text, `import { common_default } from "../${chunk}"; // scripts/.js-api-tests/splittingWithChunkPath/b/demo.js console.log("b" + common_default); `) assert.strictEqual(value.outputFiles[2].text, `// scripts/.js-api-tests/splittingWithChunkPath/common.js var common_default = "common"; export { common_default }; `) assert.strictEqual(value.outputFiles[0].path, path.join(outdir, path.relative(testDir, inputA))) assert.strictEqual(value.outputFiles[1].path, path.join(outdir, path.relative(testDir, inputB))) assert.strictEqual(value.outputFiles[2].path, path.join(outdir, chunk)) }, async splittingWithEntryHashes({ esbuild, testDir }) { const inputA = path.join(testDir, 'a/demo.js') const inputB = path.join(testDir, 'b/demo.js') const inputCommon = path.join(testDir, 'common.js') await mkdirAsync(path.dirname(inputA)).catch(x => x) await mkdirAsync(path.dirname(inputB)).catch(x => x) await writeFileAsync(inputA, ` import x from "../${path.basename(inputCommon)}" console.log('a' + x.name) `) await writeFileAsync(inputB, ` import x from "../${path.basename(inputCommon)}" console.log('b' + x.name) `) await writeFileAsync(inputCommon, ` export default { name: 'common' } `) const outdir = path.join(testDir, 'out') const value = await esbuild.build({ entryPoints: [inputA, inputB], bundle: true, outdir, format: 'esm', splitting: true, write: false, entryNames: 'entry/name=[name]/hash=[hash]', chunkNames: 'chunks/name=[name]/hash=[hash]', }) assert.strictEqual(value.outputFiles.length, 3) // These should all use forward slashes, even on Windows const chunk = 'chunks/name=chunk/hash=Q52CWN2F.js' assert.strictEqual(value.outputFiles[0].text, `import { common_default } from "../../${chunk}"; // scripts/.js-api-tests/splittingWithEntryHashes/a/demo.js console.log("a" + common_default.name); `) assert.strictEqual(value.outputFiles[1].text, `import { common_default } from "../../${chunk}"; // scripts/.js-api-tests/splittingWithEntryHashes/b/demo.js console.log("b" + common_default.name); `) assert.strictEqual(value.outputFiles[2].text, `// scripts/.js-api-tests/splittingWithEntryHashes/common.js var common_default = {name: "common"}; export { common_default }; `) const outputA = 'entry/name=demo/hash=XVIIHBIC.js' const outputB = 'entry/name=demo/hash=VXSEQP3F.js' assert.strictEqual(value.outputFiles[0].path, path.join(outdir, outputA)) assert.strictEqual(value.outputFiles[1].path, path.join(outdir, outputB)) assert.strictEqual(value.outputFiles[2].path, path.join(outdir, chunk)) }, async splittingWithChunkPathAndCrossChunkImportsIssue899({ esbuild, testDir }) { const entry1 = path.join(testDir, 'src', 'entry1.js') const entry2 = path.join(testDir, 'src', 'entry2.js') const entry3 = path.join(testDir, 'src', 'entry3.js') const shared1 = path.join(testDir, 'src', 'shared1.js') const shared2 = path.join(testDir, 'src', 'shared2.js') const shared3 = path.join(testDir, 'src', 'shared3.js') await mkdirAsync(path.join(testDir, 'src')).catch(x => x) await writeFileAsync(entry1, ` import { shared1 } from './shared1'; import { shared2 } from './shared2'; export default async function() { return shared1() + shared2(); } `) await writeFileAsync(entry2, ` import { shared2 } from './shared2'; import { shared3 } from './shared3'; export default async function() { return shared2() + shared3(); } `) await writeFileAsync(entry3, ` import { shared3 } from './shared3'; import { shared1 } from './shared1'; export default async function() { return shared3() + shared1(); } `) await writeFileAsync(shared1, ` import { shared2 } from './shared2'; export function shared1() { return shared2().replace('2', '1'); } `) await writeFileAsync(shared2, ` import { shared3 } from './shared3' export function shared2() { return 'shared2'; } `) await writeFileAsync(shared3, ` export function shared3() { return 'shared3'; } `) const outdir = path.join(testDir, 'out') await esbuild.build({ entryPoints: [entry1, entry2, entry3], bundle: true, outdir, format: 'esm', splitting: true, outExtension: { '.js': '.mjs' }, chunkNames: 'chunks/[hash]/[name]', }) // This needs to use relative paths to avoid breaking on Windows. // Importing by absolute path doesn't work on Windows in node. const result1 = await import('./' + path.relative(__dirname, path.join(outdir, 'entry1.mjs'))) const result2 = await import('./' + path.relative(__dirname, path.join(outdir, 'entry2.mjs'))) const result3 = await import('./' + path.relative(__dirname, path.join(outdir, 'entry3.mjs'))) assert.strictEqual(await result1.default(), 'shared1shared2'); assert.strictEqual(await result2.default(), 'shared2shared3'); assert.strictEqual(await result3.default(), 'shared3shared1'); }, async splittingStaticImportHashChange({ esbuild, testDir }) { const input1 = path.join(testDir, 'a', 'in1.js') const input2 = path.join(testDir, 'b', 'in2.js') const outdir = path.join(testDir, 'out') await mkdirAsync(path.dirname(input1), { recursive: true }) await mkdirAsync(path.dirname(input2), { recursive: true }) await writeFileAsync(input1, `import ${JSON.stringify(input2)}`) await writeFileAsync(input2, `console.log(123)`) const result1 = await esbuild.build({ entryPoints: [input1, input2], bundle: true, outdir, format: 'esm', splitting: true, write: false, entryNames: '[name]-[hash]', }) await writeFileAsync(input2, `console.log(321)`) const result2 = await esbuild.build({ entryPoints: [input1, input2], bundle: true, outdir, format: 'esm', splitting: true, write: false, entryNames: '[name]-[hash]', }) assert.strictEqual(result1.outputFiles.length, 3) assert.strictEqual(result2.outputFiles.length, 3) // The hashes of both output files must change. Previously there was a bug // where hash changes worked for static imports but not for dynamic imports. for (const { path: oldPath } of result1.outputFiles) for (const { path: newPath } of result2.outputFiles) assert.notStrictEqual(oldPath, newPath) }, async splittingDynamicImportHashChangeIssue1076({ esbuild, testDir }) { const input1 = path.join(testDir, 'a', 'in1.js') const input2 = path.join(testDir, 'b', 'in2.js') const outdir = path.join(testDir, 'out') await mkdirAsync(path.dirname(input1), { recursive: true }) await mkdirAsync(path.dirname(input2), { recursive: true }) await writeFileAsync(input1, `import(${JSON.stringify(input2)})`) await writeFileAsync(input2, `console.log(123)`) const result1 = await esbuild.build({ entryPoints: [input1], bundle: true, outdir, format: 'esm', splitting: true, write: false, entryNames: '[name]-[hash]', }) await writeFileAsync(input2, `console.log(321)`) const result2 = await esbuild.build({ entryPoints: [input1], bundle: true, outdir, format: 'esm', splitting: true, write: false, entryNames: '[name]-[hash]', }) assert.strictEqual(result1.outputFiles.length, 2) assert.strictEqual(result2.outputFiles.length, 2) // The hashes of both output files must change. Previously there was a bug // where hash changes worked for static imports but not for dynamic imports. for (const { path: oldPath } of result1.outputFiles) for (const { path: newPath } of result2.outputFiles) assert.notStrictEqual(oldPath, newPath) }, async stdinStdoutBundle({ esbuild, testDir }) { const auxiliary = path.join(testDir, 'auxiliary.js') await writeFileAsync(auxiliary, 'export default 123') const value = await esbuild.build({ stdin: { contents: ` import x from './auxiliary.js' console.log(x) `, resolveDir: testDir, }, bundle: true, write: false, }) assert.strictEqual(value.outputFiles.length, 1) assert.strictEqual(value.outputFiles[0].path, '') assert.strictEqual(Buffer.from(value.outputFiles[0].contents).toString(), `(() => { // scripts/.js-api-tests/stdinStdoutBundle/auxiliary.js var auxiliary_default = 123; // console.log(auxiliary_default); })(); `) }, async stdinOutfileBundle({ esbuild, testDir }) { const auxiliary = path.join(testDir, 'auxiliary.js') const outfile = path.join(testDir, 'out.js') await writeFileAsync(auxiliary, 'export default 123') const value = await esbuild.build({ stdin: { contents: ` import x from './auxiliary.js' export {x as fromStdin} `, resolveDir: testDir, }, bundle: true, outfile, format: 'cjs', }) assert.strictEqual(value.outputFiles, void 0) const result = require(outfile) assert.strictEqual(result.fromStdin, 123) }, async stdinAndEntryBundle({ esbuild, testDir }) { const srcDir = path.join(testDir, 'src') const entry = path.join(srcDir, 'entry.js') const auxiliary = path.join(srcDir, 'auxiliary.js') const outdir = path.join(testDir, 'out') await mkdirAsync(srcDir) await writeFileAsync(auxiliary, 'export default 123') await writeFileAsync(entry, ` import x from './auxiliary.js' export let fromEntry = x `) const value = await esbuild.build({ entryPoints: [entry], stdin: { contents: ` import x from './src/auxiliary.js' export {x as fromStdin} `, resolveDir: testDir, }, bundle: true, outdir, format: 'cjs', }) assert.strictEqual(value.outputFiles, void 0) const entryResult = require(path.join(outdir, path.basename(entry))) assert.strictEqual(entryResult.fromEntry, 123) const stdinResult = require(path.join(outdir, path.basename('stdin.js'))) assert.strictEqual(stdinResult.fromStdin, 123) }, async forceTsConfig({ esbuild, testDir }) { // ./tsconfig.json // ./a/forced-config.json // ./a/b/test-impl.js // ./a/b/c/in.js const aDir = path.join(testDir, 'a') const bDir = path.join(aDir, 'b') const cDir = path.join(bDir, 'c') await mkdirAsync(aDir).catch(x => x) await mkdirAsync(bDir).catch(x => x) await mkdirAsync(cDir).catch(x => x) const input = path.join(cDir, 'in.js') const forced = path.join(bDir, 'test-impl.js') const tsconfigIgnore = path.join(testDir, 'tsconfig.json') const tsconfigForced = path.join(aDir, 'forced-config.json') const output = path.join(testDir, 'out.js') await writeFileAsync(input, 'import "test"') await writeFileAsync(forced, 'console.log("success")') await writeFileAsync(tsconfigIgnore, '{"compilerOptions": {"baseUrl": "./a", "paths": {"test": ["./ignore.js"]}}}') await writeFileAsync(tsconfigForced, '{"compilerOptions": {"baseUrl": "./b", "paths": {"test": ["./test-impl.js"]}}}') await esbuild.build({ entryPoints: [input], bundle: true, outfile: output, tsconfig: tsconfigForced, format: 'esm', }) const result = await readFileAsync(output, 'utf8') assert.strictEqual(result, `// scripts/.js-api-tests/forceTsConfig/a/b/test-impl.js console.log("success"); `) }, async es5({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const cjs = path.join(testDir, 'cjs.js') const esm = path.join(testDir, 'esm.js') const output = path.join(testDir, 'out.js') await writeFileAsync(input, ` export {foo} from "./cjs" export * as bar from "./esm" `) await writeFileAsync(cjs, 'exports.foo = 123') await writeFileAsync(esm, 'export var foo = 123') const value = await esbuild.build({ entryPoints: [input], bundle: true, outfile: output, format: 'cjs', target: 'es5', }) assert.strictEqual(value.outputFiles, void 0) const result = require(output) assert.strictEqual(result.foo, 123) assert.strictEqual(result.bar.foo, 123) assert.strictEqual(result.__esModule, true) const contents = await readFileAsync(output, 'utf8') assert.strictEqual(contents.indexOf('=>'), -1) assert.strictEqual(contents.indexOf('const'), -1) }, async outbaseImplicit({ esbuild, testDir }) { const outbase = path.join(testDir, 'pages', 'a') const b = path.join(outbase, 'b', 'index.js') const c = path.join(outbase, 'c', 'index.js') const outdir = path.join(testDir, 'outdir') await mkdirAsync(path.dirname(b), { recursive: true }) await mkdirAsync(path.dirname(c), { recursive: true }) await writeFileAsync(b, 'module.exports = "b"') await writeFileAsync(c, 'module.exports = "c"') await esbuild.build({ entryPoints: [ path.relative(process.cwd(), b), path.relative(process.cwd(), c), ], outdir, format: 'cjs', }) const outB = path.join(outdir, path.relative(outbase, b)) const outC = path.join(outdir, path.relative(outbase, c)) assert.strictEqual(require(outB), 'b') assert.strictEqual(require(outC), 'c') }, async outbaseRelPath({ esbuild, testDir }) { const outbase = path.join(testDir, 'pages') const b = path.join(outbase, 'a', 'b', 'index.js') const c = path.join(outbase, 'a', 'c', 'index.js') const outdir = path.join(testDir, 'outdir') await mkdirAsync(path.dirname(b), { recursive: true }) await mkdirAsync(path.dirname(c), { recursive: true }) await writeFileAsync(b, 'module.exports = "b"') await writeFileAsync(c, 'module.exports = "c"') await esbuild.build({ entryPoints: [ path.relative(process.cwd(), b), path.relative(process.cwd(), c), ], outdir, outbase, format: 'cjs', }) const outB = path.join(outdir, path.relative(outbase, b)) const outC = path.join(outdir, path.relative(outbase, c)) assert.strictEqual(require(outB), 'b') assert.strictEqual(require(outC), 'c') }, async outbaseAbsPath({ esbuild, testDir }) { const outbase = path.join(testDir, 'pages') const b = path.join(outbase, 'a', 'b', 'index.js') const c = path.join(outbase, 'a', 'c', 'index.js') const outdir = path.join(testDir, 'outdir') await mkdirAsync(path.dirname(b), { recursive: true }) await mkdirAsync(path.dirname(c), { recursive: true }) await writeFileAsync(b, 'module.exports = "b"') await writeFileAsync(c, 'module.exports = "c"') await esbuild.build({ entryPoints: [b, c], outdir, outbase, format: 'cjs', }) const outB = path.join(outdir, path.relative(outbase, b)) const outC = path.join(outdir, path.relative(outbase, c)) assert.strictEqual(require(outB), 'b') assert.strictEqual(require(outC), 'c') }, async bundleTreeShakingDefault({ esbuild }) { const { outputFiles } = await esbuild.build({ stdin: { contents: ` let removeMe1 = /* @__PURE__ */ fn(); let removeMe2 =
; `, loader: 'jsx', }, write: false, bundle: true, }) assert.strictEqual(outputFiles[0].text, `(() => {\n})();\n`) }, async bundleTreeShakingTrue({ esbuild }) { const { outputFiles } = await esbuild.build({ stdin: { contents: ` let removeMe1 = /* @__PURE__ */ fn(); let removeMe2 =
; `, loader: 'jsx', }, write: false, bundle: true, treeShaking: true, }) assert.strictEqual(outputFiles[0].text, `(() => {\n})();\n`) }, async bundleTreeShakingIgnoreAnnotations({ esbuild }) { const { outputFiles } = await esbuild.build({ stdin: { contents: ` let keepMe1 = /* @__PURE__ */ fn(); let keepMe2 =
; `, loader: 'jsx', }, write: false, bundle: true, treeShaking: 'ignore-annotations', }) assert.strictEqual(outputFiles[0].text, `(() => { // var keepMe1 = fn(); var keepMe2 = React.createElement("div", null); })(); `) }, async externalWithWildcard({ esbuild }) { const { outputFiles } = await esbuild.build({ stdin: { contents: `require('/assets/file.png')`, }, write: false, bundle: true, external: ['/assets/*.png'], }) assert.strictEqual(outputFiles[0].text, `(() => { // require("/assets/file.png"); })(); `) }, async errorInvalidExternalWithTwoWildcards({ esbuild }) { try { await esbuild.build({ entryPoints: ['in.js'], external: ['a*b*c'], write: false, logLevel: 'silent', }) throw new Error('Expected build failure'); } catch (e) { if (e.message !== 'Build failed with 1 error:\nerror: External path "a*b*c" cannot have more than one "*" wildcard') { throw e; } } }, async jsBannerBuild({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const outfile = path.join(testDir, 'out.js') await writeFileAsync(input, `if (!bannerDefined) throw 'fail'`) await esbuild.build({ entryPoints: [input], outfile, banner: { js: 'const bannerDefined = true' }, }) require(outfile) }, async jsFooterBuild({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const outfile = path.join(testDir, 'out.js') await writeFileAsync(input, `footer()`) await esbuild.build({ entryPoints: [input], outfile, footer: { js: 'function footer() {}' }, }) require(outfile) }, async jsBannerFooterBuild({ esbuild, testDir }) { const aPath = path.join(testDir, 'a.js') const bPath = path.join(testDir, 'b.js') const outdir = path.join(testDir, 'out') await writeFileAsync(aPath, `module.exports = { banner: bannerDefined, footer };`) await writeFileAsync(bPath, `module.exports = { banner: bannerDefined, footer };`) await esbuild.build({ entryPoints: [aPath, bPath], outdir, banner: { js: 'const bannerDefined = true' }, footer: { js: 'function footer() {}' }, }) const a = require(path.join(outdir, path.basename(aPath))) const b = require(path.join(outdir, path.basename(bPath))) if (!a.banner || !b.banner) throw 'fail' a.footer() b.footer() }, async cssBannerFooterBuild({ esbuild, testDir }) { const input = path.join(testDir, 'in.css') const outfile = path.join(testDir, 'out.css') await writeFileAsync(input, `div { color: red }`) await esbuild.build({ entryPoints: [input], outfile, banner: { css: '/* banner */' }, footer: { css: '/* footer */' }, }) const code = await readFileAsync(outfile, 'utf8') assert.strictEqual(code, `/* banner */\ndiv {\n color: red;\n}\n/* footer */\n`) }, async buildRelativeIssue693({ esbuild }) { const result = await esbuild.build({ stdin: { contents: `const x=1`, }, write: false, outfile: 'esbuild.js', }); assert.strictEqual(result.outputFiles.length, 1) assert.strictEqual(result.outputFiles[0].path, path.join(process.cwd(), 'esbuild.js')) assert.strictEqual(result.outputFiles[0].text, 'const x = 1;\n') }, async noRebuild({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') await writeFileAsync(input, `console.log('abc')`) const result1 = await esbuild.build({ entryPoints: [input], outfile: output, format: 'esm', incremental: false, }) assert.strictEqual(result1.outputFiles, void 0) assert.strictEqual(await readFileAsync(output, 'utf8'), `console.log("abc");\n`) assert.strictEqual(result1.rebuild, void 0) }, async rebuildBasic({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') // Build 1 await writeFileAsync(input, `console.log('abc')`) const result1 = await esbuild.build({ entryPoints: [input], outfile: output, format: 'esm', incremental: true, }) assert.strictEqual(result1.outputFiles, void 0) assert.strictEqual(await readFileAsync(output, 'utf8'), `console.log("abc");\n`) // Build 2 await writeFileAsync(input, `console.log('xyz')`) const result2 = await result1.rebuild(); assert.strictEqual(result2.rebuild, result1.rebuild) assert.strictEqual(result2.outputFiles, void 0) assert.strictEqual(await readFileAsync(output, 'utf8'), `console.log("xyz");\n`) // Build 3 await writeFileAsync(input, `console.log(123)`) const result3 = await result1.rebuild(); assert.strictEqual(result3.rebuild, result1.rebuild) assert.strictEqual(result3.outputFiles, void 0) assert.strictEqual(await readFileAsync(output, 'utf8'), `console.log(123);\n`) // Further rebuilds should not be possible after a dispose result1.rebuild.dispose() try { await result1.rebuild() throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e.message, 'Cannot rebuild') } }, async rebuildIndependent({ esbuild, testDir }) { const inputA = path.join(testDir, 'in-a.js') const inputB = path.join(testDir, 'in-b.js') const outputA = path.join(testDir, 'out-a.js') const outputB = path.join(testDir, 'out-b.js') // Build 1 await writeFileAsync(inputA, `console.log('a')`) await writeFileAsync(inputB, `console.log('b')`) const resultA1 = await esbuild.build({ entryPoints: [inputA], outfile: outputA, format: 'esm', incremental: true, }) const resultB1 = await esbuild.build({ entryPoints: [inputB], outfile: outputB, format: 'esm', incremental: true, }) assert.notStrictEqual(resultA1.rebuild, resultB1.rebuild) assert.strictEqual(resultA1.outputFiles, void 0) assert.strictEqual(resultB1.outputFiles, void 0) assert.strictEqual(await readFileAsync(outputA, 'utf8'), `console.log("a");\n`) assert.strictEqual(await readFileAsync(outputB, 'utf8'), `console.log("b");\n`) // Build 2 await writeFileAsync(inputA, `console.log(1)`) await writeFileAsync(inputB, `console.log(2)`) const promiseA = resultA1.rebuild(); const promiseB = resultB1.rebuild(); const resultA2 = await promiseA; const resultB2 = await promiseB; assert.strictEqual(resultA2.rebuild, resultA1.rebuild) assert.strictEqual(resultB2.rebuild, resultB1.rebuild) assert.strictEqual(resultA2.outputFiles, void 0) assert.strictEqual(resultB2.outputFiles, void 0) assert.strictEqual(await readFileAsync(outputA, 'utf8'), `console.log(1);\n`) assert.strictEqual(await readFileAsync(outputB, 'utf8'), `console.log(2);\n`) // Further rebuilds should not be possible after a dispose resultA1.rebuild.dispose() try { await resultA1.rebuild() throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e.message, 'Cannot rebuild') } // Build 3 await writeFileAsync(inputB, `console.log(3)`) const resultB3 = await resultB1.rebuild() assert.strictEqual(resultB3.rebuild, resultB1.rebuild) assert.strictEqual(resultB3.outputFiles, void 0) assert.strictEqual(await readFileAsync(outputB, 'utf8'), `console.log(3);\n`) // Further rebuilds should not be possible after a dispose resultB1.rebuild.dispose() try { await resultB1.rebuild() throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e.message, 'Cannot rebuild') } }, async rebuildParallel({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') // Build 1 await writeFileAsync(input, `console.log('abc')`) const result1 = await esbuild.build({ entryPoints: [input], outfile: output, format: 'esm', incremental: true, }) assert.strictEqual(result1.outputFiles, void 0) assert.strictEqual(await readFileAsync(output, 'utf8'), `console.log("abc");\n`) // Build 2 await writeFileAsync(input, `console.log('xyz')`) const promise2A = result1.rebuild(); const promise2B = result1.rebuild(); const result2A = await promise2A; const result2B = await promise2B; assert.strictEqual(result2A.rebuild, result1.rebuild) assert.strictEqual(result2B.rebuild, result1.rebuild) assert.strictEqual(result2A.outputFiles, void 0) assert.strictEqual(result2B.outputFiles, void 0) assert.strictEqual(await readFileAsync(output, 'utf8'), `console.log("xyz");\n`) // Build 3 await writeFileAsync(input, `console.log(123)`) const promise3A = result1.rebuild(); const promise3B = result1.rebuild(); const result3A = await promise3A; const result3B = await promise3B; assert.strictEqual(result3A.rebuild, result1.rebuild) assert.strictEqual(result3B.rebuild, result1.rebuild) assert.strictEqual(result3A.outputFiles, void 0) assert.strictEqual(result3B.outputFiles, void 0) assert.strictEqual(await readFileAsync(output, 'utf8'), `console.log(123);\n`) // Further rebuilds should not be possible after a dispose result1.rebuild.dispose() try { await result1.rebuild() throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e.message, 'Cannot rebuild') } }, async bundleAvoidTDZ({ esbuild }) { var { outputFiles } = await esbuild.build({ stdin: { contents: ` class Foo { // The above line will be transformed into "var". However, the // symbol "Foo" must still be defined before the class body ends. static foo = new Foo } if (!(Foo.foo instanceof Foo)) throw 'fail' `, }, bundle: true, write: false, }) assert.strictEqual(outputFiles.length, 1) new Function(outputFiles[0].text)() }, async bundleTSAvoidTDZ({ esbuild }) { var { outputFiles } = await esbuild.build({ stdin: { contents: ` class Foo { // The above line will be transformed into "var". However, the // symbol "Foo" must still be defined before the class body ends. static foo = new Foo } if (!(Foo.foo instanceof Foo)) throw 'fail' `, loader: 'ts', }, bundle: true, write: false, }) assert.strictEqual(outputFiles.length, 1) new Function(outputFiles[0].text)() }, async bundleTSDecoratorAvoidTDZ({ esbuild }) { var { outputFiles } = await esbuild.build({ stdin: { contents: ` class Bar {} var oldFoo function swap(target) { oldFoo = target return Bar } @swap class Foo { bar() { return new Foo } static foo = new Foo } if (!(oldFoo.foo instanceof oldFoo)) throw 'fail: foo' if (!(oldFoo.foo.bar() instanceof Bar)) throw 'fail: bar' `, loader: 'ts', }, bundle: true, write: false, }) assert.strictEqual(outputFiles.length, 1) new Function(outputFiles[0].text)() }, async automaticEntryPointOutputPathsWithDot({ esbuild, testDir }) { const input = path.join(testDir, 'in.file.ts') const css = path.join(testDir, 'file.css') await writeFileAsync(input, `import './file.css'; console.log('test')`) await writeFileAsync(css, `body { color: red }`) var { outputFiles } = await esbuild.build({ entryPoints: [input], outdir: testDir, bundle: true, write: false, }) assert.strictEqual(outputFiles.length, 2) assert.strictEqual(outputFiles[0].path, path.join(testDir, 'in.file.js')) assert.strictEqual(outputFiles[1].path, path.join(testDir, 'in.file.css')) }, async customEntryPointOutputPathsWithDot({ esbuild, testDir }) { const input = path.join(testDir, 'in.file.ts') const css = path.join(testDir, 'file.css') await writeFileAsync(input, `import './file.css'; console.log('test')`) await writeFileAsync(css, `body { color: red }`) var { outputFiles } = await esbuild.build({ entryPoints: { 'out.test': input, }, outdir: testDir, bundle: true, write: false, }) assert.strictEqual(outputFiles.length, 2) assert.strictEqual(outputFiles[0].path, path.join(testDir, 'out.test.js')) assert.strictEqual(outputFiles[1].path, path.join(testDir, 'out.test.css')) }, async customEntryPointOutputPathsRel({ esbuild, testDir }) { const input1 = path.join(testDir, 'in1.js') const input2 = path.join(testDir, 'in2.js') const output1 = 'out/1.cjs' const output2 = 'out/2.mjs' await writeFileAsync(input1, `console.log('in1')`) await writeFileAsync(input2, `console.log('in2')`) var { outputFiles } = await esbuild.build({ entryPoints: { [output1]: input1, [output2]: input2, }, entryNames: 'entry/[dir]/[hash]-[name]', outdir: testDir, write: false, }) assert.strictEqual(outputFiles.length, 2) assert.strictEqual(outputFiles[0].path, path.join(testDir, 'entry', 'out', 'LDM7WUFR-1.cjs.js')) assert.strictEqual(outputFiles[1].path, path.join(testDir, 'entry', 'out', '74SLWWSF-2.mjs.js')) }, async customEntryPointOutputPathsAbs({ esbuild, testDir }) { const input1 = path.join(testDir, 'in1.js') const input2 = path.join(testDir, 'in2.js') const output1 = path.join(testDir, 'out/1') const output2 = path.join(testDir, 'out/2') await writeFileAsync(input1, `console.log('in1')`) await writeFileAsync(input2, `console.log('in2')`) var { outputFiles } = await esbuild.build({ entryPoints: { [output1]: input1, [output2]: input2, }, entryNames: 'entry/[dir]/[hash]-[name]', outdir: testDir, write: false, }) assert.strictEqual(outputFiles.length, 2) assert.strictEqual(outputFiles[0].path, path.join(testDir, 'entry', 'out', 'MQUIWIER-1.js')) assert.strictEqual(outputFiles[1].path, path.join(testDir, 'entry', 'out', 'ATGPBOSJ-2.js')) }, } function fetch(host, port, path) { return new Promise((resolve, reject) => { http.get({ host, port, path }, res => { const chunks = [] res.on('data', chunk => chunks.push(chunk)) res.on('end', () => { const content = Buffer.concat(chunks) if (res.statusCode < 200 || res.statusCode > 299) reject(new Error(`${res.statusCode} when fetching ${path}: ${content}`)) else resolve(content) }) }).on('error', reject) }) } let watchTests = { async watchEditSession({ esbuild, testDir }) { const srcDir = path.join(testDir, 'src') const outfile = path.join(testDir, 'out.js') const input = path.join(srcDir, 'in.js') await mkdirAsync(srcDir, { recursive: true }) await writeFileAsync(input, `throw 1`) let onRebuild = () => { } const result = await esbuild.build({ entryPoints: [input], outfile, format: 'esm', logLevel: 'silent', watch: { onRebuild: (...args) => onRebuild(args), }, }) const rebuildUntil = (mutator, condition) => { let timeout return new Promise((resolve, reject) => { timeout = setTimeout(() => reject(new Error('Timeout after 30 seconds')), 30 * 1000) onRebuild = args => { try { if (condition(...args)) clearTimeout(timeout), resolve(args) } catch (e) { clearTimeout(timeout), reject(e) } } mutator() }) } try { assert.strictEqual(result.outputFiles, void 0) assert.strictEqual(typeof result.stop, 'function') assert.strictEqual(await readFileAsync(outfile, 'utf8'), 'throw 1;\n') // First rebuild: edit { const [error2, result2] = await rebuildUntil( () => writeFileAtomic(input, `throw 2`), () => fs.readFileSync(outfile, 'utf8') === 'throw 2;\n', ) assert.strictEqual(error2, null) assert.strictEqual(result2.outputFiles, void 0) assert.strictEqual(result2.stop, result.stop) } // Second rebuild: edit { const [error2, result2] = await rebuildUntil( () => writeFileAtomic(input, `throw 3`), () => fs.readFileSync(outfile, 'utf8') === 'throw 3;\n', ) assert.strictEqual(error2, null) assert.strictEqual(result2.outputFiles, void 0) assert.strictEqual(result2.stop, result.stop) } // Third rebuild: syntax error { const [error2, result2] = await rebuildUntil( () => writeFileAtomic(input, `throw 1 2`), err => err, ) assert.notStrictEqual(error2, null) assert(error2.message.startsWith('Build failed with 1 error')) assert.strictEqual(error2.errors.length, 1) assert.strictEqual(error2.errors[0].text, 'Expected ";" but found "2"') assert.strictEqual(result2, null) assert.strictEqual(await readFileAsync(outfile, 'utf8'), 'throw 3;\n') } // Fourth rebuild: edit { const [error2, result2] = await rebuildUntil( () => writeFileAtomic(input, `throw 4`), () => fs.readFileSync(outfile, 'utf8') === 'throw 4;\n', ) assert.strictEqual(error2, null) assert.strictEqual(result2.outputFiles, void 0) assert.strictEqual(result2.stop, result.stop) } // Fifth rebuild: delete { const [error2, result2] = await rebuildUntil( () => fs.promises.unlink(input), err => err, ) assert.notStrictEqual(error2, null) assert(error2.message.startsWith('Build failed with 1 error')) assert.strictEqual(error2.errors.length, 1) assert.strictEqual(result2, null) assert.strictEqual(await readFileAsync(outfile, 'utf8'), 'throw 4;\n') } // Sixth rebuild: restore { const [error2, result2] = await rebuildUntil( () => writeFileAtomic(input, `throw 5`), () => fs.readFileSync(outfile, 'utf8') === 'throw 5;\n', ) assert.strictEqual(error2, null) assert.strictEqual(result2.outputFiles, void 0) assert.strictEqual(result2.stop, result.stop) assert.strictEqual(await readFileAsync(outfile, 'utf8'), 'throw 5;\n') } } finally { result.stop() } }, async watchWriteFalse({ esbuild, testDir }) { const srcDir = path.join(testDir, 'src') const outdir = path.join(testDir, 'out') const input = path.join(srcDir, 'in.js') const output = path.join(outdir, 'in.js') await mkdirAsync(srcDir, { recursive: true }) await writeFileAsync(input, `throw 1`) let onRebuild = () => { } const result = await esbuild.build({ entryPoints: [input], outdir, format: 'esm', logLevel: 'silent', write: false, watch: { onRebuild: (...args) => onRebuild(args), }, }) const rebuildUntil = (mutator, condition) => { let timeout return new Promise((resolve, reject) => { timeout = setTimeout(() => reject(new Error('Timeout after 30 seconds')), 30 * 1000) onRebuild = args => { try { if (condition(...args)) clearTimeout(timeout), resolve(args) } catch (e) { clearTimeout(timeout), reject(e) } } mutator() }) } try { assert.strictEqual(result.outputFiles.length, 1) assert.strictEqual(result.outputFiles[0].text, 'throw 1;\n') assert.strictEqual(typeof result.stop, 'function') assert.strictEqual(fs.existsSync(output), false) // First rebuild: edit { const [error2, result2] = await rebuildUntil( () => writeFileAtomic(input, `throw 2`), (err, res) => res.outputFiles[0].text === 'throw 2;\n', ) assert.strictEqual(error2, null) assert.strictEqual(result2.stop, result.stop) assert.strictEqual(fs.existsSync(output), false) } // Second rebuild: edit { const [error2, result2] = await rebuildUntil( () => writeFileAtomic(input, `throw 3`), (err, res) => res.outputFiles[0].text === 'throw 3;\n', ) assert.strictEqual(error2, null) assert.strictEqual(result2.stop, result.stop) assert.strictEqual(fs.existsSync(output), false) } } finally { result.stop() } }, } let serveTests = { async serveBasic({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') await writeFileAsync(input, `console.log(123)`) let onRequest; let singleRequestPromise = new Promise(resolve => { onRequest = resolve; }); const result = await esbuild.serve({ host: '127.0.0.1', onRequest, }, { entryPoints: [input], format: 'esm', }) assert.strictEqual(result.host, '127.0.0.1'); assert.strictEqual(typeof result.port, 'number'); const buffer = await fetch(result.host, result.port, '/in.js') assert.strictEqual(buffer.toString(), `console.log(123);\n`); let singleRequest = await singleRequestPromise; assert.strictEqual(singleRequest.method, 'GET'); assert.strictEqual(singleRequest.path, '/in.js'); assert.strictEqual(singleRequest.status, 200); assert.strictEqual(typeof singleRequest.remoteAddress, 'string'); assert.strictEqual(typeof singleRequest.timeInMS, 'number'); result.stop(); await result.wait; }, async serveOutfile({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') await writeFileAsync(input, `console.log(123)`) let onRequest; let singleRequestPromise = new Promise(resolve => { onRequest = resolve; }); const result = await esbuild.serve({ host: '127.0.0.1', onRequest, }, { entryPoints: [input], format: 'esm', outfile: 'out.js', }) assert.strictEqual(result.host, '127.0.0.1'); assert.strictEqual(typeof result.port, 'number'); const buffer = await fetch(result.host, result.port, '/out.js') assert.strictEqual(buffer.toString(), `console.log(123);\n`); try { await fetch(result.host, result.port, '/in.js') throw new Error('Expected a 404 error for "/in.js"') } catch (err) { if (err.message !== '404 when fetching /in.js: 404 - Not Found') throw err } let singleRequest = await singleRequestPromise; assert.strictEqual(singleRequest.method, 'GET'); assert.strictEqual(singleRequest.path, '/out.js'); assert.strictEqual(singleRequest.status, 200); assert.strictEqual(typeof singleRequest.remoteAddress, 'string'); assert.strictEqual(typeof singleRequest.timeInMS, 'number'); result.stop(); await result.wait; }, async serveWithFallbackDir({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const wwwDir = path.join(testDir, 'www') const index = path.join(wwwDir, 'index.html') await mkdirAsync(wwwDir, { recursive: true }) await writeFileAsync(input, `console.log(123)`) await writeFileAsync(index, ``) let onRequest; let nextRequestPromise; (function generateNewPromise() { nextRequestPromise = new Promise(resolve => { onRequest = args => { generateNewPromise(); resolve(args); }; }); })(); const result = await esbuild.serve({ host: '127.0.0.1', onRequest: args => onRequest(args), servedir: wwwDir, }, { entryPoints: [input], format: 'esm', }) assert.strictEqual(result.host, '127.0.0.1'); assert.strictEqual(typeof result.port, 'number'); let promise, buffer, req; promise = nextRequestPromise; buffer = await fetch(result.host, result.port, '/in.js') assert.strictEqual(buffer.toString(), `console.log(123);\n`); req = await promise; assert.strictEqual(req.method, 'GET'); assert.strictEqual(req.path, '/in.js'); assert.strictEqual(req.status, 200); assert.strictEqual(typeof req.remoteAddress, 'string'); assert.strictEqual(typeof req.timeInMS, 'number'); promise = nextRequestPromise; buffer = await fetch(result.host, result.port, '/') assert.strictEqual(buffer.toString(), ``); req = await promise; assert.strictEqual(req.method, 'GET'); assert.strictEqual(req.path, '/'); assert.strictEqual(req.status, 200); assert.strictEqual(typeof req.remoteAddress, 'string'); assert.strictEqual(typeof req.timeInMS, 'number'); result.stop(); await result.wait; }, async serveWithFallbackDirAndSiblingOutputDir({ esbuild, testDir }) { try { const result = await esbuild.serve({ servedir: 'www', }, { entryPoints: [path.join(testDir, 'in.js')], outdir: 'out', }) result.stop() throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e + '', `Error: Output directory "out" must be contained in serve directory "www"`) } }, async serveWithFallbackDirAndParentOutputDir({ esbuild, testDir }) { try { const result = await esbuild.serve({ servedir: path.join(testDir, 'www'), }, { entryPoints: [path.join(testDir, 'in.js')], outdir: testDir, absWorkingDir: testDir, }) result.stop() throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e + '', `Error: Output directory "." must be contained in serve directory "www"`) } }, async serveWithFallbackDirAndOutputDir({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const outputDir = path.join(testDir, 'www/out') const wwwDir = path.join(testDir, 'www') const index = path.join(wwwDir, 'index.html') await mkdirAsync(wwwDir, { recursive: true }) await writeFileAsync(input, `console.log(123)`) await writeFileAsync(index, ``) let onRequest; let nextRequestPromise; (function generateNewPromise() { nextRequestPromise = new Promise(resolve => { onRequest = args => { generateNewPromise(); resolve(args); }; }); })(); const result = await esbuild.serve({ host: '127.0.0.1', onRequest: args => onRequest(args), servedir: wwwDir, }, { entryPoints: [input], format: 'esm', outdir: outputDir, }) assert.strictEqual(result.host, '127.0.0.1'); assert.strictEqual(typeof result.port, 'number'); let promise, buffer, req; promise = nextRequestPromise; buffer = await fetch(result.host, result.port, '/out/in.js') assert.strictEqual(buffer.toString(), `console.log(123);\n`); req = await promise; assert.strictEqual(req.method, 'GET'); assert.strictEqual(req.path, '/out/in.js'); assert.strictEqual(req.status, 200); assert.strictEqual(typeof req.remoteAddress, 'string'); assert.strictEqual(typeof req.timeInMS, 'number'); promise = nextRequestPromise; buffer = await fetch(result.host, result.port, '/') assert.strictEqual(buffer.toString(), ``); req = await promise; assert.strictEqual(req.method, 'GET'); assert.strictEqual(req.path, '/'); assert.strictEqual(req.status, 200); assert.strictEqual(typeof req.remoteAddress, 'string'); assert.strictEqual(typeof req.timeInMS, 'number'); result.stop(); await result.wait; }, async serveWithFallbackDirNoEntryPoints({ esbuild, testDir }) { const index = path.join(testDir, 'index.html') await writeFileAsync(index, ``) let onRequest; let nextRequestPromise; (function generateNewPromise() { nextRequestPromise = new Promise(resolve => { onRequest = args => { generateNewPromise(); resolve(args); }; }); })(); const result = await esbuild.serve({ host: '127.0.0.1', onRequest: args => onRequest(args), servedir: testDir, }, { }) assert.strictEqual(result.host, '127.0.0.1'); assert.strictEqual(typeof result.port, 'number'); let promise, buffer, req; promise = nextRequestPromise; buffer = await fetch(result.host, result.port, '/') assert.strictEqual(buffer.toString(), ``); req = await promise; assert.strictEqual(req.method, 'GET'); assert.strictEqual(req.path, '/'); assert.strictEqual(req.status, 200); assert.strictEqual(typeof req.remoteAddress, 'string'); assert.strictEqual(typeof req.timeInMS, 'number'); // Check that removing the file removes it from the directory listing (i.e. the // "fs.FS" object in Go does not cache the result of calling "ReadDirectory") await fs.promises.unlink(index) promise = nextRequestPromise; buffer = await fetch(result.host, result.port, '/') assert.strictEqual(buffer.toString(), `Directory: /

Directory: /

    `); req = await promise; assert.strictEqual(req.method, 'GET'); assert.strictEqual(req.path, '/'); assert.strictEqual(req.status, 200); assert.strictEqual(typeof req.remoteAddress, 'string'); assert.strictEqual(typeof req.timeInMS, 'number'); result.stop(); await result.wait; }, } async function futureSyntax(esbuild, js, targetBelow, targetAbove) { failure: { try { await esbuild.transform(js, { target: targetBelow }) } catch { break failure } throw new Error(`Expected failure for ${targetBelow}: ${js}`) } try { await esbuild.transform(js, { target: targetAbove }) } catch (e) { throw new Error(`Expected success for ${targetAbove}: ${js}\n${e}`) } } let transformTests = { async transformWithNonString({ esbuild }) { try { await esbuild.transform(Buffer.from(`1+2`)) throw new Error('Expected an error to be thrown'); } catch (e) { assert.strictEqual(e.errors[0].text, 'The input to "transform" must be a string') } }, async version({ esbuild }) { const version = fs.readFileSync(path.join(repoDir, 'version.txt'), 'utf8').trim() assert.strictEqual(esbuild.version, version); }, async ignoreUndefinedOptions({ esbuild }) { // This should not throw await esbuild.transform(``, { jsxFactory: void 0 }) }, async throwOnBadOptions({ esbuild }) { // This should throw try { await esbuild.transform(``, { jsxFactory: ['React', 'createElement'] }) throw new Error('Expected transform failure'); } catch (e) { if (!e.errors || !e.errors[0] || e.errors[0].text !== '"jsxFactory" must be a string') { throw e; } } }, async avoidTDZ({ esbuild }) { var { code } = await esbuild.transform(` class Foo { // The above line will be transformed into "var". However, the // symbol "Foo" must still be defined before the class body ends. static foo = new Foo } if (!(Foo.foo instanceof Foo)) throw 'fail' `) new Function(code)() }, async tsAvoidTDZ({ esbuild }) { var { code } = await esbuild.transform(` class Foo { // The above line will be transformed into "var". However, the // symbol "Foo" must still be defined before the class body ends. static foo = new Foo } if (!(Foo.foo instanceof Foo)) throw 'fail' `, { loader: 'ts', }) new Function(code)() }, // Note: The TypeScript compiler's transformer doesn't handle this case. // Using "this" like this is a syntax error instead. However, we can handle // it without too much trouble so we handle it here. This is defensive in // case the TypeScript compiler team fixes this in the future. async tsAvoidTDZThis({ esbuild }) { var { code } = await esbuild.transform(` class Foo { static foo = 123 static bar = this.foo // "this" must be rewritten when the property is relocated } if (Foo.bar !== 123) throw 'fail' `, { loader: 'ts', tsconfigRaw: { compilerOptions: { useDefineForClassFields: false, }, }, }) new Function(code)() }, async tsDecoratorAvoidTDZ({ esbuild }) { var { code } = await esbuild.transform(` class Bar {} var oldFoo function swap(target) { oldFoo = target return Bar } @swap class Foo { bar() { return new Foo } static foo = new Foo } if (!(oldFoo.foo instanceof oldFoo)) throw 'fail: foo' if (!(oldFoo.foo.bar() instanceof Bar)) throw 'fail: bar' `, { loader: 'ts', }) new Function(code)() }, async jsBannerTransform({ esbuild }) { var { code } = await esbuild.transform(` if (!bannerDefined) throw 'fail' `, { banner: 'const bannerDefined = true', }) new Function(code)() }, async jsFooterTransform({ esbuild }) { var { code } = await esbuild.transform(` footer() `, { footer: 'function footer() {}', }) new Function(code)() new Function(code)() }, async jsBannerFooterTransform({ esbuild }) { var { code } = await esbuild.transform(` return { banner: bannerDefined, footer }; `, { banner: 'const bannerDefined = true', footer: 'function footer() {}', }) const result = new Function(code)() if (!result.banner) throw 'fail' result.footer() }, async cssBannerFooterTransform({ esbuild }) { var { code } = await esbuild.transform(` div { color: red } `, { loader: 'css', banner: '/* banner */', footer: '/* footer */', }) assert.strictEqual(code, `/* banner */\ndiv {\n color: red;\n}\n/* footer */\n`) }, async transformDirectEval({ esbuild }) { var { code } = await esbuild.transform(` export let abc = 123 eval('console.log(abc)') `, { minify: true, }) assert.strictEqual(code, `export let abc=123;eval("console.log(abc)");\n`) }, async tsconfigRawRemoveUnusedImportsDefault({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: {}, }, loader: 'ts', }) assert.strictEqual(code, ``) }, async tsconfigRawRemoveUnusedImports({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: { importsNotUsedAsValues: 'remove', }, }, loader: 'ts', }) assert.strictEqual(code, ``) }, async tsconfigRawPreserveUnusedImports({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: { importsNotUsedAsValues: 'preserve', }, }, loader: 'ts', }) assert.strictEqual(code, `import {T} from "path";\n`) }, async tsconfigRawPreserveUnusedImportsMinifyIdentifiers({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: { importsNotUsedAsValues: 'preserve', }, }, loader: 'ts', minifyIdentifiers: true, }) assert.strictEqual(code, `import "path";\n`) }, async tsconfigRawPreserveUnusedImportsJS({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: { importsNotUsedAsValues: 'preserve', }, }, loader: 'js', }) assert.strictEqual(code, `import {T} from "path";\n`) }, async tsconfigRawCommentsInJSON({ esbuild }) { // Can use a string, which allows weird TypeScript pseudo-JSON with comments and trailing commas const { code: code5 } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: `{ "compilerOptions": { "importsNotUsedAsValues": "preserve", // there is a trailing comment here }, }`, loader: 'ts', }) assert.strictEqual(code5, `import {T} from "path";\n`) }, async tsconfigRawImportsNotUsedAsValues({ esbuild }) { const { code: code1 } = await esbuild.transform(`class Foo { foo }`, { tsconfigRaw: { compilerOptions: { useDefineForClassFields: false, }, }, loader: 'ts', }) assert.strictEqual(code1, `class Foo {\n}\n`) const { code: code2 } = await esbuild.transform(`class Foo { foo }`, { tsconfigRaw: { compilerOptions: { useDefineForClassFields: true, }, }, loader: 'ts', }) assert.strictEqual(code2, `class Foo {\n foo;\n}\n`) }, async tsconfigRawJSX({ esbuild }) { const { code: code1 } = await esbuild.transform(`<>
    `, { tsconfigRaw: { compilerOptions: { }, }, loader: 'jsx', }) assert.strictEqual(code1, `/* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", null));\n`) const { code: code2 } = await esbuild.transform(`<>
    `, { tsconfigRaw: { compilerOptions: { jsxFactory: 'factory', jsxFragmentFactory: 'fragment', }, }, loader: 'jsx', }) assert.strictEqual(code2, `/* @__PURE__ */ factory(fragment, null, /* @__PURE__ */ factory("div", null));\n`) }, async treeShakingDefault({ esbuild }) { const { code } = await esbuild.transform(`/* @__PURE__ */ fn();
    `, { loader: 'jsx', minifySyntax: true, }) assert.strictEqual(code, ``) }, async treeShakingTrue({ esbuild }) { const { code } = await esbuild.transform(`/* @__PURE__ */ fn();
    `, { loader: 'jsx', minifySyntax: true, treeShaking: true, }) assert.strictEqual(code, ``) }, async treeShakingIgnoreAnnotations({ esbuild }) { const { code } = await esbuild.transform(`/* @__PURE__ */ fn();
    `, { loader: 'jsx', minifySyntax: true, treeShaking: 'ignore-annotations', }) assert.strictEqual(code, `fn(), React.createElement("div", null);\n`) }, async jsCharsetDefault({ esbuild }) { const { code } = await esbuild.transform(`let π = 'π'`, {}) assert.strictEqual(code, `let \\u03C0 = "\\u03C0";\n`) }, async jsCharsetASCII({ esbuild }) { const { code } = await esbuild.transform(`let π = 'π'`, { charset: 'ascii' }) assert.strictEqual(code, `let \\u03C0 = "\\u03C0";\n`) }, async jsCharsetUTF8({ esbuild }) { const { code } = await esbuild.transform(`let π = 'π'`, { charset: 'utf8' }) assert.strictEqual(code, `let π = "π";\n`) }, async cssCharsetDefault({ esbuild }) { const { code } = await esbuild.transform(`.π:after { content: 'π' }`, { loader: 'css' }) assert.strictEqual(code, `.\\3c0:after {\n content: "\\3c0";\n}\n`) }, async cssCharsetASCII({ esbuild }) { const { code } = await esbuild.transform(`.π:after { content: 'π' }`, { loader: 'css', charset: 'ascii' }) assert.strictEqual(code, `.\\3c0:after {\n content: "\\3c0";\n}\n`) }, async cssCharsetUTF8({ esbuild }) { const { code } = await esbuild.transform(`.π:after { content: 'π' }`, { loader: 'css', charset: 'utf8' }) assert.strictEqual(code, `.π:after {\n content: "π";\n}\n`) }, async cssMinify({ esbuild }) { const { code } = await esbuild.transform(`div { color: #abcd }`, { loader: 'css', minify: true }) assert.strictEqual(code, `div{color:#abcd}\n`) }, // Using an "es" target shouldn't affect CSS async cssMinifyTargetES6({ esbuild }) { const { code } = await esbuild.transform(`div { color: #abcd }`, { loader: 'css', minify: true, target: 'es6' }) assert.strictEqual(code, `div{color:#abcd}\n`) }, // Using a "node" target shouldn't affect CSS async cssMinifyTargetNode({ esbuild }) { const { code } = await esbuild.transform(`div { color: #abcd }`, { loader: 'css', minify: true, target: 'node8' }) assert.strictEqual(code, `div{color:#abcd}\n`) }, // Using an older browser target should affect CSS async cssMinifyTargetChrome8({ esbuild }) { const { code } = await esbuild.transform(`div { color: #abcd }`, { loader: 'css', minify: true, target: 'chrome8' }) assert.strictEqual(code, `div{color:rgba(170,187,204,.867)}\n`) }, // Using a newer browser target shouldn't affect CSS async cssMinifyTargetChrome80({ esbuild }) { const { code } = await esbuild.transform(`div { color: #abcd }`, { loader: 'css', minify: true, target: 'chrome80' }) assert.strictEqual(code, `div{color:#abcd}\n`) }, async cjs_require({ esbuild }) { const { code } = await esbuild.transform(`const {foo} = require('path')`, {}) assert.strictEqual(code, `const {foo} = require("path");\n`) }, async cjs_exports({ esbuild }) { const { code } = await esbuild.transform(`exports.foo = 123`, {}) assert.strictEqual(code, `exports.foo = 123;\n`) }, async es6_import({ esbuild }) { const { code } = await esbuild.transform(`import {foo} from 'path'`, {}) assert.strictEqual(code, `import {foo} from "path";\n`) }, async es6_export({ esbuild }) { const { code } = await esbuild.transform(`export const foo = 123`, {}) assert.strictEqual(code, `export const foo = 123;\n`) }, async es6_import_to_iife({ esbuild }) { const { code } = await esbuild.transform(`import {exists} from "fs"; if (!exists) throw 'fail'`, { format: 'iife' }) new Function('require', code)(require) }, async es6_import_star_to_iife({ esbuild }) { const { code } = await esbuild.transform(`import * as fs from "fs"; if (!fs.exists) throw 'fail'`, { format: 'iife' }) new Function('require', code)(require) }, async es6_export_to_iife({ esbuild }) { const { code } = await esbuild.transform(`export {exists} from "fs"`, { format: 'iife', globalName: 'out' }) const out = new Function('require', code + ';return out')(require) if (out.exists !== fs.exists) throw 'fail' }, async es6_export_star_to_iife({ esbuild }) { const { code } = await esbuild.transform(`export * from "fs"`, { format: 'iife', globalName: 'out' }) const out = new Function('require', code + ';return out')(require) if (out.exists !== fs.exists) throw 'fail' }, async es6_export_star_as_to_iife({ esbuild }) { const { code } = await esbuild.transform(`export * as fs from "fs"`, { format: 'iife', globalName: 'out' }) const out = new Function('require', code + ';return out')(require) if (out.fs.exists !== fs.exists) throw 'fail' }, async es6_import_to_cjs({ esbuild }) { const { code } = await esbuild.transform(`import {exists} from "fs"; if (!exists) throw 'fail'`, { format: 'cjs' }) new Function('require', code)(require) }, async es6_import_star_to_cjs({ esbuild }) { const { code } = await esbuild.transform(`import * as fs from "fs"; if (!fs.exists) throw 'fail'`, { format: 'cjs' }) new Function('require', code)(require) }, async es6_export_to_cjs({ esbuild }) { const { code } = await esbuild.transform(`export {exists} from "fs"`, { format: 'cjs' }) const exports = {} new Function('require', 'exports', code)(require, exports) if (exports.exists !== fs.exists) throw 'fail' }, async es6_export_star_to_cjs({ esbuild }) { const { code } = await esbuild.transform(`export * from "fs"`, { format: 'cjs' }) const exports = {} new Function('require', 'exports', code)(require, exports) if (exports.exists !== fs.exists) throw 'fail' }, async es6_export_star_as_to_cjs({ esbuild }) { const { code } = await esbuild.transform(`export * as fs from "fs"`, { format: 'cjs' }) const exports = {} new Function('require', 'exports', code)(require, exports) if (exports.fs.exists !== fs.exists) throw 'fail' }, async es6_import_to_esm({ esbuild }) { const { code } = await esbuild.transform(`import {exists} from "fs"; if (!exists) throw 'fail'`, { format: 'esm' }) assert.strictEqual(code, `import {exists} from "fs";\nif (!exists)\n throw "fail";\n`) }, async es6_import_star_to_esm({ esbuild }) { const { code } = await esbuild.transform(`import * as fs from "fs"; if (!fs.exists) throw 'fail'`, { format: 'esm' }) assert.strictEqual(code, `import * as fs from "fs";\nif (!fs.exists)\n throw "fail";\n`) }, async es6_export_to_esm({ esbuild }) { const { code } = await esbuild.transform(`export {exists} from "fs"`, { format: 'esm' }) assert.strictEqual(code, `import {exists} from "fs";\nexport {\n exists\n};\n`) }, async es6_export_star_to_esm({ esbuild }) { const { code } = await esbuild.transform(`export * from "fs"`, { format: 'esm' }) assert.strictEqual(code, `export * from "fs";\n`) }, async es6_export_star_as_to_esm({ esbuild }) { const { code } = await esbuild.transform(`export * as fs from "fs"`, { format: 'esm' }) assert.strictEqual(code, `import * as fs from "fs";\nexport {\n fs\n};\n`) }, async iifeGlobalName({ esbuild }) { const { code } = await esbuild.transform(`export default 123`, { format: 'iife', globalName: 'testName' }) const globals = {} vm.createContext(globals) vm.runInContext(code, globals) assert.strictEqual(globals.testName.default, 123) }, async iifeGlobalNameCompound({ esbuild }) { const { code } = await esbuild.transform(`export default 123`, { format: 'iife', globalName: 'test.name' }) const globals = {} vm.createContext(globals) vm.runInContext(code, globals) assert.strictEqual(globals.test.name.default, 123) }, async iifeGlobalNameString({ esbuild }) { const { code } = await esbuild.transform(`export default 123`, { format: 'iife', globalName: 'test["some text"]' }) const globals = {} vm.createContext(globals) vm.runInContext(code, globals) assert.strictEqual(globals.test['some text'].default, 123) }, async iifeGlobalNameUnicodeEscape({ esbuild }) { const { code } = await esbuild.transform(`export default 123`, { format: 'iife', globalName: 'π["π 𐀀"].𐀀["𐀀 π"]' }) const globals = {} vm.createContext(globals) vm.runInContext(code, globals) assert.strictEqual(globals.π["π 𐀀"].𐀀["𐀀 π"].default, 123) assert.strictEqual(code.slice(0, code.indexOf('(() => {\n')), `var \\u03C0 = \\u03C0 || {}; \\u03C0["\\u03C0 \\uD800\\uDC00"] = \\u03C0["\\u03C0 \\uD800\\uDC00"] || {}; \\u03C0["\\u03C0 \\uD800\\uDC00"].\\u{10000} = \\u03C0["\\u03C0 \\uD800\\uDC00"].\\u{10000} || {}; \\u03C0["\\u03C0 \\uD800\\uDC00"].\\u{10000}["\\uD800\\uDC00 \\u03C0"] = `) }, async iifeGlobalNameUnicodeNoEscape({ esbuild }) { const { code } = await esbuild.transform(`export default 123`, { format: 'iife', globalName: 'π["π 𐀀"].𐀀["𐀀 π"]', charset: 'utf8' }) const globals = {} vm.createContext(globals) vm.runInContext(code, globals) assert.strictEqual(globals.π["π 𐀀"].𐀀["𐀀 π"].default, 123) assert.strictEqual(code.slice(0, code.indexOf('(() => {\n')), `var π = π || {}; π["π 𐀀"] = π["π 𐀀"] || {}; π["π 𐀀"].𐀀 = π["π 𐀀"].𐀀 || {}; π["π 𐀀"].𐀀["𐀀 π"] = `) }, async jsx({ esbuild }) { const { code } = await esbuild.transform(`console.log(
    )`, { loader: 'jsx' }) assert.strictEqual(code, `console.log(/* @__PURE__ */ React.createElement("div", null));\n`) }, async ts({ esbuild }) { const { code } = await esbuild.transform(`enum Foo { FOO }`, { loader: 'ts' }) assert.strictEqual(code, `var Foo;\n(function(Foo2) {\n Foo2[Foo2["FOO"] = 0] = "FOO";\n})(Foo || (Foo = {}));\n`) }, async tsx({ esbuild }) { const { code } = await esbuild.transform(`console.log(/>)`, { loader: 'tsx' }) assert.strictEqual(code, `console.log(/* @__PURE__ */ React.createElement(Foo, null));\n`) }, async minify({ esbuild }) { const { code } = await esbuild.transform(`console.log("a" + "b" + c)`, { minify: true }) assert.strictEqual(code, `console.log("ab"+c);\n`) }, async define({ esbuild }) { const define = { 'process.env.NODE_ENV': '"production"' } const { code } = await esbuild.transform(`console.log(process.env.NODE_ENV)`, { define }) assert.strictEqual(code, `console.log("production");\n`) }, async defineArray({ esbuild }) { const define = { 'process.env.NODE_ENV': '[1,2,3]', 'something.else': '[2,3,4]' } const { code } = await esbuild.transform(`console.log(process.env.NODE_ENV)`, { define }) assert.strictEqual(code, `var define_process_env_NODE_ENV_default = [1, 2, 3];\nconsole.log(define_process_env_NODE_ENV_default);\n`) }, async json({ esbuild }) { const { code } = await esbuild.transform(`{ "x": "y" }`, { loader: 'json' }) assert.strictEqual(code, `module.exports = {x: "y"};\n`) }, async jsonMinified({ esbuild }) { const { code } = await esbuild.transform(`{ "x": "y" }`, { loader: 'json', minify: true }) const module = {} new Function('module', code)(module) assert.deepStrictEqual(module.exports, { x: 'y' }) }, async jsonESM({ esbuild }) { const { code } = await esbuild.transform(`{ "x": "y" }`, { loader: 'json', format: 'esm' }) assert.strictEqual(code, `var x = "y";\nvar stdin_default = {x};\nexport {\n stdin_default as default,\n x\n};\n`) }, async jsonInvalidIdentifierStart({ esbuild }) { // This character is a valid "ID_Continue" but not a valid "ID_Start" so it must be quoted const { code } = await esbuild.transform(`{ "\\uD835\\uDFCE": "y" }`, { loader: 'json' }) assert.strictEqual(code, `module.exports = {"\\u{1D7CE}": "y"};\n`) }, async text({ esbuild }) { const { code } = await esbuild.transform(`This is some text`, { loader: 'text' }) assert.strictEqual(code, `module.exports = "This is some text";\n`) }, async textESM({ esbuild }) { const { code } = await esbuild.transform(`This is some text`, { loader: 'text', format: 'esm' }) assert.strictEqual(code, `var stdin_default = "This is some text";\nexport {\n stdin_default as default\n};\n`) }, async base64({ esbuild }) { const { code } = await esbuild.transform(`\x00\x01\x02`, { loader: 'base64' }) assert.strictEqual(code, `module.exports = "AAEC";\n`) }, async dataurl({ esbuild }) { const { code } = await esbuild.transform(`\x00\x01\x02`, { loader: 'dataurl' }) assert.strictEqual(code, `module.exports = "data:application/octet-stream;base64,AAEC";\n`) }, async sourceMapWithName({ esbuild }) { const { code, map } = await esbuild.transform(`let x`, { sourcemap: true, sourcefile: 'afile.js' }) assert.strictEqual(code, `let x;\n`) await assertSourceMap(map, 'afile.js') }, async sourceMapExternalWithName({ esbuild }) { const { code, map } = await esbuild.transform(`let x`, { sourcemap: 'external', sourcefile: 'afile.js' }) assert.strictEqual(code, `let x;\n`) await assertSourceMap(map, 'afile.js') }, async sourceMapInlineWithName({ esbuild }) { const { code, map } = await esbuild.transform(`let x`, { sourcemap: 'inline', sourcefile: 'afile.js' }) assert(code.startsWith(`let x;\n//# sourceMappingURL=`)) assert.strictEqual(map, '') const base64 = code.slice(code.indexOf('base64,') + 'base64,'.length) await assertSourceMap(Buffer.from(base64.trim(), 'base64').toString(), 'afile.js') }, async sourceMapBothWithName({ esbuild }) { const { code, map } = await esbuild.transform(`let x`, { sourcemap: 'both', sourcefile: 'afile.js' }) assert(code.startsWith(`let x;\n//# sourceMappingURL=`)) await assertSourceMap(map, 'afile.js') const base64 = code.slice(code.indexOf('base64,') + 'base64,'.length) await assertSourceMap(Buffer.from(base64.trim(), 'base64').toString(), 'afile.js') }, async sourceMapRoot({ esbuild }) { const { code, map } = await esbuild.transform(`let x`, { sourcemap: true, sourcefile: 'afile.js', sourceRoot: "https://example.com/" }) assert.strictEqual(code, `let x;\n`) assert.strictEqual(JSON.parse(map).sourceRoot, 'https://example.com/'); }, async numericLiteralPrinting({ esbuild }) { async function checkLiteral(text) { const { code } = await esbuild.transform(`return ${text}`, { minify: true }) assert.strictEqual(+text, new Function(code)()) } const promises = [] for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { promises.push(checkLiteral(`0.${'0'.repeat(i)}${'123456789'.slice(0, j)}`)) promises.push(checkLiteral(`1${'0'.repeat(i)}.${'123456789'.slice(0, j)}`)) promises.push(checkLiteral(`1${'123456789'.slice(0, j)}${'0'.repeat(i)}`)) } } await Promise.all(promises) }, async tryCatchScopeMerge({ esbuild }) { const code = ` var x = 1 if (x !== 1) throw 'fail' try { throw 2 } catch (x) { if (x !== 2) throw 'fail' { if (x !== 2) throw 'fail' var x = 3 if (x !== 3) throw 'fail' } if (x !== 3) throw 'fail' } if (x !== 1) throw 'fail' `; new Function(code)(); // Verify that the code itself is correct new Function((await esbuild.transform(code)).code)(); }, async nestedFunctionHoist({ esbuild }) { const code = ` if (x !== void 0) throw 'fail' { if (x !== void 0) throw 'fail' { x() function x() {} x() } x() } x() `; new Function(code)(); // Verify that the code itself is correct new Function((await esbuild.transform(code)).code)(); }, async nestedFunctionHoistBefore({ esbuild }) { const code = ` var x = 1 if (x !== 1) throw 'fail' { if (x !== 1) throw 'fail' { x() function x() {} x() } x() } x() `; new Function(code)(); // Verify that the code itself is correct new Function((await esbuild.transform(code)).code)(); }, async nestedFunctionHoistAfter({ esbuild }) { const code = ` if (x !== void 0) throw 'fail' { if (x !== void 0) throw 'fail' { x() function x() {} x() } x() } x() var x = 1 `; new Function(code)(); // Verify that the code itself is correct new Function((await esbuild.transform(code)).code)(); }, async nestedFunctionShadowBefore({ esbuild }) { const code = ` let x = 1 if (x !== 1) throw 'fail' { if (x !== 1) throw 'fail' { x() function x() {} x() } if (x !== 1) throw 'fail' } if (x !== 1) throw 'fail' `; new Function(code)(); // Verify that the code itself is correct new Function((await esbuild.transform(code)).code)(); }, async nestedFunctionShadowAfter({ esbuild }) { const code = ` try { x; throw 'fail' } catch (e) { if (!(e instanceof ReferenceError)) throw e } { try { x; throw 'fail' } catch (e) { if (!(e instanceof ReferenceError)) throw e } { x() function x() {} x() } try { x; throw 'fail' } catch (e) { if (!(e instanceof ReferenceError)) throw e } } try { x; throw 'fail' } catch (e) { if (!(e instanceof ReferenceError)) throw e } let x = 1 `; new Function(code)(); // Verify that the code itself is correct new Function((await esbuild.transform(code)).code)(); }, async sourceMapControlCharacterEscapes({ esbuild }) { let chars = '' for (let i = 0; i < 32; i++) chars += String.fromCharCode(i); const input = `return \`${chars}\``; const { code, map } = await esbuild.transform(input, { sourcemap: true, sourcefile: 'afile.code' }) const fn = new Function(code) assert.strictEqual(fn(), chars.replace('\r', '\n')) const json = JSON.parse(map) assert.strictEqual(json.version, 3) assert.strictEqual(json.sourcesContent.length, 1) assert.strictEqual(json.sourcesContent[0], input) }, async tsDecorators({ esbuild }) { const { code } = await esbuild.transform(` let observed = []; let on = key => (...args) => { observed.push({ key, args }); }; @on('class') class Foo { @on('field') field; @on('method') method() { } @on('staticField') static staticField; @on('staticMethod') static staticMethod() { } fn(@on('param') x) { } static staticFn(@on('staticParam') x) { } } // This is what the TypeScript compiler itself generates let expected = [ { key: 'field', args: [Foo.prototype, 'field', undefined] }, { key: 'method', args: [Foo.prototype, 'method', { value: Foo.prototype.method, writable: true, enumerable: false, configurable: true }] }, { key: 'param', args: [Foo.prototype, 'fn', 0] }, { key: 'staticField', args: [Foo, 'staticField', undefined] }, { key: 'staticMethod', args: [Foo, 'staticMethod', { value: Foo.staticMethod, writable: true, enumerable: false, configurable: true }] }, { key: 'staticParam', args: [Foo, 'staticFn', 0] }, { key: 'class', args: [Foo] } ]; return {observed, expected}; `, { loader: 'ts' }); const { observed, expected } = new Function(code)(); assert.deepStrictEqual(observed, expected); }, async pureCallPrint({ esbuild }) { const { code: code1 } = await esbuild.transform(`print(123, foo)`, { minifySyntax: true, pure: [] }) assert.strictEqual(code1, `print(123, foo);\n`) const { code: code2 } = await esbuild.transform(`print(123, foo)`, { minifySyntax: true, pure: ['print'] }) assert.strictEqual(code2, `foo;\n`) }, async pureCallConsoleLog({ esbuild }) { const { code: code1 } = await esbuild.transform(`console.log(123, foo)`, { minifySyntax: true, pure: [] }) assert.strictEqual(code1, `console.log(123, foo);\n`) const { code: code2 } = await esbuild.transform(`console.log(123, foo)`, { minifySyntax: true, pure: ['console.log'] }) assert.strictEqual(code2, `foo;\n`) }, async dynamicImportString({ esbuild }) { const { code: code1 } = await esbuild.transform(`import('foo')`, { target: 'chrome63' }) assert.strictEqual(code1, `import("foo");\n`) }, async dynamicImportStringES6({ esbuild }) { const fromPromiseResolve = text => text.slice(text.indexOf('Promise.resolve')) const { code: code2 } = await esbuild.transform(`import('foo')`, { target: 'chrome62' }) assert.strictEqual(fromPromiseResolve(code2), `Promise.resolve().then(() => __toModule(require("foo")));\n`) }, async dynamicImportStringES5({ esbuild }) { const fromPromiseResolve = text => text.slice(text.indexOf('Promise.resolve')) const { code: code3 } = await esbuild.transform(`import('foo')`, { target: 'chrome48' }) assert.strictEqual(fromPromiseResolve(code3), `Promise.resolve().then(function() {\n return __toModule(require("foo"));\n});\n`) }, async dynamicImportStringES5Minify({ esbuild }) { const fromPromiseResolve = text => text.slice(text.indexOf('Promise.resolve')) const { code: code4 } = await esbuild.transform(`import('foo')`, { target: 'chrome48', minifyWhitespace: true }) assert.strictEqual(fromPromiseResolve(code4), `Promise.resolve().then(function(){return __toModule(require("foo"))});\n`) }, async dynamicImportExpression({ esbuild }) { const { code: code1 } = await esbuild.transform(`import(foo)`, { target: 'chrome63' }) assert.strictEqual(code1, `import(foo);\n`) }, async dynamicImportExpressionES6({ esbuild }) { const fromPromiseResolve = text => text.slice(text.indexOf('Promise.resolve')) const { code: code2 } = await esbuild.transform(`import(foo)`, { target: 'chrome62' }) assert.strictEqual(fromPromiseResolve(code2), `Promise.resolve().then(() => __toModule(require(foo)));\n`) }, async dynamicImportExpressionES5({ esbuild }) { const fromPromiseResolve = text => text.slice(text.indexOf('Promise.resolve')) const { code: code3 } = await esbuild.transform(`import(foo)`, { target: 'chrome48' }) assert.strictEqual(fromPromiseResolve(code3), `Promise.resolve().then(function() {\n return __toModule(require(foo));\n});\n`) }, async dynamicImportExpressionES5Minify({ esbuild }) { const fromPromiseResolve = text => text.slice(text.indexOf('Promise.resolve')) const { code: code4 } = await esbuild.transform(`import(foo)`, { target: 'chrome48', minifyWhitespace: true }) assert.strictEqual(fromPromiseResolve(code4), `Promise.resolve().then(function(){return __toModule(require(foo))});\n`) }, async multipleEngineTargets({ esbuild }) { const check = async (target, expected) => assert.strictEqual((await esbuild.transform(`foo(a ?? b)`, { target })).code, expected) await Promise.all([ check('es2020', `foo(a ?? b);\n`), check('es2019', `foo(a != null ? a : b);\n`), check('chrome80', `foo(a ?? b);\n`), check('chrome79', `foo(a != null ? a : b);\n`), check(['es2020', 'chrome80'], `foo(a ?? b);\n`), check(['es2020', 'chrome79'], `foo(a != null ? a : b);\n`), check(['es2019', 'chrome80'], `foo(a != null ? a : b);\n`), ]) }, async multipleEngineTargetsNotSupported({ esbuild }) { try { await esbuild.transform(`0n`, { target: ['es5', 'chrome1', 'safari2', 'firefox3'] }) throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e.errors[0].text, 'Big integer literals are not available in the configured target environment ("chrome1", "es5", "firefox3", "safari2")') } }, // Future syntax forAwait: ({ esbuild }) => futureSyntax(esbuild, 'async function foo() { for await (let x of y) {} }', 'es2017', 'es2018'), bigInt: ({ esbuild }) => futureSyntax(esbuild, '123n', 'es2019', 'es2020'), bigIntKey: ({ esbuild }) => futureSyntax(esbuild, '({123n: 0})', 'es2019', 'es2020'), bigIntPattern: ({ esbuild }) => futureSyntax(esbuild, 'let {123n: x} = y', 'es2019', 'es2020'), nonIdArrayRest: ({ esbuild }) => futureSyntax(esbuild, 'let [...[x]] = y', 'es2015', 'es2016'), topLevelAwait: ({ esbuild }) => futureSyntax(esbuild, 'await foo', 'es2020', 'esnext'), topLevelForAwait: ({ esbuild }) => futureSyntax(esbuild, 'for await (foo of bar) ;', 'es2020', 'esnext'), // Future syntax: async generator functions asyncGenFnStmt: ({ esbuild }) => futureSyntax(esbuild, 'async function* foo() {}', 'es2017', 'es2018'), asyncGenFnExpr: ({ esbuild }) => futureSyntax(esbuild, '(async function*() {})', 'es2017', 'es2018'), asyncGenObjFn: ({ esbuild }) => futureSyntax(esbuild, '({ async* foo() {} })', 'es2017', 'es2018'), asyncGenClassStmtFn: ({ esbuild }) => futureSyntax(esbuild, 'class Foo { async* foo() {} }', 'es2017', 'es2018'), asyncGenClassExprFn: ({ esbuild }) => futureSyntax(esbuild, '(class { async* foo() {} })', 'es2017', 'es2018'), } function registerClassPrivateTests(target) { let contents = ` class Field { #foo = 123; bar = this.#foo } if (new Field().bar !== 123) throw 'fail: field' class Method { bar = this.#foo(); #foo() { return 123 } } if (new Method().bar !== 123) throw 'fail: method' class Accessor { bar = this.#foo; get #foo() { return 123 } } if (new Accessor().bar !== 123) throw 'fail: accessor' class StaticField { static #foo = 123; static bar = StaticField.#foo } if (StaticField.bar !== 123) throw 'fail: static field' class StaticMethod { static bar = StaticMethod.#foo(); static #foo() { return 123 } } if (StaticMethod.bar !== 123) throw 'fail: static method' class StaticAccessor { static bar = StaticAccessor.#foo; static get #foo() { return 123 } } if (StaticAccessor.bar !== 123) throw 'fail: static accessor' class StaticFieldThis { static #foo = 123; static bar = this.#foo } if (StaticFieldThis.bar !== 123) throw 'fail: static field' class StaticMethodThis { static bar = this.#foo(); static #foo() { return 123 } } if (StaticMethodThis.bar !== 123) throw 'fail: static method' class StaticAccessorThis { static bar = this.#foo; static get #foo() { return 123 } } if (StaticAccessorThis.bar !== 123) throw 'fail: static accessor' class FieldFromStatic { #foo = 123; static bar = new FieldFromStatic().#foo } if (FieldFromStatic.bar !== 123) throw 'fail: field from static' class MethodFromStatic { static bar = new MethodFromStatic().#foo(); #foo() { return 123 } } if (MethodFromStatic.bar !== 123) throw 'fail: method from static' class AccessorFromStatic { static bar = new AccessorFromStatic().#foo; get #foo() { return 123 } } if (AccessorFromStatic.bar !== 123) throw 'fail: accessor from static' ` // Test this code as JavaScript let buildOptions = { stdin: { contents }, bundle: true, write: false, target, } transformTests[`transformClassPrivate_${target[0]}`] = async ({ esbuild }) => new Function((await esbuild.transform(contents, { target })).code)() buildTests[`buildClassPrivate_${target[0]}`] = async ({ esbuild }) => new Function((await esbuild.build(buildOptions)).outputFiles[0].text)() // Test this code as TypeScript let buildOptionsTS = { stdin: { contents, loader: 'ts' }, bundle: true, write: false, } transformTests[`tsTransformClassPrivate_${target[0]}`] = async ({ esbuild }) => new Function((await esbuild.transform(contents, { target, loader: 'ts' })).code)() buildTests[`tsBuildClassPrivate_${target[0]}`] = async ({ esbuild }) => new Function((await esbuild.build(buildOptionsTS)).outputFiles[0].text)() } for (let es of ['es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'esnext']) registerClassPrivateTests([es]) for (let chrome = 49; chrome < 100; chrome++) registerClassPrivateTests([`chrome${chrome}`]) for (let firefox = 45; firefox < 100; firefox++) registerClassPrivateTests([`firefox${firefox}`]) for (let edge = 13; edge < 100; edge++) registerClassPrivateTests([`edge${edge}`]) for (let safari = 10; safari < 20; safari++) registerClassPrivateTests([`safari${safari}`]) let formatTests = { async formatMessages({ esbuild }) { const messages = await esbuild.formatMessages([ { text: 'This is an error' }, { text: 'Another error', location: { file: 'file.js' } }, ], { kind: 'error', }) assert.strictEqual(messages.length, 2) assert.strictEqual(messages[0], ` > error: This is an error\n\n`) assert.strictEqual(messages[1], ` > file.js:0:0: error: Another error\n 0 │ \n ╵ ^\n\n`) }, } let functionScopeCases = [ 'function x() {} { var x }', 'function* x() {} { var x }', 'async function x() {} { var x }', 'async function* x() {} { var x }', '{ var x } function x() {}', '{ var x } function* x() {}', '{ var x } async function x() {}', '{ var x } async function* x() {}', '{ function x() {} { var x } }', '{ function* x() {} { var x } }', '{ async function x() {} { var x } }', '{ async function* x() {} { var x } }', '{ { var x } function x() {} }', '{ { var x } function* x() {} }', '{ { var x } async function x() {} }', '{ { var x } async function* x() {} }', 'function f() { function x() {} { var x } }', 'function f() { function* x() {} { var x } }', 'function f() { async function x() {} { var x } }', 'function f() { async function* x() {} { var x } }', 'function f() { { var x } function x() {} }', 'function f() { { var x } function* x() {} }', 'function f() { { var x } async function x() {} }', 'function f() { { var x } async function* x() {} }', 'function f() { { function x() {} { var x } }}', 'function f() { { function* x() {} { var x } }}', 'function f() { { async function x() {} { var x } }}', 'function f() { { async function* x() {} { var x } }}', 'function f() { { { var x } function x() {} }}', 'function f() { { { var x } function* x() {} }}', 'function f() { { { var x } async function x() {} }}', 'function f() { { { var x } async function* x() {} }}', ]; { let counter = 0; for (let kind of ['var', 'let', 'const']) { for (let code of functionScopeCases) { code = code.replace('var', kind) transformTests['functionScope' + counter++] = async ({ esbuild }) => { let esbuildError let nodeError try { await esbuild.transform(code) } catch (e) { esbuildError = e } try { new Function(code)() } catch (e) { nodeError = e } if (!esbuildError !== !nodeError) { throw new Error(` code: ${code} esbuild: ${esbuildError} node: ${nodeError} `) } } } } } let syncTests = { async buildSync({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') await writeFileAsync(input, 'export default 123') esbuild.buildSync({ entryPoints: [input], bundle: true, outfile: output, format: 'cjs' }) const result = require(output) assert.strictEqual(result.default, 123) assert.strictEqual(result.__esModule, true) }, async buildSyncOutputFiles({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') await writeFileAsync(input, 'module.exports = 123') let prettyPath = path.relative(process.cwd(), input).replace(/\\/g, '/') let text = `// ${prettyPath}\nmodule.exports = 123;\n` let result = esbuild.buildSync({ entryPoints: [input], bundle: true, outfile: output, format: 'cjs', write: false }) assert.strictEqual(result.outputFiles.length, 1) assert.strictEqual(result.outputFiles[0].path, output) assert.strictEqual(result.outputFiles[0].text, text) assert.deepStrictEqual(result.outputFiles[0].contents, new Uint8Array(Buffer.from(text))) }, async transformSyncJSMap({ esbuild }) { const { code, map } = esbuild.transformSync(`1+2`, { sourcemap: true }) assert.strictEqual(code, `1 + 2;\n`) assert.strictEqual(map, `{ "version": 3, "sources": [""], "sourcesContent": ["1+2"], "mappings": "AAAA,IAAE;", "names": [] } `) }, async transformSyncJSMapNoContent({ esbuild }) { const { code, map } = esbuild.transformSync(`1+2`, { sourcemap: true, sourcesContent: false }) assert.strictEqual(code, `1 + 2;\n`) assert.strictEqual(map, `{ "version": 3, "sources": [""], "mappings": "AAAA,IAAE;", "names": [] } `) }, async transformSyncCSS({ esbuild }) { const { code, map } = esbuild.transformSync(`a{b:c}`, { loader: 'css' }) assert.strictEqual(code, `a {\n b: c;\n}\n`) assert.strictEqual(map, '') }, async transformSyncWithNonString({ esbuild }) { try { esbuild.transformSync(Buffer.from(`1+2`)) throw new Error('Expected an error to be thrown'); } catch (e) { assert.strictEqual(e.errors[0].text, 'The input to "transform" must be a string') } }, async transformSync100x({ esbuild }) { for (let i = 0; i < 100; i++) { const { code } = esbuild.transformSync(`console.log(1+${i})`, {}) assert.strictEqual(code, `console.log(1 + ${i});\n`) } }, async buildSyncThrow({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') try { const output = path.join(testDir, 'out.js') await writeFileAsync(input, '1+') esbuild.buildSync({ entryPoints: [input], bundle: true, outfile: output, format: 'cjs', logLevel: 'silent' }) const result = require(output) assert.strictEqual(result.default, 123) assert.strictEqual(result.__esModule, true) throw new Error('Expected an error to be thrown'); } catch (error) { assert(error instanceof Error, 'Must be an Error object'); assert.strictEqual(error.message, `Build failed with 1 error: ${path.relative(process.cwd(), input).replace(/\\/g, '/')}:1:2: error: Unexpected end of file`); assert.strictEqual(error.errors.length, 1); assert.strictEqual(error.warnings.length, 0); } }, async buildSyncIncrementalThrow({ esbuild, testDir }) { try { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') await writeFileAsync(input, '1+') esbuild.buildSync({ entryPoints: [input], bundle: true, outfile: output, format: 'cjs', logLevel: 'silent', incremental: true }) const result = require(output) assert.strictEqual(result.default, 123) assert.strictEqual(result.__esModule, true) throw new Error('Expected an error to be thrown'); } catch (error) { assert(error instanceof Error, 'Must be an Error object'); assert.strictEqual(error.message, `Build failed with 1 error:\nerror: Cannot use "incremental" with a synchronous build`); assert.strictEqual(error.errors.length, 1); assert.strictEqual(error.warnings.length, 0); } }, async transformThrow({ esbuild }) { try { await esbuild.transform(`1+`, {}) throw new Error('Expected an error to be thrown'); } catch (error) { assert(error instanceof Error, 'Must be an Error object'); assert.strictEqual(error.message, `Transform failed with 1 error:\n:1:2: error: Unexpected end of file`); assert.strictEqual(error.errors.length, 1); assert.strictEqual(error.warnings.length, 0); } }, async formatMessagesSync({ esbuild }) { const messages = esbuild.formatMessagesSync([ { text: 'This is an error' }, { text: 'Another error', location: { file: 'file.js' } }, ], { kind: 'error', }) assert.strictEqual(messages.length, 2) assert.strictEqual(messages[0], ` > error: This is an error\n\n`) assert.strictEqual(messages[1], ` > file.js:0:0: error: Another error\n 0 │ \n ╵ ^\n\n`) }, } async function assertSourceMap(jsSourceMap, source) { const map = await new SourceMapConsumer(jsSourceMap) const original = map.originalPositionFor({ line: 1, column: 4 }) assert.strictEqual(original.source, source) assert.strictEqual(original.line, 1) assert.strictEqual(original.column, 10) } async function main() { const esbuild = installForTests() // Create a fresh test directory removeRecursiveSync(rootTestDir) fs.mkdirSync(rootTestDir) // Time out these tests after 5 minutes. This exists to help debug test hangs in CI. let minutes = 5 let timeout = setTimeout(() => { console.error(`❌ js api tests timed out after ${minutes} minutes, exiting...`) process.exit(1) }, minutes * 60 * 1000) // Run all tests concurrently const runTest = async ([name, fn]) => { let testDir = path.join(rootTestDir, name) try { await mkdirAsync(testDir) await fn({ esbuild, testDir }) removeRecursiveSync(testDir) return true } catch (e) { console.error(`❌ ${name}: ${e && e.message || e}`) return false } } const tests = [ ...Object.entries(buildTests), ...Object.entries(watchTests), ...Object.entries(serveTests), ...Object.entries(transformTests), ...Object.entries(formatTests), ...Object.entries(syncTests), ] let allTestsPassed = (await Promise.all(tests.map(runTest))).every(success => success) if (!allTestsPassed) { console.error(`❌ js api tests failed`) process.exit(1) } else { console.log(`✅ js api tests passed`) removeRecursiveSync(rootTestDir) } clearTimeout(timeout); } main().catch(e => setTimeout(() => { throw e }))