const { installForTests, removeRecursiveSync, writeFileAtomic } = require('./esbuild') const child_process = require('child_process') const assert = require('assert') const path = require('path') const http = require('http') const https = require('https') const events = require('events') 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') const errorIcon = process.platform !== 'win32' ? '✘' : 'X' 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; } } }, // Verify that it's possible to disable a loader by setting it to "default". // In particular, verify that it's possible to disable the special loader "" // for extensionless files. async errorIfExtensionlessLoaderIsDisabled({ esbuild, testDir }) { let entry = path.join(testDir, 'entry.js'); let what = path.join(testDir, 'what'); await writeFileAsync(entry, 'import "./what"') await writeFileAsync(what, 'foo()') await esbuild.build({ entryPoints: [entry], bundle: true, write: false, }) try { await esbuild.build({ entryPoints: [entry], bundle: true, write: false, logLevel: 'silent', loader: { '': 'default' }, }) throw new Error('Expected build failure'); } catch (e) { const relPath = path.relative(process.cwd(), what).split(path.sep).join('/') if (!e.errors || !e.errors[0] || e.errors[0].text !== 'Do not know how to load path: ' + relPath) { throw e; } } }, async mangleCacheBuild({ esbuild }) { var result = await esbuild.build({ stdin: { contents: `x = { x_: 0, y_: 1, z_: 2 }`, }, mangleProps: /_/, mangleCache: { x_: 'FIXED', z_: false }, write: false, }) assert.strictEqual(result.outputFiles[0].text, 'x = { FIXED: 0, a: 1, z_: 2 };\n') assert.deepStrictEqual(result.mangleCache, { x_: 'FIXED', y_: 'a', z_: false }) }, async windowsBackslashPathTest({ esbuild, testDir }) { let entry = path.join(testDir, 'entry.js'); let nested = path.join(testDir, 'nested.js'); let outfile = path.join(testDir, 'out.js'); // On Windows, backslash and forward slash should be treated the same fs.writeFileSync(entry, ` import ${JSON.stringify(nested)} import ${JSON.stringify(nested.split(path.sep).join('/'))} `); fs.writeFileSync(nested, `console.log('once')`); const result = await esbuild.build({ entryPoints: [entry], outfile, bundle: true, write: false, minify: true, format: 'esm', }) assert.strictEqual(result.outputFiles[0].text, 'console.log("once");\n') }, 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 absPathsCodeTest({ esbuild, testDir }) { let srcDir = path.join(testDir, 'src'); let outfile = path.join(testDir, 'out', 'result.js'); let entry = path.join(srcDir, 'entry.js'); fs.mkdirSync(srcDir, { recursive: true }); fs.writeFileSync(entry, `x = typeof y == "null"`); const { metafile, warnings, outputFiles } = await esbuild.build({ entryPoints: [entry], outfile, bundle: true, write: false, metafile: true, format: 'cjs', logLevel: 'silent', absPaths: ['code'], }) const cwd = process.cwd() const makePath = absPath => path.relative(cwd, absPath).split(path.sep).join('/') assert.strictEqual(outputFiles.length, 1) assert.deepStrictEqual(outputFiles[0].path, outfile) assert.deepStrictEqual(outputFiles[0].text, `// ${entry}\nx = typeof y == "null";\n`) assert.deepStrictEqual(Object.keys(metafile.inputs), [makePath(entry)]) assert.deepStrictEqual(Object.keys(metafile.outputs), [makePath(outfile)]) assert.strictEqual(metafile.inputs[makePath(entry)].imports.length, 0) assert.strictEqual(metafile.outputs[makePath(outfile)].entryPoint, makePath(entry)) assert.strictEqual(warnings.length, 1) assert.strictEqual(warnings[0].text, 'The "typeof" operator will never evaluate to "null"') assert.strictEqual(warnings[0].location.file, makePath(entry)) }, async absPathsLogTest({ esbuild, testDir }) { let srcDir = path.join(testDir, 'src'); let outfile = path.join(testDir, 'out', 'result.js'); let entry = path.join(srcDir, 'entry.js'); fs.mkdirSync(srcDir, { recursive: true }); fs.writeFileSync(entry, `x = typeof y == "null"`); const { metafile, warnings, outputFiles } = await esbuild.build({ entryPoints: [entry], outfile, bundle: true, write: false, metafile: true, format: 'cjs', logLevel: 'silent', absPaths: ['log'], }) const cwd = process.cwd() const makePath = absPath => path.relative(cwd, absPath).split(path.sep).join('/') assert.strictEqual(outputFiles.length, 1) assert.deepStrictEqual(outputFiles[0].path, outfile) assert.deepStrictEqual(outputFiles[0].text, `// ${makePath(entry)}\nx = typeof y == "null";\n`) assert.deepStrictEqual(Object.keys(metafile.inputs), [makePath(entry)]) assert.deepStrictEqual(Object.keys(metafile.outputs), [makePath(outfile)]) assert.strictEqual(metafile.inputs[makePath(entry)].imports.length, 0) assert.strictEqual(metafile.outputs[makePath(outfile)].entryPoint, makePath(entry)) assert.strictEqual(warnings.length, 1) assert.strictEqual(warnings[0].text, 'The "typeof" operator will never evaluate to "null"') assert.strictEqual(warnings[0].location.file, entry) }, async absPathsMetafileTest({ esbuild, testDir }) { let srcDir = path.join(testDir, 'src'); let outfile = path.join(testDir, 'out', 'result.js'); let entry = path.join(srcDir, 'entry.js'); fs.mkdirSync(srcDir, { recursive: true }); fs.writeFileSync(entry, `x = typeof y == "null"`); const { metafile, warnings, outputFiles } = await esbuild.build({ entryPoints: [entry], outfile, bundle: true, write: false, metafile: true, format: 'cjs', logLevel: 'silent', absPaths: ['metafile'], }) const cwd = process.cwd() const makePath = absPath => path.relative(cwd, absPath).split(path.sep).join('/') assert.strictEqual(outputFiles.length, 1) assert.deepStrictEqual(outputFiles[0].path, outfile) assert.deepStrictEqual(outputFiles[0].text, `// ${makePath(entry)}\nx = typeof y == "null";\n`) assert.deepStrictEqual(Object.keys(metafile.inputs), [entry]) assert.deepStrictEqual(Object.keys(metafile.outputs), [outfile]) assert.strictEqual(metafile.inputs[entry].imports.length, 0) assert.strictEqual(metafile.outputs[outfile].entryPoint, entry) assert.strictEqual(warnings.length, 1) assert.strictEqual(warnings[0].text, 'The "typeof" operator will never evaluate to "null"') assert.strictEqual(warnings[0].location.file, makePath(entry)) }, async aliasValidity({ esbuild }) { const valid = async alias => { const result = await esbuild.build({ stdin: { contents: 'import ' + JSON.stringify(alias) }, bundle: true, alias: { [alias]: 'foo' }, external: ['foo'], format: 'esm', write: false, }) assert.strictEqual(result.outputFiles[0].text, '// \nimport "foo";\n') } const invalid = async alias => { try { await esbuild.build({ bundle: true, alias: { [alias]: 'foo' }, logLevel: 'silent', }) } catch { return } throw new Error('Expected an error for alias: ' + alias) } await Promise.all([ valid('foo'), valid('foo/bar'), valid('@foo'), valid('@foo/bar'), valid('@foo/bar/baz'), invalid('./foo'), invalid('../foo'), invalid('/foo'), invalid('C:\\foo'), invalid('.foo'), invalid('foo/'), invalid('@foo/'), invalid('foo/../bar'), ]) }, 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 sourceMapTrue({ 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 sourceMapLinked({ 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: 'linked', }) 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.length, 1) assert.strictEqual(json.sources[0], path.basename(input)) assert.strictEqual(json.sourcesContent[0], content) }, async sourceMapWithEmptyFile({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const empty = path.join(testDir, 'file.empty') const output = path.join(testDir, 'out.js') const content = 'exports.foo = require("./file.empty")' await writeFileAsync(input, content) await writeFileAsync(empty, 'module.exports = 123') await esbuild.build({ entryPoints: [input], outfile: output, sourcemap: true, bundle: true, loader: { '.empty': 'empty' }, }) 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.length, 1) assert.strictEqual(json.sources[0], path.basename(input)) assert.strictEqual(json.sourcesContent[0], 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.length, 1) assert.strictEqual(json.sources[0], path.basename(input)) assert.strictEqual(json.sourcesContent[0], 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 minifyWithoutInject({ esbuild, testDir }) { const input = path.join(testDir, 'in.js'); const output = path.join(testDir, 'out.js') await writeFileAsync(input, 'function exampleFn() { return Math.min(4, 3) }') const result = await esbuild.build({ entryPoints: [input], outfile: output, write: false, minify: true, }) assert.strictEqual(3, new Function(result.outputFiles[0].text + '\nreturn exampleFn()')()) }, // This should be the same as "minifyWithoutInject" above async minifyWithInject({ 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, 'function exampleFn() { return Math.min(4, 3) }') await writeFileAsync(inject, 'let min = Math.min; export { min as "Math.min" }') const result = await esbuild.build({ entryPoints: [input], outfile: output, inject: [inject], write: false, minify: true, }) assert.strictEqual(3, new Function(result.outputFiles[0].text + '\nreturn exampleFn()')()) }, 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 buildLoaderStdinBase64({ esbuild }) { // UTF-16 var result = await esbuild.build({ stdin: { contents: `\xFF`, loader: 'base64', }, write: false, }) assert.strictEqual(result.outputFiles[0].text, `module.exports = "w78=";\n`) // Binary var result = await esbuild.build({ stdin: { contents: new Uint8Array([0xFF]), loader: 'base64', }, write: false, }) assert.strictEqual(result.outputFiles[0].text, `module.exports = "/w==";\n`) }, 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 fileLoaderEntryHash({ esbuild, testDir }) { const input = path.join(testDir, 'index.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 result1 = await esbuild.build({ entryPoints: [input], bundle: true, outdir, format: 'cjs', loader: { '.bin': 'file' }, entryNames: '[name]-[hash]', write: false, }) await writeFileAsync(data, `more stuff`) const result2 = await esbuild.build({ entryPoints: [input], bundle: true, outdir, format: 'cjs', loader: { '.bin': 'file' }, entryNames: '[name]-[hash]', write: false, }) assert.strictEqual(result1.outputFiles.length, 2) assert.strictEqual(result2.outputFiles.length, 2) // Make sure each path is unique. This tests for a bug where the hash in // the output filename corresponding to the "index.js" entry point wasn't // including the filename for the "file" loader. assert.strictEqual(new Set(result1.outputFiles.concat(result2.outputFiles).map(x => x.path)).size, 4) }, async fileLoaderEntryHashNoChange({ esbuild, testDir }) { const input = path.join(testDir, 'index.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 result1 = await esbuild.build({ entryPoints: [input], bundle: true, outdir, format: 'cjs', loader: { '.bin': 'file' }, entryNames: '[name]-[hash]', assetNames: '[name]', write: false, }) await writeFileAsync(data, `more stuff`) const result2 = await esbuild.build({ entryPoints: [input], bundle: true, outdir, format: 'cjs', loader: { '.bin': 'file' }, entryNames: '[name]-[hash]', assetNames: '[name]', write: false, }) assert.strictEqual(result1.outputFiles.length, 2) assert.strictEqual(result2.outputFiles.length, 2) // The paths should be the same. The hash augmentation from the previous // test should only be checking for a file name difference, not a difference // in content, because the JS output file only contains the file name for // something using the "file" loader. assert.strictEqual(new Set(result1.outputFiles.concat(result2.outputFiles).map(x => x.path)).size, 2) }, 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-3MCOY2GR.js')) assert.deepStrictEqual(value.outputFiles[0].text, `import { foo } from "https://www.example.com/assets/chunk-3MCOY2GR.js"; export { foo as input1 }; `) assert.deepStrictEqual(value.outputFiles[1].text, `import { foo } from "https://www.example.com/assets/chunk-3MCOY2GR.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 publicPathHashing({ 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} from ${JSON.stringify(data)}`) await writeFileAsync(data, `stuff`) const [result1, result2] = await Promise.all([ esbuild.build({ entryPoints: [input], bundle: true, outdir, format: 'cjs', loader: { '.bin': 'file' }, entryNames: '[name]-[hash]', write: false, }), esbuild.build({ entryPoints: [input], bundle: true, outdir, format: 'cjs', loader: { '.bin': 'file' }, entryNames: '[name]-[hash]', publicPath: 'https://www.example.com', write: false, }), ]) const names1 = result1.outputFiles.map(x => path.basename(x.path)).sort() const names2 = result2.outputFiles.map(x => path.basename(x.path)).sort() // Check that the public path is included in chunk hashes but not asset hashes assert.deepStrictEqual(names1, ['data-BYATPJRB.bin', 'in-6QN3TZ3A.js']) assert.deepStrictEqual(names2, ['data-BYATPJRB.bin', 'in-EJERHMG4.js']) }, 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, 5) 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', original: './imported' }, { path: makePath(css), kind: 'import-statement', original: './example.css' }, { path: makePath(text), kind: 'require-call', original: './text.txt' }, ]) 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(outputCSS)].bytes, 'number') assert.strictEqual(typeof json.outputs[makePath(outputJS) + '.map'].bytes, 'number') assert.strictEqual(typeof json.outputs[makePath(outputCSS) + '.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, {}) assert.deepStrictEqual(json.outputs[makePath(outputCSS) + '.map'].imports, []) assert.deepStrictEqual(json.outputs[makePath(outputCSS) + '.map'].exports, []) assert.deepStrictEqual(json.outputs[makePath(outputCSS) + '.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-YNV25ITT.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', original: './' + path.basename(imported) }], format: 'esm', }) assert.deepStrictEqual(json.inputs[inEntry2], { bytes: 107, imports: [{ path: inImported, kind: 'import-statement', original: './' + path.basename(imported) }], format: 'esm', }) assert.deepStrictEqual(json.inputs[inImported], { bytes: 118, imports: [], format: 'esm', }) 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-ELD4XOGW.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', original: './' + path.basename(imported) }], format: 'esm', }) assert.deepStrictEqual(json.inputs[inEntry2], { bytes: 107, imports: [{ path: inImported, kind: 'import-statement', original: './' + path.basename(imported) }], format: 'esm', }) assert.deepStrictEqual(json.inputs[inImported], { bytes: 118, imports: [], format: 'esm', }) 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) => './' + path.relative(path.dirname(importing), imported).split(path.sep).join('/') await mkdirAsync(importDir) await writeFileAsync(entry, ` import ${JSON.stringify(makeImportPath(entry, shared))} import(${JSON.stringify(makeImportPath(entry, import1))}) import(${JSON.stringify(makeImportPath(entry, import2))}) `) await writeFileAsync(import1, ` import ${JSON.stringify(makeImportPath(import1, shared))} `) await writeFileAsync(import2, ` import ${JSON.stringify(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-3GRHLZ7X.js'; const outEntry = makeOutPath(path.relative(testDir, entry)); const outImport1 = makeOutPath('import1-SELM3ZIG.js'); const outImport2 = makeOutPath('import2-3GSTEHBF.js'); const outChunk = makeOutPath(chunk); assert.deepStrictEqual(json.inputs[inEntry], { bytes: 112, imports: [ { path: inShared, kind: 'import-statement', original: makeImportPath(entry, shared) }, { path: inImport1, kind: 'dynamic-import', original: makeImportPath(entry, import1) }, { path: inImport2, kind: 'dynamic-import', original: makeImportPath(entry, import2) }, ], format: 'esm', }) assert.deepStrictEqual(json.inputs[inImport1], { bytes: 35, imports: [ { path: inShared, kind: 'import-statement', original: makeImportPath(import1, shared) }, ], format: 'esm', }) assert.deepStrictEqual(json.inputs[inImport2], { bytes: 35, imports: [ { path: inShared, kind: 'import-statement', original: makeImportPath(import2, shared) }, ], format: 'esm', }) 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: outChunk, kind: 'import-statement' }, { path: outImport1, kind: 'dynamic-import' }, { path: outImport2, kind: 'dynamic-import' }, ]) 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: 66 } }) 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', original: nested1 }, { path: makePath(nested2), kind: 'import-statement', original: nested2 }, ]) assert.deepStrictEqual(json.inputs[makePath(nested1)].imports, [ { path: makePath(nested3), kind: 'import-statement', original: nested3 }, ]) 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, 2) 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', original: './imported' }, { external: true, kind: 'url-token', path: 'https://example.com/external.png' }, ] }, [makePath(image)]: { bytes: 8, imports: [] }, [makePath(imported)]: { bytes: 48, imports: [{ path: makePath(image), kind: 'url-token', original: './example.png' }] }, }, outputs: { [makePath(output)]: { bytes: 253, entryPoint: makePath(entry), imports: [ { kind: 'url-token', path: 'data:image/png,an image' }, { external: true, kind: 'url-token', path: 'https://example.com/external.png' }, ], inputs: { [makePath(entry)]: { bytesInOutput: 62 }, [makePath(imported)]: { bytesInOutput: 51 }, }, }, [makePath(output + '.map')]: { bytes: 325, exports: [], imports: [], inputs: {}, }, }, }) }, 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 } }) }, // See: https://github.com/evanw/esbuild/issues/4420 async metafileEmpty({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.ts') const unknown = path.join(testDir, 'a.unknown') await writeFileAsync(entry, ` import contents from "./a.unknown"; console.log(contents); `) await writeFileAsync(unknown, '') // Generate a build with an empty metafile JSON string due to an error let error try { await esbuild.build({ entryPoints: [entry], bundle: true, outdir: testDir, metafile: true, logLevel: 'silent', }) } catch (e) { error = e } // Make sure we get an error message about the unknown file extension assert.strictEqual(error.errors.length, 1) assert(error.errors[0].text.includes('.unknown')) }, // 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[0].hash, 'BIjVBRZOQ5s') assert.strictEqual(value.outputFiles[1].path, output) assert.strictEqual(value.outputFiles[1].contents.constructor, Uint8Array) assert.strictEqual(value.outputFiles[1].hash, 'zvyzJPvi96o') 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-P3NHLAOZ.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-BPDO6GL2.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=7RIY4OCQ.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=6VLJRT45.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=ZKX5HN4L.js' const outputB = 'entry/name=demo/hash=TYTZIN4P.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 forceTsConfigRaw({ esbuild, testDir }) { // ./a/tsconfig.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(aDir, 'tsconfig.json') const output = path.join(testDir, 'out.js') await writeFileAsync(input, 'import "test"') await writeFileAsync(forced, 'console.log("success")') await writeFileAsync(tsconfigIgnore, '{"compilerOptions": {"baseUrl": "./b", "paths": {"test": ["./ignore.js"]}}}') await esbuild.build({ entryPoints: [input], bundle: true, outfile: output, absWorkingDir: testDir, // "paths" are resolved relative to the current working directory tsconfigRaw: { compilerOptions: { baseUrl: './a/b', paths: { test: ['./test-impl.js'], }, }, }, format: 'esm', }) const result = await readFileAsync(output, 'utf8') assert.strictEqual(result, `// a/b/test-impl.js console.log("success"); `) }, async forceTsConfigRawStdin({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const test = path.join(testDir, 'test-impl.js') const output = path.join(testDir, 'out.js') await writeFileAsync(input, 'import "test"') await writeFileAsync(test, 'console.log("success")') await esbuild.build({ stdin: { contents: `import "test"`, resolveDir: '.', }, bundle: true, outfile: output, absWorkingDir: testDir, tsconfigRaw: { compilerOptions: { paths: { test: ['./test-impl.js'], }, }, }, format: 'esm', }) const result = await readFileAsync(output, 'utf8') assert.strictEqual(result, `// 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, ignoreAnnotations: true, }) 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'], platform: 'node', }) assert.strictEqual(outputFiles[0].text, `// require("/assets/file.png"); `) }, async externalPackages({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const pkgPath = path.join(testDir, 'node_modules', 'pkg', 'path.js') const dirPath = path.join(testDir, 'dir', 'path.js') await mkdirAsync(path.dirname(pkgPath), { recursive: true }) await mkdirAsync(path.dirname(dirPath), { recursive: true }) await writeFileAsync(input, ` import 'pkg/path.js' import './dir/path.js' import 'before/alias' `) await writeFileAsync(pkgPath, `console.log('pkg')`) await writeFileAsync(dirPath, `console.log('dir')`) const { outputFiles } = await esbuild.build({ entryPoints: [input], write: false, bundle: true, packages: 'external', format: 'esm', alias: { 'before': 'after' }, }) assert.strictEqual(outputFiles[0].text, `// scripts/.js-api-tests/externalPackages/in.js import "pkg/path.js"; // scripts/.js-api-tests/externalPackages/dir/path.js console.log("dir"); // scripts/.js-api-tests/externalPackages/in.js import "after/alias"; `) }, 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 rebuildBasic({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') const context = await esbuild.context({ entryPoints: [input], outfile: output, format: 'esm', }); // Build 1 await writeFileAsync(input, `console.log('abc')`) const result1 = await context.rebuild(); 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 context.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 context.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 context.dispose() try { await context.rebuild() throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e.message, 'Cannot rebuild') } }, async rebuildMerging({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const output = path.join(testDir, 'out.js') await writeFileAsync(input, `console.log(123)`) let resolveWait let waitPromise = new Promise(resolve => { resolveWait = resolve }) const waitPlugin = { name: 'wait-plugin', setup(build) { build.onStart(() => waitPromise) }, } const context = await esbuild.context({ entryPoints: [input], outfile: output, format: 'esm', plugins: [waitPlugin], write: false, }); try { // Do two rebuilds const rebuild1 = context.rebuild() const rebuild2 = context.rebuild() // Let the build end resolveWait() // Get both rebuild results const result1 = await rebuild1; const result2 = await rebuild2; assert.strictEqual(result1, result2) assert.strictEqual(result1.outputFiles.length, 1) assert.strictEqual(result1.outputFiles[0].text, 'console.log(123);\n') // Make an edit await writeFileAsync(input, `console.log(234)`) // Do two more rebuilds waitPromise = new Promise(resolve => { resolveWait = resolve }) const rebuild3 = context.rebuild() const rebuild4 = context.rebuild() // Let the build end resolveWait() // Get both rebuild results const result3 = await rebuild3; const result4 = await rebuild4; assert.strictEqual(result3, result4) assert.notStrictEqual(result3, result1) assert.strictEqual(result3.outputFiles.length, 1) assert.strictEqual(result3.outputFiles[0].text, 'console.log(234);\n') } finally { await context.dispose() } }, 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') const contextA = await esbuild.context({ entryPoints: [inputA], outfile: outputA, format: 'esm', }) const contextB = await esbuild.context({ entryPoints: [inputB], outfile: outputB, format: 'esm', }) // Build 1 await writeFileAsync(inputA, `console.log('a')`) await writeFileAsync(inputB, `console.log('b')`) assert.notStrictEqual(contextA.rebuild, contextB.rebuild) const resultA1 = await contextA.rebuild() const resultB1 = await contextB.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 = contextA.rebuild(); const promiseB = contextB.rebuild(); const resultA2 = await promiseA; const resultB2 = await promiseB; 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 contextA.dispose() try { await contextA.rebuild() throw new Error('Expected an error to be thrown (context A)') } catch (e) { assert.strictEqual(e.message, 'Cannot rebuild') } // Build 3 await writeFileAsync(inputB, `console.log(3)`) const resultB3 = await contextB.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 contextB.dispose() try { await contextB.rebuild() throw new Error('Expected an error to be thrown (context B)') } 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') const context = await esbuild.context({ entryPoints: [input], outfile: output, format: 'esm', }) try { // Build 1 await writeFileAsync(input, `console.log('abc')`) const result1 = await context.rebuild() 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 = context.rebuild(); const promise2B = context.rebuild(); const result2A = await promise2A; const result2B = await promise2B; 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 = context.rebuild(); const promise3B = context.rebuild(); const result3A = await promise3A; const result3B = await promise3B; assert.strictEqual(result3A.outputFiles, void 0) assert.strictEqual(result3B.outputFiles, void 0) assert.strictEqual(await readFileAsync(output, 'utf8'), `console.log(123);\n`) } finally { context.dispose() } // Further rebuilds should not be possible after a dispose try { await context.rebuild() throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e.message, 'Cannot rebuild') } }, async rebuildCancel({ esbuild }) { let loopForever = true let onEndResult let onLoadCallback const context = await esbuild.context({ entryPoints: ['entry'], bundle: true, write: false, logLevel: 'silent', format: 'esm', plugins: [{ name: '∞', setup(build) { build.onResolve({ filter: /.*/ }, args => { return { path: args.path, namespace: '∞' } }) build.onLoad({ filter: /.*/ }, async (args) => { onLoadCallback() if (!loopForever) return { contents: 'foo()' } await new Promise(r => setTimeout(r, 10)) return { contents: 'import ' + JSON.stringify(args.path + '.') } }) build.onEnd(result => { onEndResult = result }) }, }], }) try { // Build 1 { // Stop the build when "onLoad" has been called at least 5 times const shouldCancelPromise = new Promise(resolve => { let count = 0 onLoadCallback = () => { if (++count > 5) resolve() } }) // Start a build const buildPromise = context.rebuild() // Add a dummy catch handler to avoid terminating due to an unhandled exception buildPromise.catch(() => { }) // Cancel the build await shouldCancelPromise await context.cancel() // Check the result try { await buildPromise throw new Error('Expected an error to be thrown') } catch (error) { assert.strictEqual(error.message, `Build failed with 1 error:\nerror: The build was canceled`) assert.strictEqual(error.errors.length, 1) assert.strictEqual(error.warnings.length, 0) assert.notStrictEqual(onEndResult, undefined) assert.strictEqual(onEndResult.errors.length, 1) assert.strictEqual(onEndResult.errors[0].text, 'The build was canceled') assert.strictEqual(onEndResult.warnings.length, 0) assert.strictEqual(onEndResult.outputFiles.length, 0) } } // Build 2 { // Stop the build when "onLoad" has been called at least 5 times const shouldCancelPromise = new Promise(resolve => { let count = 0 onLoadCallback = () => { if (++count > 5) resolve() } }) // Start a build const buildPromise = context.rebuild() // Add a dummy catch handler to avoid terminating due to an unhandled exception buildPromise.catch(() => { }) // Cancel the build await shouldCancelPromise await context.cancel() // Check the result try { await buildPromise throw new Error('Expected an error to be thrown') } catch (error) { assert.strictEqual(error.message, `Build failed with 1 error:\nerror: The build was canceled`) assert.strictEqual(error.errors.length, 1) assert.strictEqual(error.warnings.length, 0) assert.notStrictEqual(onEndResult, undefined) assert.strictEqual(onEndResult.errors.length, 1) assert.strictEqual(onEndResult.errors[0].text, 'The build was canceled') assert.strictEqual(onEndResult.warnings.length, 0) assert.strictEqual(onEndResult.outputFiles.length, 0) } } // Build 3 loopForever = false { const result = await context.rebuild() assert.strictEqual(result.errors.length, 0) assert.strictEqual(result.warnings.length, 0) assert.strictEqual(result.outputFiles.length, 1) assert.strictEqual(result.outputFiles[0].text, `// ∞:entry\nfoo();\n`) assert.strictEqual(onEndResult, result) } } finally { context.dispose() } }, // This test checks for races between manual "rebuild()" and "cancel()". // Ideally calling "cancel()" after "rebuild()" will always cancel it even // if the Go side hasn't started running the rebuild yet. Since Go is multi- // threaded, Go can actually start running the "cancel()" before the // "rebuild()" (i.e. in the other order). The Go code does some complex stuff // with mutexes to try to make this ideal behavior happen despite multi- // threading. async rapidRebuildCancel({ esbuild }) { const context = await esbuild.context({ entryPoints: ['entry'], logLevel: 'silent', plugins: [{ name: '∞', setup(build) { build.onResolve({ filter: /.*/ }, args => { return { path: args.path, namespace: '∞' } }) build.onLoad({ filter: /.*/ }, async (args) => { await new Promise(r => setTimeout(r, 10)) return { contents: 'import ' + JSON.stringify(args.path + '.') } }) }, }], }) try { const promises = [] for (let i = 0; i < 100; i++) { const promise = context.rebuild() promise.catch(() => { /* avoid termination due to an uncaught exception */ }) promises.push(promise) await context.cancel() } for (let i = 0; i < promises.length; i++) { try { await promises[i] throw new Error('Expected an error to be thrown for rebuild ' + i) } catch (err) { if (!err.errors || err.errors.length !== 1 || err.errors[0].text !== 'The build was canceled') throw err } } } finally { context.dispose() } }, 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({ testDir, esbuild }) { const input = path.join(testDir, 'input.ts') await writeFileAsync(input, ` 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' `) await writeFileAsync(path.join(testDir, 'tsconfig.json'), `{ "compilerOptions": { "experimentalDecorators": true, }, }`) var { outputFiles } = await esbuild.build({ entryPoints: [input], 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', 'CXHWNMAN-1.cjs.js')) assert.strictEqual(outputFiles[1].path, path.join(testDir, 'entry', 'out', 'EYSNILNO-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', 'TIORPBNU-1.js')) assert.strictEqual(outputFiles[1].path, path.join(testDir, 'entry', 'out', '3KY7NOSR-2.js')) }, async customEntryPointOutputPathsDuplicates({ esbuild, testDir }) { const input1 = path.join(testDir, 'foo.js') const input2 = path.join(testDir, 'bar.css') await writeFileAsync(input1, `foo()`) await writeFileAsync(input2, `.bar {}`) var { outputFiles } = await esbuild.build({ entryPoints: [ { in: input1, out: 'abc' }, { in: input2, out: 'abc' }, ], outdir: testDir, write: false, }) assert.strictEqual(outputFiles.length, 2) assert.strictEqual(outputFiles[0].path, path.join(testDir, 'abc.js')) assert.strictEqual(outputFiles[1].path, path.join(testDir, 'abc.css')) }, async nodeColonPrefixImport({ esbuild }) { const tryTargetESM = async target => { const result = await esbuild.build({ stdin: { contents: `import fs from 'node:fs'; import('node:fs'); fs()` }, bundle: true, platform: 'node', target: target.split(','), format: 'esm', write: false, }) const code = result.outputFiles[0].text return code.slice(code.indexOf(`// \n`)) } assert.strictEqual(await tryTargetESM('node14.13.1'), `// \nimport fs from "node:fs";\nimport("node:fs");\nfs();\n`) assert.strictEqual(await tryTargetESM('node14.13.0'), `// \nimport fs from "fs";\nimport("fs");\nfs();\n`) assert.strictEqual(await tryTargetESM('node13'), `// \nimport fs from "fs";\nPromise.resolve().then(() => __toESM(__require("fs")));\nfs();\n`) assert.strictEqual(await tryTargetESM('node12.99'), `// \nimport fs from "node:fs";\nimport("node:fs");\nfs();\n`) assert.strictEqual(await tryTargetESM('node12.20'), `// \nimport fs from "node:fs";\nimport("node:fs");\nfs();\n`) assert.strictEqual(await tryTargetESM('node12.19'), `// \nimport fs from "fs";\nPromise.resolve().then(() => __toESM(__require("fs")));\nfs();\n`) assert.strictEqual(await tryTargetESM('node18,es6'), `// \nimport fs from "node:fs";\nimport("node:fs");\nfs();\n`) }, async nodeColonPrefixRequire({ esbuild }) { const tryTargetESM = async target => { const result = await esbuild.build({ stdin: { contents: `require('node:fs'); require.resolve('node:fs')` }, bundle: true, platform: 'node', target: target.split(','), format: 'cjs', write: false, }) const code = result.outputFiles[0].text return code.slice(code.indexOf(`// \n`)) } assert.strictEqual(await tryTargetESM('node16'), `// \nrequire("node:fs");\nrequire.resolve("node:fs");\n`) assert.strictEqual(await tryTargetESM('node15.99'), `// \nrequire("fs");\nrequire.resolve("fs");\n`) assert.strictEqual(await tryTargetESM('node15'), `// \nrequire("fs");\nrequire.resolve("fs");\n`) assert.strictEqual(await tryTargetESM('node14.99'), `// \nrequire("node:fs");\nrequire.resolve("node:fs");\n`) assert.strictEqual(await tryTargetESM('node14.18'), `// \nrequire("node:fs");\nrequire.resolve("node:fs");\n`) assert.strictEqual(await tryTargetESM('node14.17'), `// \nrequire("fs");\nrequire.resolve("fs");\n`) assert.strictEqual(await tryTargetESM('node18,es6'), `// \nrequire("node:fs");\nrequire.resolve("node:fs");\n`) }, async nodeColonPrefixImportTurnedIntoRequire({ esbuild }) { const tryTargetESM = async target => { const result = await esbuild.build({ stdin: { contents: `import fs from 'node:fs'; import('node:fs'); fs()` }, bundle: true, platform: 'node', target: target.split(','), format: 'cjs', write: false, }) const code = result.outputFiles[0].text return code.slice(code.indexOf(`// \n`)) } assert.strictEqual(await tryTargetESM('node16'), `// \nvar import_node_fs = __toESM(require("node:fs"));\nimport("node:fs");\n(0, import_node_fs.default)();\n`) assert.strictEqual(await tryTargetESM('node15.99'), `// \nvar import_node_fs = __toESM(require("fs"));\nimport("fs");\n(0, import_node_fs.default)();\n`) assert.strictEqual(await tryTargetESM('node15'), `// \nvar import_node_fs = __toESM(require("fs"));\nimport("fs");\n(0, import_node_fs.default)();\n`) assert.strictEqual(await tryTargetESM('node14.99'), `// \nvar import_node_fs = __toESM(require("node:fs"));\nimport("node:fs");\n(0, import_node_fs.default)();\n`) assert.strictEqual(await tryTargetESM('node14.18'), `// \nvar import_node_fs = __toESM(require("node:fs"));\nimport("node:fs");\n(0, import_node_fs.default)();\n`) assert.strictEqual(await tryTargetESM('node14.17'), `// \nvar import_node_fs = __toESM(require("fs"));\nimport("fs");\n(0, import_node_fs.default)();\n`) assert.strictEqual(await tryTargetESM('node18,es6'), `// \nvar import_node_fs = __toESM(require("node:fs"));\nimport("node:fs");\n(0, import_node_fs.default)();\n`) }, async zipFile({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const zip = path.join(testDir, 'test.zip') await writeFileAsync(entry, ` import foo from './test.zip/foo.js' import bar from './test.zip/bar/bar.js' import __virtual__1 from './test.zip/__virtual__/ignored/0/foo.js' import __virtual__2 from './test.zip/ignored/__virtual__/ignored/1/foo.js' import __virtual__3 from './test.zip/__virtual__/ignored/1/test.zip/foo.js' import $$virtual1 from './test.zip/$$virtual/ignored/0/foo.js' import $$virtual2 from './test.zip/ignored/$$virtual/ignored/1/foo.js' import $$virtual3 from './test.zip/$$virtual/ignored/1/test.zip/foo.js' console.log({ foo, bar, __virtual__1, __virtual__2, __virtual__3, $$virtual1, $$virtual2, $$virtual3, }) `) // This uses the real file system instead of the mock file system so that // we can check that everything works as expected on Windows, which is not // a POSIX environment. await writeFileAsync(zip, Buffer.from( `UEsDBAoAAgAAAG1qCFUSAXosFQAAABUAAAAGABwAZm9vLmpzVVQJAAOeRfFioEXxYnV4C` + `wABBPUBAAAEFAAAAGV4cG9ydCBkZWZhdWx0ICdmb28nClBLAwQKAAIAAABzaghVwuDbLR` + `UAAAAVAAAACgAcAGJhci9iYXIuanNVVAkAA6lF8WKrRfFidXgLAAEE9QEAAAQUAAAAZXh` + `wb3J0IGRlZmF1bHQgJ2JhcicKUEsBAh4DCgACAAAAbWoIVRIBeiwVAAAAFQAAAAYAGAAA` + `AAAAAQAAAKSBAAAAAGZvby5qc1VUBQADnkXxYnV4CwABBPUBAAAEFAAAAFBLAQIeAwoAA` + `gAAAHNqCFXC4NstFQAAABUAAAAKABgAAAAAAAEAAACkgVUAAABiYXIvYmFyLmpzVVQFAA` + `OpRfFidXgLAAEE9QEAAAQUAAAAUEsFBgAAAAACAAIAnAAAAK4AAAAAAA==`, 'base64')) const value = await esbuild.build({ entryPoints: [entry], bundle: true, write: false, }) assert.strictEqual(value.outputFiles.length, 1) assert.strictEqual(value.outputFiles[0].text, `(() => { // scripts/.js-api-tests/zipFile/test.zip/foo.js var foo_default = "foo"; // scripts/.js-api-tests/zipFile/test.zip/bar/bar.js var bar_default = "bar"; // scripts/.js-api-tests/zipFile/test.zip/__virtual__/ignored/0/foo.js var foo_default2 = "foo"; // scripts/.js-api-tests/zipFile/test.zip/ignored/__virtual__/ignored/1/foo.js var foo_default3 = "foo"; // scripts/.js-api-tests/zipFile/test.zip/__virtual__/ignored/1/test.zip/foo.js var foo_default4 = "foo"; // scripts/.js-api-tests/zipFile/test.zip/$$virtual/ignored/0/foo.js var foo_default5 = "foo"; // scripts/.js-api-tests/zipFile/test.zip/ignored/$$virtual/ignored/1/foo.js var foo_default6 = "foo"; // scripts/.js-api-tests/zipFile/test.zip/$$virtual/ignored/1/test.zip/foo.js var foo_default7 = "foo"; // scripts/.js-api-tests/zipFile/entry.js console.log({ foo: foo_default, bar: bar_default, __virtual__1: foo_default2, __virtual__2: foo_default3, __virtual__3: foo_default4, $$virtual1: foo_default5, $$virtual2: foo_default6, $$virtual3: foo_default7 }); })(); `) }, async yarnPnP_pnp_data_json({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const manifest = path.join(testDir, '.pnp.data.json') const leftPad = path.join(testDir, '.yarn', 'cache', 'left-pad-zip', 'node_modules', 'left-pad', 'index.js') await writeFileAsync(entry, ` import leftPad from 'left-pad' console.log(leftPad()) `) await writeFileAsync(manifest, `{ "packageRegistryData": [ [null, [ [null, { "packageLocation": "./", "packageDependencies": [ ["left-pad", "npm:1.3.0"] ], "linkType": "SOFT" }] ]], ["left-pad", [ ["npm:1.3.0", { "packageLocation": "./.yarn/cache/left-pad-zip/node_modules/left-pad/", "packageDependencies": [ ["left-pad", "npm:1.3.0"] ], "linkType": "HARD" }] ]] ] }`) await mkdirAsync(path.dirname(leftPad), { recursive: true }) await writeFileAsync(leftPad, `export default function() {}`) const value = await esbuild.build({ entryPoints: [entry], bundle: true, write: false, absWorkingDir: testDir, }) assert.strictEqual(value.outputFiles.length, 1) assert.strictEqual(value.outputFiles[0].text, `(() => { // .yarn/cache/left-pad-zip/node_modules/left-pad/index.js function left_pad_default() { } // entry.js console.log(left_pad_default()); })(); `) }, async yarnPnP_pnp_js_object_literal({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const manifest = path.join(testDir, '.pnp.js') const leftPad = path.join(testDir, '.yarn', 'cache', 'left-pad-zip', 'node_modules', 'left-pad', 'index.js') await writeFileAsync(entry, ` import leftPad from 'left-pad' console.log(leftPad()) `) await writeFileAsync(manifest, `#!/usr/bin/env node /* eslint-disable */ try { Object.freeze({}).detectStrictMode = true; } catch (error) { throw new Error(); } function $$SETUP_STATE(hydrateRuntimeState, basePath) { return hydrateRuntimeState({ "packageRegistryData": [ [null, [ [null, { "packageLocation": "./", "packageDependencies": [ ["left-pad", "npm:1.3.0"] ], "linkType": "SOFT" }] ]], ["left-pad", [ ["npm:1.3.0", { "packageLocation": "./.yarn/cache/left-pad-zip/node_modules/left-pad/", "packageDependencies": [ ["left-pad", "npm:1.3.0"] ], "linkType": "HARD" }] ]] ] }) } `) await mkdirAsync(path.dirname(leftPad), { recursive: true }) await writeFileAsync(leftPad, `export default function() {}`) const value = await esbuild.build({ entryPoints: [entry], bundle: true, write: false, absWorkingDir: testDir, }) assert.strictEqual(value.outputFiles.length, 1) assert.strictEqual(value.outputFiles[0].text, `(() => { // .yarn/cache/left-pad-zip/node_modules/left-pad/index.js function left_pad_default() { } // entry.js console.log(left_pad_default()); })(); `) }, async yarnPnP_pnp_cjs_JSON_parse_string_literal({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const manifest = path.join(testDir, '.pnp.cjs') const leftPad = path.join(testDir, '.yarn', 'cache', 'left-pad-zip', 'node_modules', 'left-pad', 'index.js') await writeFileAsync(entry, ` import leftPad from 'left-pad' console.log(leftPad()) `) await writeFileAsync(manifest, `#!/usr/bin/env node /* eslint-disable */ try { Object.freeze({}).detectStrictMode = true; } catch (error) { throw new Error(); } function $$SETUP_STATE(hydrateRuntimeState, basePath) { return hydrateRuntimeState(JSON.parse('{\\ "packageRegistryData": [\\ [null, [\\ [null, {\\ "packageLocation": "./",\\ "packageDependencies": [\\ ["left-pad", "npm:1.3.0"]\\ ],\\ "linkType": "SOFT"\\ }]\\ ]],\\ ["left-pad", [\\ ["npm:1.3.0", {\\ "packageLocation": "./.yarn/cache/left-pad-zip/node_modules/left-pad/",\\ "packageDependencies": [\\ ["left-pad", "npm:1.3.0"]\\ ],\\ "linkType": "HARD"\\ }]\\ ]]\\ ]\\ }')) } `) await mkdirAsync(path.dirname(leftPad), { recursive: true }) await writeFileAsync(leftPad, `export default function() {}`) const value = await esbuild.build({ entryPoints: [entry], bundle: true, write: false, absWorkingDir: testDir, }) assert.strictEqual(value.outputFiles.length, 1) assert.strictEqual(value.outputFiles[0].text, `(() => { // .yarn/cache/left-pad-zip/node_modules/left-pad/index.js function left_pad_default() { } // entry.js console.log(left_pad_default()); })(); `) }, async yarnPnP_pnp_cjs_JSON_parse_identifier({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const manifest = path.join(testDir, '.pnp.cjs') const leftPad = path.join(testDir, '.yarn', 'cache', 'left-pad-zip', 'node_modules', 'left-pad', 'index.js') await writeFileAsync(entry, ` import leftPad from 'left-pad' console.log(leftPad()) `) await writeFileAsync(manifest, `#!/usr/bin/env node /* eslint-disable */ try { Object.freeze({}).detectStrictMode = true; } catch (error) { throw new Error(); } const RAW_RUNTIME_STATE = '{\\ "packageRegistryData": [\\ [null, [\\ [null, {\\ "packageLocation": "./",\\ "packageDependencies": [\\ ["left-pad", "npm:1.3.0"]\\ ],\\ "linkType": "SOFT"\\ }]\\ ]],\\ ["left-pad", [\\ ["npm:1.3.0", {\\ "packageLocation": "./.yarn/cache/left-pad-zip/node_modules/left-pad/",\\ "packageDependencies": [\\ ["left-pad", "npm:1.3.0"]\\ ],\\ "linkType": "HARD"\\ }]\\ ]]\\ ]\\ }' function $$SETUP_STATE(hydrateRuntimeState, basePath) { return hydrateRuntimeState(JSON.parse(RAW_RUNTIME_STATE)) } `) await mkdirAsync(path.dirname(leftPad), { recursive: true }) await writeFileAsync(leftPad, `export default function() {}`) const value = await esbuild.build({ entryPoints: [entry], bundle: true, write: false, absWorkingDir: testDir, }) assert.strictEqual(value.outputFiles.length, 1) assert.strictEqual(value.outputFiles[0].text, `(() => { // .yarn/cache/left-pad-zip/node_modules/left-pad/index.js function left_pad_default() { } // entry.js console.log(left_pad_default()); })(); `) }, async yarnPnP_ignoreNestedManifests({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const foo = path.join(testDir, 'node_modules', 'foo', 'index.js') const bar = path.join(testDir, 'node_modules', 'bar', 'index.js') const manifest = path.join(testDir, '.pnp.data.json') await writeFileAsync(entry, ` import foo from 'foo' console.log(foo) `) await mkdirAsync(path.dirname(foo), { recursive: true }) await writeFileAsync(foo, ` import bar from 'bar' export default 'foo' + bar `) await mkdirAsync(path.dirname(bar), { recursive: true }) await writeFileAsync(bar, ` export default 'bar' `) await writeFileAsync(manifest, `{ "packageRegistryData": [ [null, [ [null, { "packageLocation": "./", "packageDependencies": [ ["foo", "npm:1.0.0"], ["bar", "npm:1.0.0"] ], "linkType": "SOFT" }] ]], ["foo", [ ["npm:1.0.0", { "packageLocation": "./__virtual__/whatever/0/node_modules/foo/", "packageDependencies": [ ["bar", "npm:1.0.0"] ], "linkType": "HARD" }] ]], ["bar", [ ["npm:1.0.0", { "packageLocation": "./__virtual__/whatever/0/node_modules/bar/", "packageDependencies": [], "linkType": "HARD" }] ]] ] }`) const value = await esbuild.build({ entryPoints: [entry], bundle: true, write: false, absWorkingDir: testDir, }) assert.strictEqual(value.outputFiles.length, 1) assert.strictEqual(value.outputFiles[0].text, `(() => { // __virtual__/whatever/0/node_modules/bar/index.js var bar_default = "bar"; // __virtual__/whatever/0/node_modules/foo/index.js var foo_default = "foo" + bar_default; // entry.js console.log(foo_default); })(); `) }, async yarnPnP_tsconfig({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.jsx') const tsconfigExtends = path.join(testDir, 'tsconfig.json') const tsconfigBase = path.join(testDir, 'foo', 'tsconfig.json') const manifest = path.join(testDir, '.pnp.data.json') await writeFileAsync(entry, ` console.log(
) `) await writeFileAsync(tsconfigExtends, `{ "extends": "@scope/base/tsconfig.json", }`) await mkdirAsync(path.dirname(tsconfigBase), { recursive: true }) await writeFileAsync(tsconfigBase, `{ "compilerOptions": { "jsxFactory": "success" } }`) await writeFileAsync(manifest, `{ "packageRegistryData": [ [null, [ [null, { "packageLocation": "./", "packageDependencies": [ ["@scope/base", "npm:1.0.0"] ], "linkType": "SOFT" }] ]], ["@scope/base", [ ["npm:1.0.0", { "packageLocation": "./foo/", "packageDependencies": [], "linkType": "HARD" }] ]] ] }`) const value = await esbuild.build({ entryPoints: [entry], bundle: true, write: false, absWorkingDir: testDir, }) assert.strictEqual(value.outputFiles.length, 1) assert.strictEqual(value.outputFiles[0].text, `(() => { // entry.jsx console.log(/* @__PURE__ */ success("div", null)); })(); `) }, async yarnPnP_indexJs({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const fooIndex = path.join(testDir, 'node_modules', '@some', 'pkg', 'index.js') const fooFoo = path.join(testDir, 'node_modules', '@some', 'pkg', 'foo.js') const manifest = path.join(testDir, '.pnp.data.json') await writeFileAsync(entry, ` import x from '@some/pkg' x() `) await mkdirAsync(path.dirname(fooIndex), { recursive: true }) await writeFileAsync(fooIndex, `export default success`) await mkdirAsync(path.dirname(fooFoo), { recursive: true }) await writeFileAsync(fooFoo, `failure!`) await writeFileAsync(manifest, `{ "packageRegistryData": [ [null, [ [null, { "packageLocation": "./", "packageDependencies": [ ["@some/pkg", "npm:1.0.0"] ], "linkType": "SOFT" }] ]], ["@some/pkg", [ ["npm:1.0.0", { "packageLocation": "./node_modules/@some/pkg/", "packageDependencies": [], "linkType": "HARD" }] ]] ] }`) const value = await esbuild.build({ entryPoints: [entry], bundle: true, write: false, absWorkingDir: testDir, }) assert.strictEqual(value.outputFiles.length, 1) assert.strictEqual(value.outputFiles[0].text, `(() => { // node_modules/@some/pkg/index.js var pkg_default = success; // entry.js pkg_default(); })(); `) }, async yarnPnP_depOfVirtual({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.js') const pkg = path.join(testDir, '.yarn', 'cache', 'pkg-zip', 'node_modules', 'pkg', 'index.js') const dep = path.join(testDir, '.yarn', 'cache', 'dep-zip', 'node_modules', 'dep', 'index.js') const manifest = path.join(testDir, '.pnp.data.json') await writeFileAsync(entry, `import 'pkg'`) await mkdirAsync(path.dirname(pkg), { recursive: true }) await writeFileAsync(pkg, `import 'dep'`) await mkdirAsync(path.dirname(dep), { recursive: true }) await writeFileAsync(dep, `success()`) await writeFileAsync(manifest, `{ "packageRegistryData": [ [null, [ [null, { "packageLocation": "./", "packageDependencies": [ ["pkg", "virtual:some-path"] ], "linkType": "SOFT" }] ]], ["demo", [ ["workspace:.", { "packageLocation": "./", "packageDependencies": [ ["demo", "workspace:."], ["pkg", "virtual:some-path"] ], "linkType": "SOFT" }] ]], ["pkg", [ ["npm:1.0.0", { "packageLocation": "./.yarn/cache/pkg-zip/node_modules/pkg/", "packageDependencies": [ ["pkg", "npm:1.0.0"] ], "linkType": "SOFT" }], ["virtual:some-path", { "packageLocation": "./.yarn/__virtual__/pkg-virtual/0/cache/pkg-zip/node_modules/pkg/", "packageDependencies": [ ["pkg", "virtual:some-path"], ["dep", "npm:1.0.0"] ], "linkType": "HARD" }] ]], ["dep", [ ["npm:1.0.0", { "packageLocation": "./.yarn/cache/dep-zip/node_modules/dep/", "packageDependencies": [ ["dep", "npm:1.0.0"] ], "linkType": "HARD" }] ]] ] }`) const value = await esbuild.build({ entryPoints: [entry], bundle: true, write: false, absWorkingDir: testDir, }) assert.strictEqual(value.outputFiles.length, 1) assert.strictEqual(value.outputFiles[0].text, `(() => { // .yarn/cache/dep-zip/node_modules/dep/index.js success(); })(); `) }, // https://github.com/evanw/esbuild/issues/3915 async yarnPnP_stackOverflow({ esbuild, testDir }) { const entry = path.join(testDir, 'entry.jsx') await writeFileAsync(entry, `console.log(
)`) await writeFileAsync(path.join(testDir, 'tsconfig.json'), `{ "extends": "tsconfigs/config" }`) await mkdirAsync(path.join(testDir, 'packages/tsconfigs/configs'), { recursive: true }) await writeFileAsync(path.join(testDir, 'packages/tsconfigs/package.json'), `{ "exports": { "./config": "./configs/tsconfig.json" } }`) await writeFileAsync(path.join(testDir, 'packages/tsconfigs/configs/tsconfig.json'), `{ "compilerOptions": { "jsxFactory": "success" } }`) await writeFileAsync(path.join(testDir, '.pnp.data.json'), `{ "packageRegistryData": [ [null, [ [null, { "packageLocation": "./", "packageDependencies": [ ["tsconfigs", "virtual:some-path"] ], "linkType": "SOFT" }] ]], ["tsconfigs", [ ["virtual:some-path", { "packageLocation": "./.yarn/__virtual__/tsconfigs-virtual-f56a53910e/1/packages/tsconfigs/", "packageDependencies": [ ["tsconfigs", "virtual:some-path"] ], "packagePeers": [], "linkType": "SOFT" }], ["workspace:packages/tsconfigs", { "packageLocation": "./packages/tsconfigs/", "packageDependencies": [ ["tsconfigs", "workspace:packages/tsconfigs"] ], "linkType": "SOFT" }] ]] ] }`) const value = await esbuild.build({ entryPoints: [entry], bundle: true, write: false, absWorkingDir: testDir, }) assert.strictEqual(value.outputFiles.length, 1) assert.strictEqual(value.outputFiles[0].text, `(() => { // entry.jsx console.log(/* @__PURE__ */ success("div", null)); })(); `) }, } function fetch(host, port, path, { headers, method = 'GET' } = {}) { return new Promise((resolve, reject) => { http.request({ method, host, port, path, headers }, 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) { const error = new Error(`${res.statusCode} when fetching "${path}": ${content}`) error.statusCode = res.statusCode reject(error) } else { content.headers = res.headers resolve(content) } }) }).on('error', reject).end() }) } function fetchHTTPS(host, port, path, { certfile }) { return new Promise((resolve, reject) => { const checkServerIdentity = (hostname, cert) => { // I'm not sure why node seems to always reject the host "127.0.0.1" assert.strictEqual(hostname, '127.0.0.1') assert.strictEqual(cert.subject.CN, '127.0.0.1') } const ca = [fs.readFileSync(certfile, 'utf8')] https.get({ host, port, path, ca, checkServerIdentity }, 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) { const error = new Error(`${res.statusCode} when fetching "${path}": ${content}`) error.statusCode = res.statusCode reject(error) } else { content.headers = res.headers resolve(content) } }) }).on('error', reject) }) } function partialFetch(host, port, path, { headers, method = 'GET' } = {}) { return new Promise((resolve, reject) => { http.request({ method, host, port, path, headers }, res => { resolve(res) res.socket.destroy() }).on('error', reject).end() }) } const makeEventStream = (host, port, path) => { return new Promise((resolve, reject) => { const headers = { 'Accept': 'text/event-stream', } http.get({ host, port, path, headers }, res => { if (res.statusCode !== 200) { reject(new Error(`${res.statusCode} when fetching "${path}"`)) return } const stream = new events.EventEmitter let buffer = '' res.on('data', chunk => { buffer += chunk while (true) { const index = buffer.indexOf('\n\n') if (index < 0) break const lines = buffer.slice(0, index).split('\n') const fields = Object.create(null) buffer = buffer.slice(index + 2) for (const line of lines) { const colon = line.indexOf(':') if (colon >= 0) { const key = line.slice(0, colon).trim() const value = line.slice(colon + 1).trim() fields[key] = value } } if ('data' in fields) { stream.emit('event' in fields ? fields.event : 'message', fields) } } }) res.on('close', () => stream.emit('close')) stream.destroy = () => res.destroy() stream.waitFor = name => new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Timeout after 30 seconds')) stream.destroy() }, 30 * 1000) stream.once(name, value => { clearTimeout(timeout) resolve(value) }) }) resolve(stream) }).on('error', reject) }) } const makeRebuildUntilPlugin = () => { let onEnd return { rebuildUntil: (mutator, condition) => new Promise((resolve, reject) => { let timeout = setTimeout(() => reject(new Error('Timeout after 30 seconds')), 30 * 1000) onEnd = result => { try { if (result && condition(result)) clearTimeout(timeout), resolve(result) } catch (e) { clearTimeout(timeout), reject(e) } } mutator() }), plugin: { name: 'rebuildUntil', setup(build) { build.onEnd(result => onEnd(result)) }, }, } } let watchTests = { async watchTwice({ esbuild }) { const context = await esbuild.context({}) try { // Watch once await context.watch() // Watch twice try { await context.watch() throw new Error('Expected an error to be thrown') } catch (err) { assert.strictEqual(err.message, 'Watch mode has already been enabled') } } finally { await context.dispose() } }, 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`) const { rebuildUntil, plugin } = makeRebuildUntilPlugin() const context = await esbuild.context({ entryPoints: [input], outfile, format: 'esm', logLevel: 'silent', plugins: [plugin], }) try { const result = await rebuildUntil( () => context.watch(), () => true, ) assert.strictEqual(result.outputFiles, void 0) assert.strictEqual(result.errors.length, 0) assert.strictEqual(await readFileAsync(outfile, 'utf8'), 'throw 1;\n') // First rebuild: edit { const result2 = await rebuildUntil( () => writeFileAtomic(input, `throw 2`), () => fs.readFileSync(outfile, 'utf8') === 'throw 2;\n', ) assert.strictEqual(result2.outputFiles, void 0) assert.strictEqual(result2.errors.length, 0) assert.strictEqual(await readFileAsync(outfile, 'utf8'), 'throw 2;\n') } // Second rebuild: edit { const result2 = await rebuildUntil( () => writeFileAtomic(input, `throw 3`), () => fs.readFileSync(outfile, 'utf8') === 'throw 3;\n', ) assert.strictEqual(result2.outputFiles, void 0) assert.strictEqual(result2.errors.length, 0) assert.strictEqual(await readFileAsync(outfile, 'utf8'), 'throw 3;\n') } // Third rebuild: syntax error { const result2 = await rebuildUntil( () => writeFileAtomic(input, `throw 1 2`), result => result.errors.length > 0, ) assert.strictEqual(result2.errors.length, 1) assert.strictEqual(result2.errors[0].text, 'Expected ";" but found "2"') assert.strictEqual(fs.existsSync(outfile), false) } // Fourth rebuild: edit { const result2 = await rebuildUntil( () => writeFileAtomic(input, `throw 4`), () => fs.readFileSync(outfile, 'utf8') === 'throw 4;\n', ) assert.strictEqual(result2.outputFiles, void 0) assert.strictEqual(result2.errors.length, 0) assert.strictEqual(await readFileAsync(outfile, 'utf8'), 'throw 4;\n') } // Fifth rebuild: delete { const result2 = await rebuildUntil( () => fs.promises.unlink(input), result => result.errors.length > 0, ) assert.strictEqual(result2.errors.length, 1) assert.strictEqual(fs.existsSync(outfile), false) } // Sixth rebuild: restore { const result2 = await rebuildUntil( () => writeFileAtomic(input, `throw 5`), () => fs.readFileSync(outfile, 'utf8') === 'throw 5;\n', ) assert.strictEqual(result2.outputFiles, void 0) assert.strictEqual(result2.errors.length, 0) assert.strictEqual(await readFileAsync(outfile, 'utf8'), 'throw 5;\n') } } finally { await context.dispose() } }, 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`) const { rebuildUntil, plugin } = makeRebuildUntilPlugin() const context = await esbuild.context({ entryPoints: [input], outdir, format: 'esm', logLevel: 'silent', write: false, plugins: [plugin], }) try { const result = await rebuildUntil( () => context.watch(), () => true, ) assert.strictEqual(result.errors.length, 0) assert.strictEqual(result.outputFiles[0].text, 'throw 1;\n') assert.strictEqual(fs.existsSync(output), false) // First rebuild: edit { const result2 = await rebuildUntil( () => writeFileAtomic(input, `throw 2`), res => res.outputFiles[0].text === 'throw 2;\n', ) assert.strictEqual(result2.errors.length, 0) assert.strictEqual(fs.existsSync(output), false) } // Second rebuild: edit { const result2 = await rebuildUntil( () => writeFileAtomic(input, `throw 3`), res => res.outputFiles[0].text === 'throw 3;\n', ) assert.strictEqual(result2.errors.length, 0) assert.strictEqual(fs.existsSync(output), false) } } finally { await context.dispose() } }, async watchMetafile({ 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, `foo()`) const { rebuildUntil, plugin } = makeRebuildUntilPlugin() const context = await esbuild.context({ entryPoints: [input], outdir, format: 'esm', logLevel: 'silent', metafile: true, plugins: [plugin], }) try { const result = await rebuildUntil( () => context.watch(), () => true, ) assert.strictEqual(result.errors.length, 0) const relInput = path.relative(process.cwd(), input).split(path.sep).join('/') assert.strictEqual(result.metafile.inputs[relInput].bytes, 5) assert.strictEqual(await readFileAsync(output, 'utf8'), 'foo();\n') // Rebuild and check that the metafile has been updated { const result2 = await rebuildUntil( () => writeFileAtomic(input, `foo(123)`), () => fs.readFileSync(output, 'utf8') === 'foo(123);\n', ) assert.strictEqual(result2.errors.length, 0) assert.strictEqual(result2.metafile.inputs[relInput].bytes, 8) } } finally { await context.dispose() } }, async watchMangleCache({ 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, `foo()`) const { rebuildUntil, plugin } = makeRebuildUntilPlugin() const context = await esbuild.context({ entryPoints: [input], outdir, format: 'esm', logLevel: 'silent', mangleProps: /./, mangleCache: {}, plugins: [plugin], }) try { const result = await rebuildUntil( () => context.watch(), () => true, ) assert.strictEqual(result.errors.length, 0) assert.strictEqual(JSON.stringify(result.mangleCache), '{}') // Rebuild and check that the mangle cache has been updated { const result2 = await rebuildUntil( () => writeFileAtomic(input, `foo(bar.baz)`), () => fs.readFileSync(output, 'utf8') === 'foo(bar.a);\n', ) assert.strictEqual(result2.errors.length, 0) assert.strictEqual(JSON.stringify(result2.mangleCache), '{"baz":"a"}') } } finally { await context.dispose() } }, // See: https://github.com/evanw/esbuild/issues/3062 async watchNodePaths({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const outfile = path.join(testDir, 'out.js') const libDir = path.join(testDir, 'lib') const libFile = path.join(libDir, 'foo.js') await mkdirAsync(libDir, { recursive: true }) await writeFileAsync(input, ` import { foo } from ${JSON.stringify(path.basename(libFile))} console.log(foo) `) const { rebuildUntil, plugin } = makeRebuildUntilPlugin() const context = await esbuild.context({ entryPoints: [input], outfile, write: false, bundle: true, minifyWhitespace: true, format: 'esm', logLevel: 'silent', plugins: [plugin], nodePaths: [libDir], }) try { const result = await rebuildUntil( () => { context.watch() writeFileAtomic(libFile, `export let foo = 0`) }, result => result.outputFiles.length === 1, ) assert.strictEqual(result.outputFiles[0].text, `var foo=0;console.log(foo);\n`) // Make sure watch mode works for files imported via NODE_PATH for (let i = 1; i <= 3; i++) { const result2 = await rebuildUntil( () => writeFileAtomic(libFile, `export let foo = ${i}`), result => result.outputFiles.length === 1 && result.outputFiles[0].text.includes(i), ) assert.strictEqual(result2.outputFiles[0].text, `var foo=${i};console.log(foo);\n`) } } finally { await context.dispose() } }, async watchDelay({ 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`) const { rebuildUntil, plugin } = makeRebuildUntilPlugin() const context = await esbuild.context({ entryPoints: [input], outdir, format: 'esm', logLevel: 'silent', plugins: [plugin], }) try { const delay = 2000 const result = await rebuildUntil( () => context.watch({ delay }), () => true, ) assert.strictEqual(result.errors.length, 0) assert.strictEqual(await readFileAsync(output, 'utf8'), 'throw 1;\n') // Edit a file const startTime = Date.now() const editPromise = rebuildUntil( () => writeFileAtomic(input, `throw 2`), () => fs.readFileSync(output, 'utf8') === 'throw 2;\n', ) // Wait for half the delay time await new Promise(resolve => setTimeout(resolve, delay / 2)) // The rebuild should not have happened yet (check synchronously) if (Date.now() - startTime < delay) { assert.strictEqual(fs.readFileSync(output, 'utf8'), 'throw 1;\n') } else { // To avoid a flaky test, don't check and assert if the CPU is busy and we missed our window } // Wait for the rebuild to happen const result2 = await editPromise assert.strictEqual(result2.errors.length, 0) } finally { await context.dispose() } }, } let serveTests = { async serveTwice({ esbuild }) { const context = await esbuild.context({}) try { // Serve once await context.serve() // Serve twice try { await context.serve() throw new Error('Expected an error to be thrown') } catch (err) { assert.strictEqual(err.message, 'Serve mode has already been enabled') } } finally { await context.dispose() } }, async serveWatch({ esbuild, testDir }) { const input = path.join(testDir, 'in.ts') const outfile = path.join(testDir, 'out.js') const { rebuildUntil, plugin } = makeRebuildUntilPlugin() const context = await esbuild.context({ entryPoints: [input], outfile, logLevel: 'silent', plugins: [plugin], }) try { await context.watch() const server = await context.serve({ host: '127.0.0.1', }) // Try fetching the non-existent file try { await fetch(server.hosts[0], server.port, '/' + path.basename(outfile)) throw new Error('Expected an error to be thrown') } catch (err) { if (err.statusCode !== 503) throw err } // Check that watch mode works for (let i = 0; i < 5; i++) { const result = await rebuildUntil( () => writeFileAtomic(input, `throw ${i}`), () => fs.readFileSync(outfile, 'utf8') === `throw ${i};\n`, ) assert.strictEqual(result.errors.length, 0) assert.strictEqual(fs.readFileSync(outfile, 'utf8'), `throw ${i};\n`) } // Try fetching the file now const data = await fetch(server.hosts[0], server.port, '/' + path.basename(outfile)) assert.strictEqual(data.toString(), 'throw 4;\n') } finally { await context.dispose() } }, async serveBasic({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') await writeFileAsync(input, `console.log(123)`) let onRequest; const context = await esbuild.context({ entryPoints: [input], format: 'esm', outdir: testDir, write: false, }); try { const result = await context.serve({ host: '127.0.0.1', onRequest: args => onRequest(args), }) assert.deepStrictEqual(result.hosts, ['127.0.0.1']); assert.strictEqual(typeof result.port, 'number'); // GET /in.js { const singleRequestPromise = new Promise(resolve => { onRequest = resolve }); const buffer = await fetch(result.hosts[0], result.port, '/in.js') assert.strictEqual(buffer.toString(), `console.log(123);\n`); assert.strictEqual(fs.readFileSync(input, 'utf8'), `console.log(123)`) 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'); } // HEAD /in.js { const singleRequestPromise = new Promise(resolve => { onRequest = resolve }); const buffer = await fetch(result.hosts[0], result.port, '/in.js', { method: 'HEAD' }) assert.strictEqual(buffer.toString(), ``); // HEAD omits the content assert.strictEqual(fs.readFileSync(input, 'utf8'), `console.log(123)`) let singleRequest = await singleRequestPromise; assert.strictEqual(singleRequest.method, 'HEAD'); assert.strictEqual(singleRequest.path, '/in.js'); assert.strictEqual(singleRequest.status, 200); assert.strictEqual(typeof singleRequest.remoteAddress, 'string'); assert.strictEqual(typeof singleRequest.timeInMS, 'number'); } } finally { await context.dispose(); } }, async serveBasicHTTPS({ esbuild, testDir }) { const run = command => new Promise((resolve, reject) => { child_process.execFile(command.shift(), command, (error, stdout, stderr) => { if (error) reject(error) else resolve() }) }) try { await run(['which', 'openssl']) } catch { console.warn('Skipping HTTPS tests because the "openssl" command was not found') return } const keyfile = path.join(testDir, 'key.pem') const certfile = path.join(testDir, 'cert.pem') await run(['openssl', 'req', '-x509', '-newkey', 'rsa:4096', '-keyout', keyfile, '-out', certfile, '-days', '9999', '-nodes', '-subj', '/CN=127.0.0.1']) const input = path.join(testDir, 'in.js') await writeFileAsync(input, `console.log(123)`) let onRequest; let singleRequestPromise = new Promise(resolve => { onRequest = resolve; }); const context = await esbuild.context({ entryPoints: [input], format: 'esm', outdir: testDir, write: false, }); try { const result = await context.serve({ host: '127.0.0.1', keyfile, certfile, onRequest, }) assert.deepStrictEqual(result.hosts, ['127.0.0.1']); assert.strictEqual(typeof result.port, 'number'); const buffer = await fetchHTTPS(result.hosts[0], result.port, '/in.js', { certfile }) assert.strictEqual(buffer.toString(), `console.log(123);\n`); assert.strictEqual(fs.readFileSync(input, 'utf8'), `console.log(123)`) 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'); } finally { await context.dispose(); } }, async serveSlashRedirect({ esbuild, testDir }) { const nestedDir = path.join(testDir, 'nested', 'dir') const index = path.join(nestedDir, 'index.html') await mkdirAsync(nestedDir, { recursive: true }) await writeFileAsync(index, ``) const context = await esbuild.context({}); try { const result = await context.serve({ host: '127.0.0.1', servedir: testDir, }) assert.deepStrictEqual(result.hosts, ['127.0.0.1']); assert.strictEqual(typeof result.port, 'number'); // With a trailing slash { const buffer = await fetch(result.hosts[0], result.port, '/nested/dir/index.html') assert.strictEqual(buffer.toString(), ``) } { const buffer = await fetch(result.hosts[0], result.port, '/nested/dir/') assert.strictEqual(buffer.toString(), ``) } { const buffer = await fetch(result.hosts[0], result.port, '/nested/./dir/') assert.strictEqual(buffer.toString(), ``) } { const buffer = await fetch(result.hosts[0], result.port, '/./nested/./dir/./') assert.strictEqual(buffer.toString(), ``) } { const buffer = await fetch(result.hosts[0], result.port, '/nested/dir//') assert.strictEqual(buffer.toString(), ``) } { const buffer = await fetch(result.hosts[0], result.port, '/nested//dir/') assert.strictEqual(buffer.toString(), ``) } // Without a trailing slash { const res = await partialFetch(result.hosts[0], result.port, '/nested') assert.strictEqual(res.statusCode, 302) assert.strictEqual(res.headers.location, '/nested/') } { const res = await partialFetch(result.hosts[0], result.port, '/nested/dir') assert.strictEqual(res.statusCode, 302) assert.strictEqual(res.headers.location, '/nested/dir/') } { const res = await partialFetch(result.hosts[0], result.port, '/nested//dir') assert.strictEqual(res.statusCode, 302) assert.strictEqual(res.headers.location, '/nested/dir/') } // With leading double slashes (looks like a protocol-relative URL) { const res = await partialFetch(result.hosts[0], result.port, '//nested') assert.strictEqual(res.statusCode, 302) assert.strictEqual(res.headers.location, '/nested/') } { const res = await partialFetch(result.hosts[0], result.port, '//nested/dir') assert.strictEqual(res.statusCode, 302) assert.strictEqual(res.headers.location, '/nested/dir/') } { const res = await partialFetch(result.hosts[0], result.port, '//nested//dir') assert.strictEqual(res.statusCode, 302) assert.strictEqual(res.headers.location, '/nested/dir/') } } finally { await context.dispose(); } }, async serveOutfile({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const outfile = path.join(testDir, 'out.js') await writeFileAsync(input, `console.log(123)`) let onRequest; let singleRequestPromise = new Promise(resolve => { onRequest = resolve; }); const context = await esbuild.context({ entryPoints: [input], format: 'esm', outfile, }); try { const result = await context.serve({ host: '127.0.0.1', onRequest, }) assert.deepStrictEqual(result.hosts, ['127.0.0.1']); assert.strictEqual(typeof result.port, 'number'); const buffer = await fetch(result.hosts[0], result.port, '/out.js') assert.strictEqual(buffer.toString(), `console.log(123);\n`); 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'); try { await fetch(result.hosts[0], 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 } } finally { await context.dispose(); } }, async serveWithServedir({ 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 context = await esbuild.context({ entryPoints: [input], format: 'esm', outdir: wwwDir, write: false, }); try { const result = await context.serve({ host: '127.0.0.1', onRequest: args => onRequest(args), servedir: wwwDir, }) assert.deepStrictEqual(result.hosts, ['127.0.0.1']); assert.strictEqual(typeof result.port, 'number'); let promise, buffer, req; promise = nextRequestPromise; buffer = await fetch(result.hosts[0], result.port, '/in.js') assert.strictEqual(buffer.toString(), `console.log(123);\n`); assert.strictEqual(fs.existsSync(path.join(wwwDir, path.basename(input))), false); 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.hosts[0], 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'); } finally { await context.dispose(); } }, async serveWithServedirAndSiblingOutputDir({ esbuild, testDir }) { const context = await esbuild.context({ entryPoints: [path.join(testDir, 'in.js')], outdir: 'out', }); try { await context.serve({ servedir: 'www', }); throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e.message, `Output directory "out" must be contained in serve directory "www"`) } finally { await context.dispose(); } }, async serveWithServedirAndParentOutputDir({ esbuild, testDir }) { const context = await esbuild.context({ entryPoints: [path.join(testDir, 'in.js')], outdir: testDir, absWorkingDir: testDir, }); try { await context.serve({ servedir: path.join(testDir, 'www'), }) throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e.message, `Output directory "." must be contained in serve directory "www"`) } finally { await context.dispose(); } }, async serveWithServedirAndOutputDir({ 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 context = await esbuild.context({ entryPoints: [input], format: 'esm', outdir: outputDir, }) try { const result = await context.serve({ host: '127.0.0.1', onRequest: args => onRequest(args), servedir: wwwDir, }) assert.deepStrictEqual(result.hosts, ['127.0.0.1']); assert.strictEqual(typeof result.port, 'number'); let promise, buffer, req; promise = nextRequestPromise; buffer = await fetch(result.hosts[0], 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.hosts[0], 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'); // Make sure the output directory prefix requires a slash separator promise = nextRequestPromise; try { await fetch(result.hosts[0], result.port, '/outin.js') throw new Error('Expected an error to be thrown') } catch (err) { if (err.statusCode !== 404) throw err } req = await promise; assert.strictEqual(req.method, 'GET'); assert.strictEqual(req.path, '/outin.js'); assert.strictEqual(req.status, 404); assert.strictEqual(typeof req.remoteAddress, 'string'); assert.strictEqual(typeof req.timeInMS, 'number'); } finally { await context.dispose(); } }, async serveWithServedirNoEntryPoints({ 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 context = await esbuild.context({}); try { const result = await context.serve({ host: '127.0.0.1', onRequest: args => onRequest(args), servedir: testDir, }) assert.deepStrictEqual(result.hosts, ['127.0.0.1']); assert.strictEqual(typeof result.port, 'number'); let promise, buffer, req; promise = nextRequestPromise; buffer = await fetch(result.hosts[0], 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.hosts[0], result.port, '/') assert.notStrictEqual(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'); } finally { await context.dispose(); } }, async serveRange({ esbuild, testDir }) { const big = path.join(testDir, 'big.txt') const byteCount = 16 * 1024 * 1024 const buffer = require('crypto').randomBytes(byteCount) await writeFileAsync(big, buffer) const context = await esbuild.context({}); try { const result = await context.serve({ host: '127.0.0.1', servedir: testDir, }) // Test small to big ranges const minLength = 1 const maxLength = buffer.length for (let i = 0, n = 16; i < n; i++) { const length = Math.round(minLength + (maxLength - minLength) * i / (n - 1)) const start = Math.floor(Math.random() * (buffer.length - length)) const headers = { // Subtract 1 because range headers are inclusive on both ends Range: `bytes=${start}-${start + length - 1}`, } const fetched = await fetch(result.hosts[0], result.port, '/big.txt', { headers }) delete fetched.headers.date // This changes every time delete fetched.headers.connection // Node v19+ no longer sends this const expected = buffer.slice(start, start + length) expected.headers = { 'content-length': `${length}`, 'content-range': `bytes ${start}-${start + length - 1}/${byteCount}`, 'content-type': 'application/octet-stream', } assert.deepStrictEqual(fetched, expected) } } finally { await context.dispose(); } }, async serveWatchCopyLoaderWithEntryPoint({ esbuild, testDir }) { const ts = path.join(testDir, 'script.ts') const html = path.join(testDir, 'index.html') const outdir = path.join(testDir, 'out') await writeFileAsync(ts, `console.log(123)`) await writeFileAsync(html, ``) const context = await esbuild.context({ entryPoints: [ts, html], outdir, loader: { '.html': 'copy' }, }); try { const server = await context.serve({ host: '127.0.0.1', servedir: testDir, }) await context.watch() const js = await fetch(server.hosts[0], server.port, '/out/script.js') assert.strictEqual(js.toString(), `console.log(123);\n`); const explicitHTML = await fetch(server.hosts[0], server.port, '/out/index.html') assert.strictEqual(explicitHTML.toString(), ``); // The server should support implicit "index.html" extensions on entry point files const implicitHTML = await fetch(server.hosts[0], server.port, '/out/') assert.strictEqual(implicitHTML.toString(), ``); // Make a change to the HTML await writeFileAsync(html, ``) // Wait for watch mode to rebuild const start = Date.now() while (true) { if (Date.now() - start > 30 * 1000) throw new Error('Timeout after 30 seconds') const toCheck = fs.readFileSync(path.join(outdir, 'index.html'), 'utf8') if (toCheck === ``) break } } finally { await context.dispose(); } }, async serveWithoutServedirWatchLiveReload({ esbuild, testDir }) { const js = path.join(testDir, 'app.js') const css = path.join(testDir, 'app.css') const outdir = path.join(testDir, 'out') await writeFileAsync(css, ``) let endPromise const context = await esbuild.context({ entryPoints: [js], outdir, bundle: true, logLevel: 'silent', }); try { const server = await context.serve({ host: '127.0.0.1', }) const stream = await makeEventStream(server.hosts[0], server.port, '/esbuild') await context.rebuild().then( () => Promise.reject(new Error('Expected an error to be thrown')), () => { /* Ignore the build error due to the missing JS file */ }, ) // Event 1: a new JavaScript file var eventPromise = stream.waitFor('change') await writeFileAsync(js, ``) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: ['/app.js'], removed: [], updated: [] }) // Event 2: edit the JavaScript file var eventPromise = stream.waitFor('change') await writeFileAsync(js, `foo()`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: [], removed: [], updated: ['/app.js'] }) // Event 3: a new CSS file var eventPromise = stream.waitFor('change') await writeFileAsync(js, `import "./app.css"; foo()`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: ['/app.css'], removed: [], updated: [] }) // Event 4: edit the CSS file var eventPromise = stream.waitFor('change') await writeFileAsync(css, `a { color: red }`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: [], removed: [], updated: ['/app.css'] }) // Event 5: remove the CSS file var eventPromise = stream.waitFor('change') await writeFileAsync(js, `bar()`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: [], removed: ['/app.css'], updated: ['/app.js'] }) // Wait for the stream to end once we call "dispose()" below endPromise = stream.waitFor('close') } finally { await context.dispose(); } // This stream should end once "dispose()" is called above await endPromise }, async serveWithServedirWatchLiveReload({ esbuild, testDir }) { const js = path.join(testDir, 'app.js') const css = path.join(testDir, 'app.css') const outdir = path.join(testDir, 'out') await writeFileAsync(css, ``) let endPromise const context = await esbuild.context({ entryPoints: [js], outdir, bundle: true, logLevel: 'silent', }); try { const server = await context.serve({ host: '127.0.0.1', servedir: testDir, }) const stream = await makeEventStream(server.hosts[0], server.port, '/esbuild') await context.rebuild().then( () => Promise.reject(new Error('Expected an error to be thrown')), () => { /* Ignore the build error due to the missing JS file */ }, ) // Event 1: a new JavaScript file var eventPromise = stream.waitFor('change') await writeFileAsync(js, ``) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: ['/out/app.js'], removed: [], updated: [] }) // Event 2: edit the JavaScript file var eventPromise = stream.waitFor('change') await writeFileAsync(js, `foo()`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: [], removed: [], updated: ['/out/app.js'] }) // Event 3: a new CSS file var eventPromise = stream.waitFor('change') await writeFileAsync(js, `import "./app.css"; foo()`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: ['/out/app.css'], removed: [], updated: [] }) // Event 4: edit the CSS file var eventPromise = stream.waitFor('change') await writeFileAsync(css, `a { color: red }`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: [], removed: [], updated: ['/out/app.css'] }) // Event 5: remove the CSS file var eventPromise = stream.waitFor('change') await writeFileAsync(js, `bar()`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: [], removed: ['/out/app.css'], updated: ['/out/app.js'] }) // Wait for the stream to end once we call "dispose()" below endPromise = stream.waitFor('close') } finally { await context.dispose(); } // This stream should end once "dispose()" is called above await endPromise }, async serveWithoutServedirWatchLiveReloadPublicPath({ esbuild, testDir }) { const js = path.join(testDir, 'app.js') const css = path.join(testDir, 'app.css') const outdir = path.join(testDir, 'out') await writeFileAsync(css, ``) let endPromise const context = await esbuild.context({ entryPoints: [js], outdir, bundle: true, logLevel: 'silent', publicPath: 'http://example.com/about', }); try { const server = await context.serve({ host: '127.0.0.1', }) const stream = await makeEventStream(server.hosts[0], server.port, '/esbuild') await context.rebuild().then( () => Promise.reject(new Error('Expected an error to be thrown')), () => { /* Ignore the build error due to the missing JS file */ }, ) // Event 1: a new JavaScript file var eventPromise = stream.waitFor('change') await writeFileAsync(js, ``) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: ['http://example.com/about/app.js'], removed: [], updated: [] }) // Event 2: edit the JavaScript file var eventPromise = stream.waitFor('change') await writeFileAsync(js, `foo()`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: [], removed: [], updated: ['http://example.com/about/app.js'] }) // Event 3: a new CSS file var eventPromise = stream.waitFor('change') await writeFileAsync(js, `import "./app.css"; foo()`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: ['http://example.com/about/app.css'], removed: [], updated: [] }) // Event 4: edit the CSS file var eventPromise = stream.waitFor('change') await writeFileAsync(css, `a { color: red }`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: [], removed: [], updated: ['http://example.com/about/app.css'] }) // Event 5: remove the CSS file var eventPromise = stream.waitFor('change') await writeFileAsync(js, `bar()`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: [], removed: ['http://example.com/about/app.css'], updated: ['http://example.com/about/app.js'] }) // Wait for the stream to end once we call "dispose()" below endPromise = stream.waitFor('close') } finally { await context.dispose(); } // This stream should end once "dispose()" is called above await endPromise }, async serveWithServedirWatchLiveReloadPublicPath({ esbuild, testDir }) { const js = path.join(testDir, 'app.js') const css = path.join(testDir, 'app.css') const outdir = path.join(testDir, 'out') await writeFileAsync(css, ``) let endPromise const context = await esbuild.context({ entryPoints: [js], outdir, bundle: true, logLevel: 'silent', publicPath: 'http://example.com/about', }); try { const server = await context.serve({ host: '127.0.0.1', servedir: testDir, }) const stream = await makeEventStream(server.hosts[0], server.port, '/esbuild') await context.rebuild().then( () => Promise.reject(new Error('Expected an error to be thrown')), () => { /* Ignore the build error due to the missing JS file */ }, ) // Event 1: a new JavaScript file var eventPromise = stream.waitFor('change') await writeFileAsync(js, ``) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: ['http://example.com/about/out/app.js'], removed: [], updated: [] }) // Event 2: edit the JavaScript file var eventPromise = stream.waitFor('change') await writeFileAsync(js, `foo()`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: [], removed: [], updated: ['http://example.com/about/out/app.js'] }) // Event 3: a new CSS file var eventPromise = stream.waitFor('change') await writeFileAsync(js, `import "./app.css"; foo()`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: ['http://example.com/about/out/app.css'], removed: [], updated: [] }) // Event 4: edit the CSS file var eventPromise = stream.waitFor('change') await writeFileAsync(css, `a { color: red }`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: [], removed: [], updated: ['http://example.com/about/out/app.css'] }) // Event 5: remove the CSS file var eventPromise = stream.waitFor('change') await writeFileAsync(js, `bar()`) await context.rebuild() var data = JSON.parse((await eventPromise).data) assert.deepStrictEqual(data, { added: [], removed: ['http://example.com/about/out/app.css'], updated: ['http://example.com/about/out/app.js'] }) // Wait for the stream to end once we call "dispose()" below endPromise = stream.waitFor('close') } finally { await context.dispose(); } // This stream should end once "dispose()" is called above await endPromise }, async serveWithFallback({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const wwwDir = path.join(testDir, 'www') const index = path.join(wwwDir, 'app', 'index.html') const fallback = path.join(testDir, 'fallback.html') await mkdirAsync(path.dirname(index), { recursive: true }) await writeFileAsync(input, `console.log(123)`) await writeFileAsync(index, `

index

`) await writeFileAsync(fallback, `

fallback

`) const context = await esbuild.context({ entryPoints: [input], format: 'esm', outdir: wwwDir, write: false, }); try { const result = await context.serve({ host: '127.0.0.1', servedir: wwwDir, fallback, }) assert.deepStrictEqual(result.hosts, ['127.0.0.1']); assert.strictEqual(typeof result.port, 'number'); let buffer; buffer = await fetch(result.hosts[0], result.port, '/in.js') assert.strictEqual(buffer.toString(), `console.log(123);\n`); buffer = await fetch(result.hosts[0], result.port, '/') assert.strictEqual(buffer.toString(), `

fallback

`); buffer = await fetch(result.hosts[0], result.port, '/app/') assert.strictEqual(buffer.toString(), `

index

`); buffer = await fetch(result.hosts[0], result.port, '/app/?foo') assert.strictEqual(buffer.toString(), `

index

`); buffer = await fetch(result.hosts[0], result.port, '/app/foo') assert.strictEqual(buffer.toString(), `

fallback

`); } finally { await context.dispose(); } }, async serveHostCheckIPv4({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') await writeFileAsync(input, `console.log(123)`) let onRequest; const context = await esbuild.context({ entryPoints: [input], format: 'esm', outdir: testDir, write: false, }); try { const result = await context.serve({ port: 0, onRequest: args => onRequest(args), }) assert(result.hosts.includes('127.0.0.1')); assert.strictEqual(typeof result.port, 'number'); // GET /in.js from each host for (const host of result.hosts) { const singleRequestPromise = new Promise(resolve => { onRequest = resolve }); const buffer = await fetch(host, result.port, '/in.js') assert.strictEqual(buffer.toString(), `console.log(123);\n`); assert.strictEqual(fs.readFileSync(input, 'utf8'), `console.log(123)`) 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'); } // GET /in.js with a forbidden host header const forbiddenHosts = [ 'evil.com', 'evil.com:666', '1.2.3.4', '1.2.3.4:666', '::1234', '[::1234]:666', '[', ] for (const forbiddenHost of forbiddenHosts) { const singleRequestPromise = new Promise(resolve => { onRequest = resolve }); try { await fetch(result.hosts[0], result.port, '/in.js', { headers: { Host: forbiddenHost } }) } catch { } let singleRequest = await singleRequestPromise; assert.strictEqual(singleRequest.method, 'GET'); assert.strictEqual(singleRequest.path, '/in.js'); assert.strictEqual(singleRequest.status, 403, forbiddenHost); // 403 means "Forbidden" assert.strictEqual(typeof singleRequest.remoteAddress, 'string'); assert.strictEqual(typeof singleRequest.timeInMS, 'number'); } } finally { await context.dispose(); } }, async serveHostCheckIPv6({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') await writeFileAsync(input, `console.log(123)`) let onRequest; const context = await esbuild.context({ entryPoints: [input], format: 'esm', outdir: testDir, write: false, }); try { const result = await context.serve({ host: '::', port: 0, onRequest: args => onRequest(args), }) assert(result.hosts.includes('::1')); assert.strictEqual(typeof result.port, 'number'); // GET /in.js from each host for (const host of result.hosts) { const singleRequestPromise = new Promise(resolve => { onRequest = resolve }); const buffer = await fetch(host, result.port, '/in.js') assert.strictEqual(buffer.toString(), `console.log(123);\n`); assert.strictEqual(fs.readFileSync(input, 'utf8'), `console.log(123)`) 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'); } // GET /in.js with a forbidden host header const forbiddenHosts = [ 'evil.com', 'evil.com:666', '1.2.3.4', '1.2.3.4:666', '::1234', '[::1234]:666', '[', ] for (const forbiddenHost of forbiddenHosts) { const singleRequestPromise = new Promise(resolve => { onRequest = resolve }); try { await fetch(result.hosts[0], result.port, '/in.js', { headers: { Host: forbiddenHost } }) } catch { } let singleRequest = await singleRequestPromise; assert.strictEqual(singleRequest.method, 'GET'); assert.strictEqual(singleRequest.path, '/in.js'); assert.strictEqual(singleRequest.status, 403, forbiddenHost); // 403 means "Forbidden" assert.strictEqual(typeof singleRequest.remoteAddress, 'string'); assert.strictEqual(typeof singleRequest.timeInMS, 'number'); } } finally { await context.dispose(); } }, async serveCORSNoOrigins({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') await writeFileAsync(input, `console.log(123)`) const context = await esbuild.context({ entryPoints: [input], format: 'esm', outdir: testDir, write: false, }); try { const result = await context.serve({ port: 0, cors: {}, }) assert(result.hosts.length > 0); assert.strictEqual(typeof result.port, 'number'); // There should be no CORS header const origin = 'https://example.com' const buffer = await fetch(result.hosts[0], result.port, '/in.js', { headers: { Origin: origin } }) assert.strictEqual(buffer.toString(), `console.log(123);\n`); assert.strictEqual(fs.readFileSync(input, 'utf8'), `console.log(123)`) assert.strictEqual(buffer.headers['access-control-allow-origin'], undefined) } finally { await context.dispose(); } }, async serveCORSAllOrigins({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') await writeFileAsync(input, `console.log(123)`) const context = await esbuild.context({ entryPoints: [input], format: 'esm', outdir: testDir, write: false, }); try { const result = await context.serve({ port: 0, cors: { origin: '*' }, }) assert(result.hosts.length > 0); assert.strictEqual(typeof result.port, 'number'); // There should be a CORS header allowing all origins const origin = 'https://example.com' const buffer = await fetch(result.hosts[0], result.port, '/in.js', { headers: { Origin: origin } }) assert.strictEqual(buffer.toString(), `console.log(123);\n`); assert.strictEqual(fs.readFileSync(input, 'utf8'), `console.log(123)`) assert.strictEqual(buffer.headers['access-control-allow-origin'], '*') } finally { await context.dispose(); } }, async serveCORSSpecificOrigins({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') await writeFileAsync(input, `console.log(123)`) const context = await esbuild.context({ entryPoints: [input], format: 'esm', outdir: testDir, write: false, }); try { const result = await context.serve({ port: 0, cors: { origin: [ 'http://example.com', 'http://foo.example.com', 'https://*.example.com', ], }, }) assert(result.hosts.length > 0); assert.strictEqual(typeof result.port, 'number'); const allowedOrigins = [ 'http://example.com', 'http://foo.example.com', 'https://bar.example.com', ] const forbiddenOrigins = [ 'http://bar.example.com', 'https://example.com', 'http://evil.com', 'https://evil.com', ] // GET /in.js from each allowed origin for (const origin of allowedOrigins) { const buffer = await fetch(result.hosts[0], result.port, '/in.js', { headers: { Origin: origin } }) assert.strictEqual(buffer.toString(), `console.log(123);\n`); assert.strictEqual(fs.readFileSync(input, 'utf8'), `console.log(123)`) assert.strictEqual(buffer.headers['access-control-allow-origin'], origin) } // GET /in.js from each forbidden origin for (const origin of forbiddenOrigins) { const buffer = await fetch(result.hosts[0], result.port, '/in.js', { headers: { Origin: origin } }) assert.strictEqual(buffer.toString(), `console.log(123);\n`); assert.strictEqual(fs.readFileSync(input, 'utf8'), `console.log(123)`) assert.strictEqual(buffer.headers['access-control-allow-origin'], undefined) } } finally { await context.dispose(); } }, } 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(Object.create({ toString() { return '1+2' } })) throw new Error('Expected an error to be thrown'); } catch (e) { assert.strictEqual(e.errors ? e.errors[0].text : e + '', 'The input to "transform" must be a string or a Uint8Array') } }, 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 transformLoaderBase64({ esbuild }) { // UTF-16 var result = await esbuild.transform(`\xFF`, { loader: 'base64' }) assert.strictEqual(result.code, `module.exports = "w78=";\n`) // Binary var result = await esbuild.transform(new Uint8Array([0xFF]), { loader: 'base64' }) assert.strictEqual(result.code, `module.exports = "/w==";\n`) }, 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', tsconfigRaw: { compilerOptions: { useDefineForClassFields: false, experimentalDecorators: true, }, }, }) new Function(code)() }, async mangleQuotedTransform({ esbuild }) { var { code } = await esbuild.transform(`x.foo_ = 'foo_' in x`, { mangleProps: /_/, mangleQuoted: true, }) assert.strictEqual(code, 'x.a = "a" in x;\n') }, async mangleCacheTransform({ esbuild }) { var { code, mangleCache } = await esbuild.transform(`x = { x_: 0, y_: 1, z_: 2 }`, { mangleProps: /_/, mangleCache: { x_: 'FIXED', z_: false }, }) assert.strictEqual(code, 'x = { FIXED: 0, a: 1, z_: 2 };\n') assert.deepStrictEqual(mangleCache, { x_: 'FIXED', y_: 'a', z_: false }) }, 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 }) { for (const loader of ['css', 'local-css', 'global-css']) { var { code } = await esbuild.transform(` div { color: red } `, { loader, 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 tsconfigRawImportsNotUsedAsValuesDefault({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: {}, }, loader: 'ts', }) assert.strictEqual(code, ``) }, async tsconfigRawImportsNotUsedAsValuesRemove({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: { importsNotUsedAsValues: 'remove', }, }, loader: 'ts', }) assert.strictEqual(code, ``) }, async tsconfigRawImportsNotUsedAsValuesPreserve({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: { importsNotUsedAsValues: 'preserve', }, }, loader: 'ts', }) assert.strictEqual(code, `import "path";\n`) }, async tsconfigRawImportsNotUsedAsValuesError({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: { importsNotUsedAsValues: 'error', }, }, loader: 'ts', }) assert.strictEqual(code, `import "path";\n`) }, async tsconfigRawImportsNotUsedAsValuesRemoveJS({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: { importsNotUsedAsValues: 'remove', }, }, }) assert.strictEqual(code, `import { T } from "path";\n`) }, async tsconfigRawImportsNotUsedAsValuesPreserveJS({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: { importsNotUsedAsValues: 'preserve', }, }, }) assert.strictEqual(code, `import { T } from "path";\n`) }, async tsconfigRawPreserveValueImportsDefault({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: {}, }, loader: 'ts', }) assert.strictEqual(code, ``) }, async tsconfigRawPreserveValueImportsFalse({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: { preserveValueImports: false, }, }, loader: 'ts', }) assert.strictEqual(code, ``) }, async tsconfigRawPreserveValueImportsTrue({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: { preserveValueImports: true, }, }, loader: 'ts', }) assert.strictEqual(code, `import { T } from "path";\n`) }, async tsconfigRawPreserveValueImportsTrueMinifyIdentifiers({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: { preserveValueImports: true, }, }, loader: 'ts', minifyIdentifiers: true, }) assert.strictEqual(code, `import "path";\n`) }, async tsconfigRawPreserveValueImportsTrueImportsNotUsedAsValuesRemove({ esbuild }) { const { code } = await esbuild.transform(`import {T} from 'path'`, { tsconfigRaw: { compilerOptions: { importsNotUsedAsValues: 'remove', preserveValueImports: true, }, }, loader: 'ts', }) 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": { "preserveValueImports": true, // there is a trailing comment here }, }`, loader: 'ts', }) assert.strictEqual(code5, `import { T } from "path";\n`) }, async tsconfigRawUseDefineForClassFields({ 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 tsconfigRawAlwaysStrict({ esbuild }) { const input = `console.log(123)` assert.strictEqual((await esbuild.transform(input, { loader: 'ts', tsconfigRaw: { compilerOptions: { strict: false } } })).code, `console.log(123);\n`) assert.strictEqual((await esbuild.transform(input, { loader: 'ts', tsconfigRaw: { compilerOptions: { alwaysStrict: false } } })).code, `console.log(123);\n`) assert.strictEqual((await esbuild.transform(input, { loader: 'ts', tsconfigRaw: { compilerOptions: { alwaysStrict: false, strict: true } } })).code, `console.log(123);\n`) assert.strictEqual((await esbuild.transform(input, { loader: 'ts', tsconfigRaw: { compilerOptions: { strict: true } } })).code, `"use strict";\nconsole.log(123);\n`) assert.strictEqual((await esbuild.transform(input, { loader: 'ts', tsconfigRaw: { compilerOptions: { alwaysStrict: true } } })).code, `"use strict";\nconsole.log(123);\n`) assert.strictEqual((await esbuild.transform(input, { loader: 'ts', tsconfigRaw: { compilerOptions: { alwaysStrict: true, strict: false } } })).code, `"use strict";\nconsole.log(123);\n`) }, async tsconfigRawTarget({ esbuild }) { // The "target" from "tsconfig.json" should not apply, but esbuild's "target" should apply assert.strictEqual((await esbuild.transform('x => x', { loader: 'ts', tsconfigRaw: { compilerOptions: { target: 'ES6' } } })).code, `(x) => x;\n`) assert.strictEqual((await esbuild.transform('x => x', { loader: 'ts', tsconfigRaw: { compilerOptions: { target: 'ES5' } } })).code, `(x) => x;\n`) assert.strictEqual((await esbuild.transform('x => x', { loader: 'ts', target: 'es6' })).code, `(x) => x;\n`) assert.strictEqual((await esbuild.transform('x => x', { loader: 'ts', target: 'es5' })).code, `(function(x) {\n return x;\n});\n`) assert.strictEqual((await esbuild.transform('x => x', { loader: 'ts', target: 'es5', tsconfigRaw: { compilerOptions: { target: 'ES6' } } })).code, `(function(x) {\n return x;\n});\n`) assert.strictEqual((await esbuild.transform('x => x', { loader: 'ts', target: 'es6', tsconfigRaw: { compilerOptions: { target: 'ES5' } } })).code, `(x) => x;\n`) }, async tsconfigRawExtends({ esbuild }) { // Uses of "extends" in "tsconfigRaw" should be ignored for the transform API const result = await esbuild.transform('let foo: bar', { loader: 'ts', logOverride: { 'tsconfig.json': 'error' }, tsconfigRaw: { extends: __filename, }, }) assert.strictEqual(result.code, `let foo;\n`) }, async tsImplicitUseDefineForClassFields({ esbuild }) { var { code } = await esbuild.transform(`class Foo { foo }`, { loader: 'ts', }) assert.strictEqual(code, `class Foo {\n foo;\n}\n`) var { code } = await esbuild.transform(`class Foo { foo }`, { target: 'es2021', loader: 'ts', }) assert.strictEqual(code, `var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); class Foo { constructor() { __publicField(this, "foo"); } } `) var { code } = await esbuild.transform(`class Foo { foo }`, { target: 'es2022', loader: 'ts', }) assert.strictEqual(code, `class Foo {\n foo;\n}\n`) var { code } = await esbuild.transform(`class Foo { foo }`, { target: 'esnext', loader: 'ts', }) assert.strictEqual(code, `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`) const { code: code3 } = await esbuild.transform(`<>
`, { tsconfigRaw: { compilerOptions: { jsx: 'react-jsx' }, }, loader: 'jsx', }) assert.strictEqual(code3, `import { Fragment, jsx } from "react/jsx-runtime";\n/* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", {}) });\n`) const { code: code4 } = await esbuild.transform(`<>
`, { tsconfigRaw: { compilerOptions: { jsx: 'react-jsx', jsxImportSource: 'notreact' }, }, loader: 'jsx', }) assert.strictEqual(code4, `import { Fragment, jsx } from "notreact/jsx-runtime";\n/* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", {}) });\n`) const { code: code5 } = await esbuild.transform(`<>
`, { tsconfigRaw: { compilerOptions: { jsx: 'react-jsxdev' }, }, loader: 'jsx', }) assert.strictEqual(code5, `import { Fragment, jsxDEV } from "react/jsx-dev-runtime";\n/* @__PURE__ */ jsxDEV(Fragment, { children: /* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 3\n}, this) }, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n`) }, // Note: tree shaking is disabled when the output format isn't IIFE async treeShakingDefault({ esbuild }) { const { code } = await esbuild.transform(` var unused = 123 var used = 234 export { used } `, { loader: 'jsx', format: 'esm', treeShaking: undefined, }) assert.strictEqual(code, `var unused = 123;\nvar used = 234;\nexport {\n used\n};\n`) }, async treeShakingFalse({ esbuild }) { const { code } = await esbuild.transform(` var unused = 123 var used = 234 export { used } `, { loader: 'jsx', format: 'esm', treeShaking: false, }) assert.strictEqual(code, `var unused = 123;\nvar used = 234;\nexport {\n used\n};\n`) }, async treeShakingTrue({ esbuild }) { const { code } = await esbuild.transform(` var unused = 123 var used = 234 export { used } `, { loader: 'jsx', format: 'esm', treeShaking: true, }) assert.strictEqual(code, `var used = 234;\nexport {\n used\n};\n`) }, // Note: tree shaking is enabled when the output format is IIFE async treeShakingDefaultIIFE({ esbuild }) { const { code } = await esbuild.transform(`var unused = 123`, { loader: 'jsx', format: 'iife', treeShaking: undefined, }) assert.strictEqual(code, `(() => {\n})();\n`) }, async treeShakingFalseIIFE({ esbuild }) { const { code } = await esbuild.transform(`var unused = 123`, { loader: 'jsx', format: 'iife', treeShaking: false, }) assert.strictEqual(code, `(() => {\n var unused = 123;\n})();\n`) }, async treeShakingTrueIIFE({ esbuild }) { const { code } = await esbuild.transform(`var unused = 123`, { loader: 'jsx', format: 'iife', treeShaking: true, }) assert.strictEqual(code, `(() => {\n})();\n`) }, async ignoreAnnotationsDefault({ esbuild }) { const { code } = await esbuild.transform(`/* @__PURE__ */ fn();
`, { loader: 'jsx', minifySyntax: true, }) assert.strictEqual(code, ``) }, async ignoreAnnotationsFalse({ esbuild }) { const { code } = await esbuild.transform(`/* @__PURE__ */ fn();
`, { loader: 'jsx', minifySyntax: true, ignoreAnnotations: false, }) assert.strictEqual(code, ``) }, async ignoreAnnotationsTrue({ esbuild }) { const { code } = await esbuild.transform(`/* @__PURE__ */ fn();
`, { loader: 'jsx', minifySyntax: true, ignoreAnnotations: true, }) 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 cssSyntaxErrorWarning({ esbuild }) { const { code } = await esbuild.transform(`. {}`, { loader: 'css' }) assert.strictEqual(code, `. {\n}\n`) }, async cssSyntaxErrorWarningOverride({ esbuild }) { try { await esbuild.transform(`. {}`, { loader: 'css', logOverride: { 'css-syntax-error': 'error' } }) throw new Error('Expected a transform failure') } catch (e) { assert.strictEqual((e && e.message || e) + '', `Transform failed with 1 error:\n:1:1: ERROR: Expected identifier but found whitespace`) } }, 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 cssNestingExpansionLimitWithoutIs({ esbuild }) { const css = `a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{color:red}}}}}}}}}}}}}}}}}}}}` try { await esbuild.transform(css, { loader: 'css', target: 'safari1' }) throw new Error('Expected a transform failure') } catch (e) { assert.strictEqual(e.errors.length, 1) assert.strictEqual(e.errors[0].text, 'CSS nesting is causing too much expansion') } }, async cssNestingExpansionLimitWithIs({ esbuild }) { const css = `a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{color:red}}}}}}}}}}}}}}}}}}}}` try { await esbuild.transform(css, { loader: 'css', target: 'safari14' }) throw new Error('Expected a transform failure') } catch (e) { assert.strictEqual(e.errors.length, 1) assert.strictEqual(e.errors[0].text, 'CSS nesting is causing too much expansion') } }, 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 module = { exports: {} } new Function('module', 'exports', 'require', code)(module, module.exports, require) if (module.exports.exists !== fs.exists) throw 'fail' }, async es6_export_star_to_cjs({ esbuild }) { const { code } = await esbuild.transform(`export * from "fs"`, { format: 'cjs' }) const module = { exports: {} } new Function('module', 'exports', 'require', code)(module, module.exports, require) if (module.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 module = { exports: {} } new Function('module', 'exports', 'require', code)(module, module.exports, require) if (module.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) 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) 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 \\uD800\\uDC00"] ||= {})["\\uD800\\uDC00"] ||= {})["\\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 iifeGlobalNameUnicodeEscapeNoLogicalAssignment({ esbuild }) { const { code } = await esbuild.transform(`export default 123`, { format: 'iife', globalName: 'π["π 𐀀"].𐀀["𐀀 π"]', supported: { 'logical-assignment': false } }) 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"]["\\uD800\\uDC00"] = \\u03C0["\\u03C0 \\uD800\\uDC00"]["\\uD800\\uDC00"] || {}; \\u03C0["\\u03C0 \\uD800\\uDC00"]["\\uD800\\uDC00"]["\\uD800\\uDC00 \\u03C0"] = `) }, async iifeGlobalNameUnicodeNoEscapeNoLogicalAssignment({ esbuild }) { const { code } = await esbuild.transform(`export default 123`, { format: 'iife', globalName: 'π["π 𐀀"].𐀀["𐀀 π"]', supported: { 'logical-assignment': false }, 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 iifeGlobalNameThis({ esbuild }) { const { code } = await esbuild.transform(`export default 123`, { format: 'iife', globalName: 'this.foo.bar' }) const globals = {} vm.createContext(globals) vm.runInContext(code, globals) assert.strictEqual(globals.foo.bar.default, 123) assert.strictEqual(code.slice(0, code.indexOf('(() => {\n')), `(this.foo ||= {}).bar = `) }, async iifeGlobalNameImportMeta({ esbuild }) { const { code } = await esbuild.transform(`export default 123`, { format: 'iife', globalName: 'import.meta.foo.bar' }) const { default: import_meta } = await import('data:text/javascript,' + code + '\nexport default import.meta') assert.strictEqual(import_meta.foo.bar.default, 123) assert.strictEqual(code.slice(0, code.indexOf('(() => {\n')), `(import.meta.foo ||= {}).bar = `) }, async jsx({ esbuild }) { const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx' }) assert.strictEqual(code, `console.log(/* @__PURE__ */ React.createElement("div", null));\n`) }, async jsxTransform({ esbuild }) { const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsx: 'transform' }) assert.strictEqual(code, `console.log(/* @__PURE__ */ React.createElement("div", null));\n`) }, async jsxPreserve({ esbuild }) { const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsx: 'preserve' }) assert.strictEqual(code, `console.log(
);\n`) }, async jsxRuntimeAutomatic({ esbuild }) { const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsx: 'automatic' }) assert.strictEqual(code, `import { jsx } from "react/jsx-runtime";\nconsole.log(/* @__PURE__ */ jsx("div", {}));\n`) }, async jsxDev({ esbuild }) { const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsx: 'automatic', jsxDev: true }) assert.strictEqual(code, `import { jsxDEV } from "react/jsx-dev-runtime";\nconsole.log(/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 13\n}, this));\n`) }, async jsxImportSource({ esbuild }) { const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsx: 'automatic', jsxImportSource: 'notreact' }) assert.strictEqual(code, `import { jsx } from "notreact/jsx-runtime";\nconsole.log(/* @__PURE__ */ jsx("div", {}));\n`) }, async jsxSideEffects({ esbuild }) { const { code } = await esbuild.transform(``, { loader: 'jsx', jsxSideEffects: true }) assert.strictEqual(code, `React.createElement("b", null);\n`) }, async ts({ esbuild }) { const { code } = await esbuild.transform(`enum Foo { FOO }`, { loader: 'ts' }) assert.strictEqual(code, `var Foo = /* @__PURE__ */ ((Foo2) => {\n Foo2[Foo2["FOO"] = 0] = "FOO";\n return Foo2;\n})(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 keepConsole({ esbuild }) { const { code } = await esbuild.transform(`console.log('foo')`, { drop: [] }) assert.strictEqual(code, `console.log("foo");\n`) }, async dropConsole({ esbuild }) { const { code: drop } = await esbuild.transform(` console.log('foo') console.log(foo()) console.log.call(console, foo()) console.log.apply(console, foo()) x = console.log(bar()) console['log']('foo') `, { drop: ['console'] }) assert.strictEqual(drop, `x = void 0;\n`) const { code: keepArrow } = await esbuild.transform(` console('foo') console.abc.xyz('foo') console['log']('foo') console[abc][xyz]('foo') console[foo()][bar()]('foo') const x = { log: console.log.bind(console), } `, { drop: ['console'] }) assert.strictEqual(keepArrow, `console("foo"); (() => { }).xyz("foo"); console[abc][xyz]("foo"); console[foo()][bar()]("foo"); const x = { log: (() => { }).bind(console) }; `) const { code: keepFn } = await esbuild.transform(` console('foo') console.abc.xyz('foo') console['log']('foo') console[abc][xyz]('foo') console[foo()][bar()]('foo') const x = { log: console.log.bind(console), } `, { drop: ['console'], supported: { arrow: false } }) assert.strictEqual(keepFn, `console("foo"); (function() { }).xyz("foo"); console[abc][xyz]("foo"); console[foo()][bar()]("foo"); const x = { log: function() { }.bind(console) }; `) }, async keepDebugger({ esbuild }) { const { code } = await esbuild.transform(`if (x) debugger`, { drop: [] }) assert.strictEqual(code, `if (x) debugger;\n`) }, async dropDebugger({ esbuild }) { const { code } = await esbuild.transform(`if (x) debugger`, { drop: ['debugger'] }) assert.strictEqual(code, `if (x) ;\n`) }, async defineProcessEnvNodeEnv({ esbuild }) { const define = { 'process.env.NODE_ENV': '"something"' } const { code: code1 } = await esbuild.transform(`console.log(process.env.NODE_ENV)`, { define }) assert.strictEqual(code1, `console.log("something");\n`) const { code: code2 } = await esbuild.transform(`console.log(process.env['NODE_ENV'])`, { define }) assert.strictEqual(code2, `console.log("something");\n`) const { code: code3 } = await esbuild.transform(`console.log(process['env'].NODE_ENV)`, { define }) assert.strictEqual(code3, `console.log("something");\n`) const { code: code4 } = await esbuild.transform(`console.log(process['env']['NODE_ENV'])`, { define }) assert.strictEqual(code4, `console.log("something");\n`) const { code: code5 } = await esbuild.transform(`console.log(process.env.NODE_ENV)`, {}) assert.strictEqual(code5, `console.log(process.env.NODE_ENV);\n`) const { code: code6 } = await esbuild.transform(`console.log(process.env.NODE_ENV)`, { platform: 'browser' }) assert.strictEqual(code6, `console.log(process.env.NODE_ENV);\n`) }, async defineBuiltInConstants({ esbuild }) { const define = { a: 'NaN', b: 'Infinity', c: 'undefined', d: 'something', e: 'null' } const { code } = await esbuild.transform(`console.log([typeof a, typeof b, typeof c, typeof d, typeof e])`, { define }) assert.strictEqual(code, `console.log(["number", "number", "undefined", typeof something, "object"]);\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 defineThis({ esbuild }) { const { code: code1 } = await esbuild.transform(`console.log(a, b); export {}`, { define: { a: 'this', b: 'this.foo' }, format: 'esm' }) assert.strictEqual(code1, `console.log(void 0, (void 0).foo);\n`) const { code: code2 } = await esbuild.transform(`console.log(this, this.x); export {}`, { define: { this: 'a', 'this.x': 'b' }, format: 'esm' }) assert.strictEqual(code2, `console.log(a, b);\n`) }, async defineImportMetaESM({ esbuild }) { const { code: code1 } = await esbuild.transform(`console.log(a, b); export {}`, { define: { a: 'import.meta', b: 'import.meta.foo' }, format: 'esm' }) assert.strictEqual(code1, `console.log(import.meta, import.meta.foo);\n`) const { code: code2 } = await esbuild.transform(`console.log(import.meta, import.meta.x); export {}`, { define: { 'import.meta': 'a', 'import.meta.x': 'b' }, format: 'esm' }) assert.strictEqual(code2, `console.log(a, b);\n`) }, async defineImportMetaIIFE({ esbuild }) { const { code } = await esbuild.transform(`console.log(a, b); export {}`, { define: { a: 'import.meta', b: 'import.meta.foo' }, format: 'iife' }) assert.strictEqual(code, `(() => {\n const import_meta = {};\n console.log(import_meta, import_meta.foo);\n})();\n`) }, async defineIdentifierVsStringWarningIssue466Transform({ esbuild }) { const { warnings } = await esbuild.transform(``, { define: { 'process.env.NODE_ENV': 'production' } }) const formatted = await esbuild.formatMessages(warnings, { kind: 'warning' }) assert.strictEqual(formatted[0], `▲ [WARNING] "process.env.NODE_ENV" is defined as an identifier instead of a string (surround "production" with quotes to get a string) [suspicious-define] :1:34: 1 │ define: { 'process.env.NODE_ENV': 'production' } │ ~~~~~~~~~~~~ ╵ '"production"' `) }, async defineIdentifierVsStringWarningIssue466Build({ esbuild }) { const { warnings } = await esbuild.build({ define: { 'process.env.NODE_ENV': 'production' }, logLevel: 'silent' }) const formatted = await esbuild.formatMessages(warnings, { kind: 'warning' }) assert.strictEqual(formatted[0], `▲ [WARNING] "process.env.NODE_ENV" is defined as an identifier instead of a string (surround "production" with quotes to get a string) [suspicious-define] :1:34: 1 │ define: { 'process.env.NODE_ENV': 'production' } │ ~~~~~~~~~~~~ ╵ '"production"' `) }, async defineQuotedPropertyNameTransform({ esbuild }) { const { code: code1 } = await esbuild.transform(`return x.y['z!']`, { define: { 'x.y["z!"]': 'true' } }) assert.strictEqual(code1, `return true;\n`) const { code: code2 } = await esbuild.transform(`foo(x['y'].z, x.y['z'], x['y']['z'])`, { define: { 'x.y.z': 'true' } }) assert.strictEqual(code2, `foo(true, true, true);\n`) const { code: code3 } = await esbuild.transform(`foo(x['y'].z, x.y['z'], x['y']['z'])`, { define: { 'x["y"].z': 'true' } }) assert.strictEqual(code3, `foo(true, true, true);\n`) const { code: code4 } = await esbuild.transform(`foo(x['y'].z, x.y['z'], x['y']['z'])`, { define: { 'x.y["z"]': 'true' } }) assert.strictEqual(code4, `foo(true, true, true);\n`) const { code: code5 } = await esbuild.transform(`foo(x['y'].z, x.y['z'], x['y']['z'])`, { define: { 'x["y"][\'z\']': 'true' } }) assert.strictEqual(code5, `foo(true, true, true);\n`) const { code: code6 } = await esbuild.transform(`foo(import.meta['y'].z, import.meta.y['z'], import.meta['y']['z'])`, { define: { 'import.meta["y"].z': 'true' } }) assert.strictEqual(code6, `foo(true, true, true);\n`) const { code: code7 } = await esbuild.transform(`foo(import.meta['y!'].z, import.meta.y['z!'], import.meta['y!']['z!'])`, { define: { 'import.meta["y!"].z': 'true', 'import.meta.y["z!"]': 'true', 'import.meta["y!"]["z!"]': 'true' }, }) assert.strictEqual(code7, `foo(true, true, true);\n`) }, async defineQuotedPropertyNameBuild({ esbuild }) { const { outputFiles } = await esbuild.build({ stdin: { contents: `return process.env['SOME-TEST-VAR']` }, define: { 'process.env["SOME-TEST-VAR"]': 'true' }, write: false, }) assert.strictEqual(outputFiles[0].text, `return true;\n`) }, async defineBigInt({ esbuild }) { const { code } = await esbuild.transform(`console.log(a, b); export {}`, { define: { a: '0n', b: '{"x":[123n]}' }, format: 'esm' }) assert.strictEqual(code, `var define_b_default = { x: [123n] };\nconsole.log(0n, define_b_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: code1 } = await esbuild.transform(`\x00\x01\x02`, { loader: 'dataurl' }) assert.strictEqual(code1, `module.exports = "data:application/octet-stream,%00%01%02";\n`) const { code: code2 } = await esbuild.transform(`\xFD\xFE\xFF`, { loader: 'dataurl' }) assert.strictEqual(code2, `module.exports = "data:text/plain;charset=utf-8,\\xFD\\xFE\\xFF";\n`) const { code: code3 } = await esbuild.transform(new Uint8Array([0xFD, 0xFE, 0xFF]), { loader: 'dataurl' }) assert.strictEqual(code3, `module.exports = "data:text/plain;charset=utf-8;base64,/f7/";\n`) }, async sourceMapTrueWithName({ 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 sourceMapLinkedWithName({ esbuild }) { try { await esbuild.transform(`let x`, { sourcemap: 'linked', sourcefile: 'afile.js' }) throw new Error('Expected a transform failure') } catch (e) { assert.strictEqual(e + '', `Error: Transform failed with 1 error:\nerror: Cannot transform with linked source maps`) } }, 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 transformLegalCommentsJS({ esbuild }) { assert.strictEqual((await esbuild.transform(`//!x\ny()`, { legalComments: 'none' })).code, `y();\n`) assert.strictEqual((await esbuild.transform(`//!x\ny()`, { legalComments: 'inline' })).code, `//!x\ny();\n`) const eofResult = await esbuild.transform(`//!x\ny()`, { legalComments: 'eof' }) assert.strictEqual(eofResult.code, `y();\n//!x\n`) assert.strictEqual(eofResult.legalComments, undefined) const externalResult = await esbuild.transform(`//!x\ny()`, { legalComments: 'external' }) assert.strictEqual(externalResult.code, `y();\n`) assert.strictEqual(externalResult.legalComments, `//!x\n`) try { await esbuild.transform(``, { legalComments: 'linked' }) throw new Error('Expected a transform failure') } catch (e) { if (!e || !e.errors || !e.errors[0] || e.errors[0].text !== 'Cannot transform with linked legal comments') throw e } }, async transformLegalCommentsCSS({ esbuild }) { assert.strictEqual((await esbuild.transform(`/*!x*/\ny{}`, { loader: 'css', legalComments: 'none' })).code, `y {\n}\n`) assert.strictEqual((await esbuild.transform(`/*!x*/\ny{}`, { loader: 'css', legalComments: 'inline' })).code, `/*!x*/\ny {\n}\n`) const eofResult = await esbuild.transform(`/*!x*/\ny{}`, { loader: 'css', legalComments: 'eof' }) assert.strictEqual(eofResult.code, `y {\n}\n/*!x*/\n`) assert.strictEqual(eofResult.legalComments, undefined) const externalResult = await esbuild.transform(`/*!x*/\ny{}`, { loader: 'css', legalComments: 'external' }) assert.strictEqual(externalResult.code, `y {\n}\n`) assert.strictEqual(externalResult.legalComments, `/*!x*/\n`) try { await esbuild.transform(``, { legalComments: 'linked' }) throw new Error('Expected a transform failure') } catch (e) { if (!e || !e.errors || !e.errors[0] || e.errors[0].text !== 'Cannot transform with linked legal comments') throw e } }, 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', tsconfigRaw: { compilerOptions: { experimentalDecorators: true, }, }, }); 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 pureImportMeta({ esbuild }) { const { code: code1 } = await esbuild.transform(`import.meta.foo(123, foo)`, { minifySyntax: true, pure: [] }) assert.strictEqual(code1, `import.meta.foo(123, foo);\n`) const { code: code2 } = await esbuild.transform(`import.meta.foo(123, foo)`, { minifySyntax: true, pure: ['import.meta.foo'] }) assert.strictEqual(code2, `foo;\n`) }, async nameCollisionEvalRename({ esbuild }) { const { code } = await esbuild.transform(` // "arg" must not be renamed to "arg2" return function(arg2) { function foo(arg) { return arg + arg2; } // "eval" prevents "arg2" from being renamed // "arg" below causes "arg" above to be renamed return eval(foo(1)) + arg }(2); `) const result = new Function('arg', code)(10) assert.strictEqual(result, 13) }, async nameCollisionEvalMinify({ esbuild }) { const { code } = await esbuild.transform(` // "arg" must not be renamed to "$" return function($) { function foo(arg) { return arg + $; } // "eval" prevents "$" from being renamed // Repeated "$" puts "$" at the top of the character frequency histogram return eval(foo($$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$)) }(2); `, { minifyIdentifiers: true }) const result = new Function('$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$', code)(1) assert.strictEqual(result, 3) }, async singleUseExpressionSubstitution({ esbuild }) { function run(code) { try { return JSON.stringify(new Function(code)()) } catch (error) { return error + '' } } let bugs = '' for (let input of [ `let fn = () => { throw new Error }; let x = undef; return fn() + x`, `let fn = () => { throw new Error }; let x = fn(); return undef + x`, `let fn = () => arg0 = 0; let x = fn(); return arg0 + x`, `let fn = () => arg0 = 0; let x = fn(); return arg0 = x`, `let fn = () => arg0 = 0; let x = fn(); return arg0 += x`, `let fn = () => arg0 = 0; let x = fn(); return arg0 ||= x`, `let fn = () => arg0 = 0; let x = fn(); return arg0 &&= x`, `let fn = () => arg0 = 0; let obj = [1]; let x = arg0; return obj[fn()] + x`, `let fn = () => arg0 = 0; let obj = [1]; let x = arg0; return obj[fn()] = x`, `let fn = () => arg0 = 0; let obj = [1]; let x = arg0; return obj[fn()] += x`, `let fn = () => arg0 = 0; let obj = [1]; let x = arg0; return obj[fn()] ||= x`, `let fn = () => arg0 = 0; let obj = [1]; let x = arg0; return obj[fn()] &&= x`, `let obj = { get y() { arg0 = 0; return 1 } }; let x = obj.y; return arg0 + x`, `let obj = { get y() { arg0 = 0; return 1 } }; let x = arg0; return obj.y + x`, `let x = undef; return arg0 || x`, `let x = undef; return arg0 && x`, `let x = undef; return arg0 ? x : 1`, `let x = undef; return arg0 ? 1 : x`, `let fn = () => { throw new Error }; let x = fn(); return arg0 || x`, `let fn = () => { throw new Error }; let x = fn(); return arg0 && x`, `let fn = () => { throw new Error }; let x = fn(); return arg0 ? x : 1`, `let fn = () => { throw new Error }; let x = fn(); return arg0 ? 1 : x`, ]) { input = `function f(arg0) { ${input} } return f(123)` const { code: minified } = await esbuild.transform(input, { minify: true }) if (run(input) !== run(minified)) bugs += '\n ' + input } if (bugs !== '') throw new Error('Single-use expression substitution bugs:' + bugs) }, async platformNode({ esbuild }) { const { code } = await esbuild.transform(`export let foo = 123`, { format: 'cjs', platform: 'node' }) assert(code.slice(code.indexOf('let foo')), `let foo = 123; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { foo }); `) }, async dynamicImportString({ esbuild }) { const { code } = await esbuild.transform(`import('foo')`, { target: 'chrome63' }) assert.strictEqual(code, `import("foo");\n`) }, async dynamicImportStringES6({ esbuild }) { const fromPromiseResolve = text => text.slice(text.indexOf('Promise.resolve')) const { code } = await esbuild.transform(`import('foo')`, { target: 'chrome62' }) assert.strictEqual(fromPromiseResolve(code), `Promise.resolve().then(() => __toESM(require("foo")));\n`) }, async dynamicImportStringES5({ esbuild }) { const fromPromiseResolve = text => text.slice(text.indexOf('Promise.resolve')) const { code } = await esbuild.transform(`import('foo')`, { target: 'chrome48' }) assert.strictEqual(fromPromiseResolve(code), `Promise.resolve().then(function() {\n return __toESM(require("foo"));\n});\n`) }, async dynamicImportStringES5Minify({ esbuild }) { const fromPromiseResolve = text => text.slice(text.indexOf('Promise.resolve')) const { code } = await esbuild.transform(`import('foo')`, { target: 'chrome48', minifyWhitespace: true }) assert.strictEqual(fromPromiseResolve(code), `Promise.resolve().then(function(){return __toESM(require("foo"))});\n`) }, async dynamicImportStringNode12_19({ esbuild }) { const fromPromiseResolve = text => text.slice(text.indexOf('Promise.resolve')) const { code } = await esbuild.transform(`import('foo')`, { target: 'node12.19' }) assert.strictEqual(fromPromiseResolve(code), `Promise.resolve().then(() => __toESM(require("foo")));\n`) }, async dynamicImportStringNode12_20({ esbuild }) { const { code } = await esbuild.transform(`import('foo')`, { target: 'node12.20' }) assert.strictEqual(code, `import("foo");\n`) }, async dynamicImportStringNode13({ esbuild }) { const fromPromiseResolve = text => text.slice(text.indexOf('Promise.resolve')) const { code } = await esbuild.transform(`import('foo')`, { target: 'node13' }) assert.strictEqual(fromPromiseResolve(code), `Promise.resolve().then(() => __toESM(require("foo")));\n`) }, async dynamicImportStringNode13_1({ esbuild }) { const fromPromiseResolve = text => text.slice(text.indexOf('Promise.resolve')) const { code } = await esbuild.transform(`import('foo')`, { target: 'node13.1' }) assert.strictEqual(fromPromiseResolve(code), `Promise.resolve().then(() => __toESM(require("foo")));\n`) }, async dynamicImportStringNode13_2({ esbuild }) { const { code } = await esbuild.transform(`import('foo')`, { target: 'node13.2' }) assert.strictEqual(code, `import("foo");\n`) }, async dynamicImportExpression({ esbuild }) { const { code } = await esbuild.transform(`import(foo)`, { target: 'chrome63' }) assert.strictEqual(code, `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(() => __toESM(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 __toESM(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 __toESM(require(foo))});\n`) }, async classStaticBlocks({ esbuild }) { // Check that empty static class blocks are still lowered (this was a regression in version 0.18.2) assert.strictEqual((await esbuild.transform(`class Foo { static {} }`, { supported: { 'class-static-blocks': false } })).code, `class Foo {\n}\n`) assert.strictEqual((await esbuild.transform(`class Foo { static { x } }`, { supported: { 'class-static-blocks': false } })).code, `class Foo {\n}\nx;\n`) }, async keepNamesUnsupported({ esbuild }) { try { await esbuild.transform(``, { keepNames: true, target: 'chrome36' }) throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e.errors[0].text, 'The "keep names" setting cannot be used with the configured target environment ("chrome36")') } try { await esbuild.transform(``, { keepNames: true, target: 'chrome46', supported: { 'function-name-configurable': false } }) throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e.errors[0].text, 'The "keep names" setting cannot be used with the configured target environment ("chrome46" + 1 override)') } await esbuild.transform(``, { keepNames: true, target: 'chrome46' }) }, async inlineScript({ esbuild }) { let p assert.strictEqual((await esbuild.transform(`x = ''`, {})).code, `x = "<\\/script>";\n`) assert.strictEqual((await esbuild.transform(`x = ' inline'`, { supported: { 'inline-script': true } })).code, `x = "<\\/script> inline";\n`) assert.strictEqual((await esbuild.transform(`x = ' noinline'`, { supported: { 'inline-script': false } })).code, `x = " noinline";\n`) p = { platform: 'browser' } assert.strictEqual((await esbuild.transform(`x = ' browser'`, { ...p })).code, `x = "<\\/script> browser";\n`) assert.strictEqual((await esbuild.transform(`x = ' browser inline'`, { ...p, supported: { 'inline-script': true } })).code, `x = "<\\/script> browser inline";\n`) assert.strictEqual((await esbuild.transform(`x = ' browser noinline'`, { ...p, supported: { 'inline-script': false } })).code, `x = " browser noinline";\n`) p = { platform: 'node' } assert.strictEqual((await esbuild.transform(`x = ' node'`, { ...p })).code, `x = " node";\n`) assert.strictEqual((await esbuild.transform(`x = ' node inline'`, { ...p, supported: { 'inline-script': true } })).code, `x = "<\\/script> node inline";\n`) assert.strictEqual((await esbuild.transform(`x = ' node noinline'`, { ...p, supported: { 'inline-script': false } })).code, `x = " node noinline";\n`) p = { platform: 'neutral' } assert.strictEqual((await esbuild.transform(`x = ' neutral'`, { ...p })).code, `x = " neutral";\n`) assert.strictEqual((await esbuild.transform(`x = ' neutral inline'`, { ...p, supported: { 'inline-script': true } })).code, `x = "<\\/script> neutral inline";\n`) assert.strictEqual((await esbuild.transform(`x = ' neutral noinline'`, { ...p, supported: { 'inline-script': false } })).code, `x = " neutral noinline";\n`) assert.strictEqual((await esbuild.transform(`x = ''`, { target: 'esnext' })).code, `x = "<\\/script>";\n`) assert.strictEqual((await esbuild.transform(`x = ''`, { target: 'es2020' })).code, `x = "<\\/script>";\n`) assert.strictEqual((await esbuild.transform(`x = ''`, { target: 'es6' })).code, `x = "<\\/script>";\n`) assert.strictEqual((await esbuild.transform(`x = ''`, { target: 'chrome999' })).code, `x = "<\\/script>";\n`) assert.strictEqual((await esbuild.transform(`x = ''`, { target: 'chrome0' })).code, `x = "<\\/script>";\n`) }, async inlineStyle({ esbuild }) { let p = { loader: 'css' } assert.strictEqual((await esbuild.transform(`x { y: '' }`, { ...p })).code, `x {\n y: "<\\/style>";\n}\n`) assert.strictEqual((await esbuild.transform(`x { y: ' inline' }`, { ...p, supported: { 'inline-style': true } })).code, `x {\n y: "<\\/style> inline";\n}\n`) assert.strictEqual((await esbuild.transform(`x { y: ' noinline' }`, { ...p, supported: { 'inline-style': false } })).code, `x {\n y: " noinline";\n}\n`) p = { loader: 'css', platform: 'browser' } assert.strictEqual((await esbuild.transform(`x { y: ' browser' }`, { ...p })).code, `x {\n y: "<\\/style> browser";\n}\n`) assert.strictEqual((await esbuild.transform(`x { y: ' browser inline' }`, { ...p, supported: { 'inline-style': true } })).code, `x {\n y: "<\\/style> browser inline";\n}\n`) assert.strictEqual((await esbuild.transform(`x { y: ' browser noinline' }`, { ...p, supported: { 'inline-style': false } })).code, `x {\n y: " browser noinline";\n}\n`) p = { loader: 'css', platform: 'node' } assert.strictEqual((await esbuild.transform(`x { y: ' node' }`, { ...p })).code, `x {\n y: " node";\n}\n`) assert.strictEqual((await esbuild.transform(`x { y: ' node inline' }`, { ...p, supported: { 'inline-style': true } })).code, `x {\n y: "<\\/style> node inline";\n}\n`) assert.strictEqual((await esbuild.transform(`x { y: ' node noinline' }`, { ...p, supported: { 'inline-style': false } })).code, `x {\n y: " node noinline";\n}\n`) p = { loader: 'css', platform: 'neutral' } assert.strictEqual((await esbuild.transform(`x { y: ' neutral' }`, { ...p })).code, `x {\n y: " neutral";\n}\n`) assert.strictEqual((await esbuild.transform(`x { y: ' neutral inline' }`, { ...p, supported: { 'inline-style': true } })).code, `x {\n y: "<\\/style> neutral inline";\n}\n`) assert.strictEqual((await esbuild.transform(`x { y: ' neutral noinline' }`, { ...p, supported: { 'inline-style': false } })).code, `x {\n y: " neutral noinline";\n}\n`) p = { loader: 'css' } assert.strictEqual((await esbuild.transform(`x { y: '' }`, { ...p, target: 'esnext' })).code, `x {\n y: "<\\/style>";\n}\n`) assert.strictEqual((await esbuild.transform(`x { y: '' }`, { ...p, target: 'es2020' })).code, `x {\n y: "<\\/style>";\n}\n`) assert.strictEqual((await esbuild.transform(`x { y: '' }`, { ...p, target: 'es6' })).code, `x {\n y: "<\\/style>";\n}\n`) assert.strictEqual((await esbuild.transform(`x { y: '' }`, { ...p, target: 'chrome999' })).code, `x {\n y: "<\\/style>";\n}\n`) assert.strictEqual((await esbuild.transform(`x { y: '' }`, { ...p, target: 'chrome0' })).code, `x {\n y: "<\\/style>";\n}\n`) }, async typeofEqualsUndefinedTarget({ esbuild }) { assert.strictEqual((await esbuild.transform(`a = typeof b !== 'undefined'`, { minify: true })).code, `a=typeof b<"u";\n`) assert.strictEqual((await esbuild.transform(`a = typeof b !== 'undefined'`, { minify: true, target: 'es2020' })).code, `a=typeof b<"u";\n`) assert.strictEqual((await esbuild.transform(`a = typeof b !== 'undefined'`, { minify: true, target: 'chrome11' })).code, `a=typeof b<"u";\n`) assert.strictEqual((await esbuild.transform(`a = typeof b !== 'undefined'`, { minify: true, target: 'es2019' })).code, `a=typeof b!="undefined";\n`) assert.strictEqual((await esbuild.transform(`a = typeof b !== 'undefined'`, { minify: true, target: 'ie11' })).code, `a=typeof b!="undefined";\n`) }, async caseInsensitiveTarget({ esbuild }) { assert.strictEqual((await esbuild.transform(`a ||= b`, { target: 'eS5' })).code, `a || (a = b);\n`) assert.strictEqual((await esbuild.transform(`a ||= b`, { target: 'eSnExT' })).code, `a ||= b;\n`) }, async propertyAccessBugWorkaroundForWebKit({ esbuild }) { const check = async (target, input, expected) => assert.strictEqual((await esbuild.transform(input, { target })).code, expected) await Promise.all([ check('safari16.2', `x(class{}.y=z)`, `x((class {\n}).y = z);\n`), check('safari16.3', `x(class{}.y=z)`, `x(class {\n}.y = z);\n`), check('safari16.2', `x(function(){}.y=z)`, `x((function() {\n}).y = z);\n`), check('safari16.3', `x(function(){}.y=z)`, `x(function() {\n}.y = z);\n`), check('safari16.2', `x(function*(){}.y=z)`, `x((function* () {\n}).y = z);\n`), check('safari16.3', `x(function*(){}.y=z)`, `x(function* () {\n}.y = z);\n`), check('safari16.2', `x(async function(){}.y=z)`, `x((async function() {\n}).y = z);\n`), check('safari16.3', `x(async function(){}.y=z)`, `x(async function() {\n}.y = z);\n`), check('safari16.2', `x(async function*(){}.y=z)`, `x((async function* () {\n}).y = z);\n`), check('safari16.3', `x(async function*(){}.y=z)`, `x(async function* () {\n}.y = z);\n`), // This should not be enabled by default check('esnext', `x(class{}.y=z)`, `x(class {\n}.y = z);\n`), // This doesn't need to be applied for methods in object literals check('safari16.2', `x({f(a=-1){}}.y=z)`, `x({ f(a = -1) {\n} }.y = z);\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(`class X {}`, { target: ['es5', 'chrome1', 'safari2', 'firefox3'] }) throw new Error('Expected an error to be thrown') } catch (e) { assert.strictEqual(e.errors[0].text, 'Transforming class syntax to the configured target environment ("chrome1", "es5", "firefox3", "safari2") is not supported yet') } }, async supported({ esbuild }) { const check = async (options, input, expected) => { try { assert.strictEqual((await esbuild.transform(input, options)).code, expected) } catch (e) { if (e.errors) assert.strictEqual(e.errors[0].text, expected) else throw e } } await Promise.all([ // JS: lower check({ supported: { arrow: true } }, `x = () => y`, `x = () => y;\n`), check({ supported: { arrow: false } }, `x = () => y`, `x = function() {\n return y;\n};\n`), check({ supported: { arrow: true }, target: 'es5' }, `x = () => y`, `x = () => y;\n`), check({ supported: { arrow: false }, target: 'es5' }, `x = () => y`, `x = function() {\n return y;\n};\n`), check({ supported: { arrow: true }, target: 'es2022' }, `x = () => y`, `x = () => y;\n`), check({ supported: { arrow: false }, target: 'es2022' }, `x = () => y`, `x = function() {\n return y;\n};\n`), // JS: error check({ supported: { class: true } }, `class X {}`, `class X {\n}\n`), check({ supported: { class: false } }, `class X {}`, `Transforming class syntax to the configured target environment is not supported yet`), check({ supported: { class: true }, target: 'es5' }, `class X {}`, `class X {\n}\n`), check({ supported: { class: false }, target: 'es5' }, `class X {}`, `Transforming class syntax to the configured target environment ("es5" + 11 overrides) is not supported yet`), check({ supported: { class: true }, target: 'es6' }, `class X {}`, `class X {\n}\n`), check({ supported: { class: false }, target: 'es6' }, `class X {}`, `Transforming class syntax to the configured target environment ("es2015" + 11 overrides) is not supported yet`), // CSS: lower check({ supported: { 'hex-rgba': true }, loader: 'css' }, `a { color: #1234 }`, `a {\n color: #1234;\n}\n`), check({ supported: { 'hex-rgba': false }, loader: 'css' }, `a { color: #1234 }`, `a {\n color: rgba(17, 34, 51, .267);\n}\n`), check({ target: 'safari15.3', loader: 'css' }, `a { mask-image: url(x.png) }`, `a {\n -webkit-mask-image: url(x.png);\n mask-image: url(x.png);\n}\n`), check({ target: 'safari15.4', loader: 'css' }, `a { mask-image: url(x.png) }`, `a {\n mask-image: url(x.png);\n}\n`), // Check for "+ 2 overrides" check({ supported: { class: false, arrow: true }, target: 'es2022' }, `class X {}`, `Transforming class syntax to the configured target environment ("es2022" + 12 overrides) is not supported yet`), ]) }, async regExpFeatures({ esbuild }) { const check = async (target, input, expected) => assert.strictEqual((await esbuild.transform(input, { target })).code, expected) await Promise.all([ // RegExpStickyAndUnicodeFlags check('es6', `x1 = /./y`, `x1 = /./y;\n`), check('es6', `x2 = /./u`, `x2 = /./u;\n`), check('es5', `x3 = /./y`, `x3 = new RegExp(".", "y");\n`), check('es5', `x4 = /./u`, `x4 = new RegExp(".", "u");\n`), // RegExpDotAllFlag check('es2018', `x1 = /a.b/s`, `x1 = /a.b/s;\n`), check('es2017', `x2 = /a.b/s`, `x2 = new RegExp("a.b", "s");\n`), // RegExpLookbehindAssertions check('es2018', `x1 = /(?<=x)/`, `x1 = /(?<=x)/;\n`), check('es2018', `x2 = /(?b)/`, `x1 = /(?b)/;\n`), check('es2017', `x2 = /(?b)/`, `x2 = new RegExp("(?b)");\n`), // RegExpUnicodePropertyEscapes check('es2018', `x1 = /\\p{Emoji}/u`, `x1 = /\\p{Emoji}/u;\n`), check('es2017', `x2 = /\\p{Emoji}/u`, `x2 = new RegExp("\\\\p{Emoji}", "u");\n`), // RegExpMatchIndices check('es2022', `x1 = /y/d`, `x1 = /y/d;\n`), check('es2021', `x2 = /y/d`, `x2 = new RegExp("y", "d");\n`), // RegExpSetNotation check('es2024', `x1 = /[\\p{White_Space}&&\\p{ASCII}]/v`, `x1 = /[\\p{White_Space}&&\\p{ASCII}]/v;\n`), check('es2022', `x2 = /[\\p{White_Space}&&\\p{ASCII}]/v`, `x2 = new RegExp("[\\\\p{White_Space}&&\\\\p{ASCII}]", "v");\n`), ]) }, // Future syntax 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'), } 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], `${errorIcon} [ERROR] This is an error\n\n`) assert.strictEqual(messages[1], `${errorIcon} [ERROR] Another error\n\n file.js:0:0:\n 0 │ \n ╵ ^\n\n`) }, } let analyzeTests = { async analyzeMetafile({ esbuild }) { const metafile = { "inputs": { "entry.js": { "bytes": 50, "imports": [ { "path": "lib.js", "kind": "import-statement" } ] }, "lib.js": { "bytes": 200, "imports": [] } }, "outputs": { "out.js": { "imports": [], "exports": [], "entryPoint": "entry.js", "inputs": { "entry.js": { "bytesInOutput": 25 }, "lib.js": { "bytesInOutput": 50 } }, "bytes": 100 } } } assert.strictEqual(await esbuild.analyzeMetafile(metafile), ` out.js 100b 100.0% ├ lib.js 50b 50.0% └ entry.js 25b 25.0% `) assert.strictEqual(await esbuild.analyzeMetafile(metafile, { verbose: true }), ` out.js ────── 100b ── 100.0% ├ lib.js ──── 50b ─── 50.0% │ └ entry.js └ entry.js ── 25b ─── 25.0% `) }, } 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 apiSyncTests = { async defaultExport({ esbuild }) { assert.strictEqual(typeof esbuild.version, 'string') assert.strictEqual(esbuild.version, esbuild.default.version) assert.strictEqual(esbuild.version, esbuild.default.default.version) assert.strictEqual(esbuild.version, esbuild.default.default.default.version) }, 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).split(path.sep).join('/') 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))) assert.strictEqual(result.outputFiles[0].hash, 'H4KMzZ07fA0') }, 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(Object.create({ toString() { return '1+2' } })) throw new Error('Expected an error to be thrown'); } catch (e) { assert.strictEqual(e.errors ? e.errors[0].text : e + '', 'The input to "transform" must be a string or a Uint8Array') } }, 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).split(path.sep).join('/')}:1:2: ERROR: Unexpected end of file`); 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], `${errorIcon} [ERROR] This is an error\n\n`) assert.strictEqual(messages[1], `${errorIcon} [ERROR] Another error\n\n file.js:0:0:\n 0 │ \n ╵ ^\n\n`) }, async analyzeMetafileSync({ esbuild }) { const metafile = { "inputs": { "entry.js": { "bytes": 50, "imports": [ { "path": "lib.js", "kind": "import-statement" } ] }, "lib.js": { "bytes": 200, "imports": [] } }, "outputs": { "out.js": { "imports": [], "exports": [], "entryPoint": "entry.js", "inputs": { "entry.js": { "bytesInOutput": 25 }, "lib.js": { "bytesInOutput": 50 } }, "bytes": 100 } } } assert.strictEqual(esbuild.analyzeMetafileSync(metafile), ` out.js 100b 100.0% ├ lib.js 50b 50.0% └ entry.js 25b 25.0% `) assert.strictEqual(esbuild.analyzeMetafileSync(metafile, { verbose: true }), ` out.js ────── 100b ── 100.0% ├ lib.js ──── 50b ─── 50.0% │ └ entry.js └ entry.js ── 25b ─── 25.0% `) }, } let childProcessTests = { // More info about this test case: https://github.com/evanw/esbuild/issues/2727 async testIncrementalChildProcessExit({ testDir, esbuild }) { const file = path.join(testDir, 'build.js') await writeFileAsync(file, ` const esbuild = require(${JSON.stringify(esbuild.ESBUILD_PACKAGE_PATH)}) esbuild.context({ entryPoints: [], }) .then(context => context.rebuild()) .then(() => { console.log('success') process.exit(0) }) `) let timeout const detectHangPromise = new Promise((_, reject) => { timeout = setTimeout(() => { reject(new Error('Timed out waiting for keep-alive check to terminate')) }, 5 * 60 * 1000) }) const testKeepAlivePingPromise = new Promise((resolve, reject) => { child_process.execFile('node', [file], { stdio: [ 'inherit', 'inherit', 'pipe', // This is important for the test to check for the hang ], }, (error, stdout, stderr) => { clearTimeout(timeout) if (error) reject(error) else if (stdout !== 'success\n') reject(new Error('Unexpected stdout: ' + JSON.stringify(stdout))) else if (stderr !== '') reject(new Error('Unexpected stderr: ' + JSON.stringify(stderr))) else resolve() }) }) await Promise.race([ detectHangPromise, testKeepAlivePingPromise, ]) }, async testWatchStdoutChildProcess({ testDir, esbuild }) { const sequence = [ { input: 'console.log(1+2)', stdout: ['console.log(1 + 2);'], stderr: ['[watch] build finished, watching for changes...'], }, { input: 'console.log(2+3)', stdout: ['console.log(2 + 3);'], stderr: ['[watch] build started (change: "in.js")', '[watch] build finished'], }, { input: 'console.log(3+4)', stdout: ['console.log(3 + 4);'], stderr: ['[watch] build started (change: "in.js")', '[watch] build finished'], }, ] const infile = path.join(testDir, 'in.js') const file = path.join(testDir, 'build.js') await writeFileAsync(infile, sequence[0].input) await writeFileAsync(file, ` const esbuild = require(${JSON.stringify(esbuild.ESBUILD_PACKAGE_PATH)}) esbuild.context({ entryPoints: [${JSON.stringify(infile)}], logLevel: 'info', }).then(ctx => ctx.watch()) `) // Start the child const maxSeconds = 60 const child = child_process.spawn('node', [file], { cwd: testDir, stdio: ['inherit', 'pipe', 'pipe'], timeout: maxSeconds * 1000, }) // Make sure the child is always killed try { for (const { input, stdout: expectedStdout, stderr: expectedStderr } of sequence) { let totalStdout = '' let totalStderr = '' let stdoutBuffer = '' let stderrBuffer = '' const onstdout = data => { totalStdout += data stdoutBuffer += data check() } const onstderr = data => { totalStderr += data stderrBuffer += data check() } let check = () => { } child.stdout.on('data', onstdout) child.stderr.on('data', onstderr) await new Promise((resolve, reject) => { const seconds = 30 const timeout = setTimeout(() => reject(new Error( `Watch mode + stdout test failed to match expected output after ${seconds} seconds input: ${JSON.stringify(input)} stdout: ${JSON.stringify(totalStdout)} stderr: ${JSON.stringify(totalStderr)} `)), seconds * 1000) check = () => { let index while ((index = stdoutBuffer.indexOf('\n')) >= 0) { const line = stdoutBuffer.slice(0, index) stdoutBuffer = stdoutBuffer.slice(index + 1) if (line === expectedStdout[0]) expectedStdout.shift() } while ((index = stderrBuffer.indexOf('\n')) >= 0) { const line = stderrBuffer.slice(0, index) stderrBuffer = stderrBuffer.slice(index + 1) if (line === expectedStderr[0]) expectedStderr.shift() } if (!expectedStdout.length && !expectedStderr.length) { clearTimeout(timeout) resolve() } } writeFileAtomic(infile, input) }) child.stdout.off('data', onstdout) child.stderr.off('data', onstderr) } } finally { child.kill() } }, } let serialTests = { async startStop({ esbuild }) { for (let i = 0; i < 3; i++) { let result1 = await esbuild.transform('1+2') assert.strictEqual(result1.code, '1 + 2;\n') let result2 = esbuild.transformSync('2+3') assert.strictEqual(result2.code, '2 + 3;\n') let result3 = await esbuild.build({ stdin: { contents: '1+2' }, write: false }) assert.strictEqual(result3.outputFiles[0].text, '1 + 2;\n') let result4 = esbuild.buildSync({ stdin: { contents: '2+3' }, write: false }) assert.strictEqual(result4.outputFiles[0].text, '2 + 3;\n') esbuild.stop() } }, } async function assertSourceMap(jsSourceMap, source) { jsSourceMap = JSON.parse(jsSourceMap) assert.deepStrictEqual(jsSourceMap.version, 3) assert.deepStrictEqual(jsSourceMap.sources, [source]) assert.deepStrictEqual(jsSourceMap.sourcesContent, ['let x']) assert.deepStrictEqual(jsSourceMap.mappings, 'AAAA,IAAU;') } 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) 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(analyzeTests), ...Object.entries(apiSyncTests), ...Object.entries(childProcessTests), ] // Run everything in "tests" concurrently let allTestsPassed = (await Promise.all(tests.map(([name, fn]) => { const promise = runTest(name, fn) // Time out each individual test after 3 minutes. This exists to help debug test hangs in CI. const minutes = 3 const timeout = setTimeout(() => { console.error(`❌ the test "${name}" timed out after ${minutes} minutes, exiting...`) process.exit(1) }, minutes * 60 * 1000) return promise.finally(() => clearTimeout(timeout)) }))).every(success => success) // Run everything in "serialTests" in serial for (let [name, fn] of Object.entries(serialTests)) { if (!await runTest(name, fn)) { allTestsPassed = false } } 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 }))