mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
## Summary
- Removes unused function and class expression names when
`--minify-syntax` is enabled during bundling
- Adds `--keep-names` flag to preserve original names when minifying
- Matches esbuild's minification behavior
## Problem
When minifying with `--minify-syntax`, Bun was keeping function and
class expression names even when they were never referenced, resulting
in larger bundle sizes compared to esbuild.
**Before:**
```js
export var AB = function A() { };
// Bun output: var AB = function A() {};
// esbuild output: var AB = function() {};
```
## Solution
This PR adds logic to remove unused function and class expression names
during minification, matching esbuild's behavior. Names are only removed
when:
- `--minify-syntax` is enabled
- Bundling is enabled (not transform-only mode)
- The scope doesn't contain direct eval (which could reference the name
dynamically)
- The symbol's usage count is 0
Additionally, a `--keep-names` flag has been added to preserve original
names when desired (useful for debugging or runtime reflection).
## Testing
- Updated existing test in `bundler_minify.test.ts`
- All transpiler tests pass
- Manually verified output matches esbuild for various cases
## Examples
```bash
# Without --keep-names (names removed)
bun build --minify-syntax input.js
# var AB = function() {}
# With --keep-names (names preserved)
bun build --minify-syntax --keep-names input.js
# var AB = function A() {}
```
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
1832 lines
51 KiB
TypeScript
1832 lines
51 KiB
TypeScript
import { describe } from "bun:test";
|
|
import { itBundled } from "../expectBundled";
|
|
|
|
// Tests ported from:
|
|
// https://github.com/evanw/esbuild
|
|
// most of these are from scripts/end-to-end-tests.js but some are from other files
|
|
|
|
// For debug, all files are written to $TEMP/bun-bundle-tests/extra
|
|
describe("bundler", () => {
|
|
itBundled("extra/FileAsDirectoryBreak", {
|
|
files: {
|
|
"/index.js": `
|
|
import foo from "./file.js/what/is/this";
|
|
`,
|
|
"/file.js": `
|
|
export default 123;
|
|
`,
|
|
},
|
|
bundleErrors: {
|
|
"/index.js": [`Could not resolve: "./file.js/what/is/this"`],
|
|
},
|
|
});
|
|
itBundled("extra/PathWithQuestionMark", {
|
|
todo: true,
|
|
files: {
|
|
"/index.js": `
|
|
import foo from "./file.js?ignore-me";
|
|
console.log(foo);
|
|
`,
|
|
"/file.js": `
|
|
export default 123;
|
|
`,
|
|
},
|
|
run: {
|
|
stdout: "123",
|
|
},
|
|
});
|
|
itBundled("extra/JSXEscaping1", {
|
|
files: {
|
|
"/index.js": `
|
|
let button = <Button content="some so-called \\"button text\\"" />
|
|
console.log(button);
|
|
`,
|
|
},
|
|
external: ["react"],
|
|
bundleErrors: {
|
|
"/index.js": [`Invalid JSX escape - use XML entity codes quotes or pass a JavaScript string instead`],
|
|
},
|
|
});
|
|
itBundled("extra/JSXEscaping2", {
|
|
files: {
|
|
"/index.js": `
|
|
let button = <Button content='some so-called \\'button text\\'' />
|
|
console.log(button);
|
|
`,
|
|
},
|
|
external: ["react"],
|
|
bundleErrors: {
|
|
"/index.js": [`Invalid JSX escape - use XML entity codes quotes or pass a JavaScript string instead`],
|
|
},
|
|
});
|
|
// Test arbitrary module namespace identifier names
|
|
// See https://github.com/tc39/ecma262/pull/2154
|
|
itBundled("extra/ArbitraryModuleNamespaceIdentifiers1", {
|
|
files: {
|
|
"entry.js": `import {'*' as star} from './export.js'; if (star !== 123) throw 'fail'`,
|
|
"export.js": `let foo = 123; export {foo as '*'}`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/ArbitraryModuleNamespaceIdentifiers2", {
|
|
files: {
|
|
"entry.js": `import {'\\0' as bar} from './export.js'; if (bar !== 123) throw 'fail'`,
|
|
"export.js": `let foo = 123; export {foo as '\\0'}`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/ArbitraryModuleNamespaceIdentifiers3", {
|
|
files: {
|
|
"entry.js": `import {'\\uD800\\uDC00' as bar} from './export.js'; if (bar !== 123) throw 'fail'`,
|
|
"export.js": `let foo = 123; export {foo as '\\uD800\\uDC00'}`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/ArbitraryModuleNamespaceIdentifiers4", {
|
|
files: {
|
|
"entry.js": `import {'🍕' as bar} from './export.js'; if (bar !== 123) throw 'fail'`,
|
|
"export.js": `let foo = 123; export {foo as '🍕'}`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/ArbitraryModuleNamespaceIdentifiers5", {
|
|
files: {
|
|
"entry.js": `import {' ' as bar} from './export.js'; if (bar !== 123) throw 'fail'`,
|
|
"export.js": `export let foo = 123; export {foo as ' '} from './export.js'`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/ArbitraryModuleNamespaceIdentifiers6", {
|
|
files: {
|
|
"entry.js": `import {'' as ab} from './export.js'; if (ab.foo !== 123 || ab.bar !== 234) throw 'fail'`,
|
|
"export.js": `export let foo = 123, bar = 234; export * as '' from './export.js'`,
|
|
},
|
|
run: true,
|
|
});
|
|
|
|
itBundled("extra/RemoveASMDirective", {
|
|
files: {
|
|
"entry.js": `
|
|
function foo() { 'use asm'; eval("/* not asm.js */") }
|
|
if(foo.toString().indexOf("use asm") !== -1) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
|
|
// See https://github.com/evanw/esbuild/issues/421
|
|
itBundled("extra/ImportOrder1", {
|
|
files: {
|
|
"in.js": `
|
|
import {foo} from './cjs'
|
|
import {bar} from './esm'
|
|
if (foo !== 1 || bar !== 2) throw 'fail'
|
|
`,
|
|
"cjs.js": `exports.foo = 1; global.internal_import_order_test1 = 2`,
|
|
"esm.js": `export let bar = global.internal_import_order_test1`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/ImportOrder2", {
|
|
files: {
|
|
"in.js": `
|
|
if (foo !== 3 || bar !== 4) throw 'fail'
|
|
import {foo} from './cjs'
|
|
import {bar} from './esm'
|
|
`,
|
|
"cjs.js": `exports.foo = 3; global.internal_import_order_test2 = 4`,
|
|
"esm.js": `export let bar = global.internal_import_order_test2`,
|
|
},
|
|
run: true,
|
|
});
|
|
// See https://github.com/evanw/esbuild/issues/542
|
|
let simpleCyclicImportTestCase542 = {
|
|
"in.js": `
|
|
import {Test} from './lib';
|
|
export function fn() {
|
|
return 42;
|
|
}
|
|
export const foo = [Test];
|
|
if (Test.method() !== 42) throw 'fail'
|
|
`,
|
|
"lib.js": `
|
|
import {fn} from './in';
|
|
export class Test {
|
|
static method() {
|
|
return fn();
|
|
}
|
|
}
|
|
`,
|
|
};
|
|
itBundled("extra/CyclicImport1", {
|
|
files: simpleCyclicImportTestCase542,
|
|
run: true,
|
|
});
|
|
itBundled("extra/TypeofRequireESM", {
|
|
files: {
|
|
"in.js": `check(typeof require)`,
|
|
"runtime.js": `
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
import url from 'url'
|
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
|
|
const out = fs.readFileSync(__dirname + '/out.js', 'utf8')
|
|
const check = x => value = x
|
|
let value
|
|
new Function('check', 'require', out)(check)
|
|
if (value !== 'function') throw 'fail'
|
|
`,
|
|
},
|
|
run: { file: "runtime.js" },
|
|
});
|
|
itBundled("extra/CJSExport1", {
|
|
files: {
|
|
"in.js": `const out = require('./foo'); if (out.__esModule || out.foo !== 123) throw 'fail'`,
|
|
"foo.js": `exports.foo = 123`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSExport2", {
|
|
files: {
|
|
"in.js": `const out = require('./foo'); if (out.__esModule || out !== 123) throw 'fail'`,
|
|
"foo.js": `module.exports = 123`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSExport3", {
|
|
files: {
|
|
"in.js": `const out = require('./foo'); if (!out.__esModule || out.foo !== 123) throw 'fail'`,
|
|
"foo.js": `export const foo = 123`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSExport4", {
|
|
files: {
|
|
"in.js": `const out = require('./foo'); if (!out.__esModule || out.default !== 123) throw 'fail'`,
|
|
"foo.js": `export default 123`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSExport5", {
|
|
files: {
|
|
"in.js": `const out = require('./foo'); if (!out.__esModule || out.default !== null) throw 'fail'`,
|
|
"foo.js": `export default function x() {} x = null`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSExport6", {
|
|
files: {
|
|
"in.js": `const out = require('./foo'); if (!out.__esModule || out.default !== null) throw 'fail'`,
|
|
"foo.js": `export default class x {} x = null`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSExport7", {
|
|
files: {
|
|
"in.js": `
|
|
// This is the JavaScript generated by "tsc" for the following TypeScript:
|
|
//
|
|
// import fn from './foo'
|
|
// if (typeof fn !== 'function') throw 'fail'
|
|
//
|
|
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const foo_1 = __importDefault(require("./foo"));
|
|
if (typeof foo_1.default !== 'function')
|
|
throw 'fail';
|
|
`,
|
|
"foo.js": `export default function fn() {}`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSSelfExport1", {
|
|
files: {
|
|
"in.js": `exports.foo = 123; const out = require('./in'); if (out.__esModule || out.foo !== 123) throw 'fail'`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSSelfExport2", {
|
|
files: {
|
|
"in.js": `module.exports = 123; const out = require('./in'); if (out.__esModule || out !== 123) throw 'fail'`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSSelfExport3", {
|
|
files: {
|
|
"in.js": `export const foo = 123; const out = require('./in'); if (!out.__esModule || out.foo !== 123) throw 'fail'`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSSelfExport4", {
|
|
files: {
|
|
"in.js": `export const foo = 123; const out = require('./in'); if (!out.__esModule || out.foo !== 123) throw 'fail'`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSSelfExport5", {
|
|
files: {
|
|
"in.js": `export default 123; const out = require('./in'); if (!out.__esModule || out.default !== 123) throw 'fail'`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSSelfExport6", {
|
|
files: {
|
|
"in.js": `export const foo = 123; const out = require('./in'); if (!out.__esModule || out.foo !== 123) throw 'fail'`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSSelfExport7", {
|
|
files: {
|
|
"in.js": `export const foo = 123; const out = require('./in'); if (!out.__esModule || out.foo !== 123) throw 'fail'`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSSelfExport8", {
|
|
files: {
|
|
"in.js": `export default 123; const out = require('./in'); if (!out.__esModule || out.default !== 123) throw 'fail'`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/DoubleExportStar1", {
|
|
files: {
|
|
"node.ts": `
|
|
import {a, b} from './re-export'
|
|
if (a !== 'a' || b !== 'b') throw 'fail'
|
|
`,
|
|
"re-export.ts": `
|
|
export * from './a'
|
|
export * from './b'
|
|
`,
|
|
"a.ts": `
|
|
export let a = 'a'
|
|
`,
|
|
"b.ts": `
|
|
export let b = 'b'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/DoubleExportStar2", {
|
|
files: {
|
|
"node.ts": `
|
|
import {a, b} from './re-export'
|
|
if (a !== 'a' || b !== 'b') throw 'fail'
|
|
|
|
// Try forcing all of these modules to be wrappers
|
|
require('./node')
|
|
require('./re-export')
|
|
require('./a')
|
|
require('./b')
|
|
`,
|
|
"re-export.ts": `
|
|
export * from './a'
|
|
export * from './b'
|
|
`,
|
|
"a.ts": `
|
|
export let a = 'a'
|
|
`,
|
|
"b.ts": `
|
|
export let b = 'b'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/DoubleExportStar3", {
|
|
files: {
|
|
"node.ts": `
|
|
import {a, b, c, d} from './re-export'
|
|
if (a !== 'a' || b !== 'b' || c !== 'c' || d !== 'd') throw 'fail'
|
|
|
|
// Try forcing all of these modules to be wrappers
|
|
require('./node')
|
|
require('./re-export')
|
|
require('./a')
|
|
require('./b')
|
|
`,
|
|
"re-export.ts": `
|
|
export * from './a'
|
|
export * from './b'
|
|
export * from './d'
|
|
`,
|
|
"a.ts": `
|
|
export let a = 'a'
|
|
`,
|
|
"b.ts": `
|
|
exports.b = 'b'
|
|
`,
|
|
"c.ts": `
|
|
exports.c = 'c'
|
|
`,
|
|
"d.ts": `
|
|
export * from './c'
|
|
export let d = 'd'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
// Complex circular bundled and non-bundled import case (https://github.com/evanw/esbuild/issues/758)
|
|
itBundled("extra/ESBuildIssue758", {
|
|
files: {
|
|
"node.ts": `
|
|
import {a} from './re-export'
|
|
let fn = a()
|
|
if (fn === a || fn() !== a) throw 'fail'
|
|
`,
|
|
"re-export.ts": `
|
|
export * from './a'
|
|
`,
|
|
"a.ts": `
|
|
import {b} from './b'
|
|
export let a = () => b
|
|
`,
|
|
"b.ts": `
|
|
import {a} from './re-export'
|
|
export let b = () => a
|
|
`,
|
|
},
|
|
format: "cjs",
|
|
run: true,
|
|
});
|
|
itBundled("extra/ESBuildIssue1894", {
|
|
files: {
|
|
"in.ts": `
|
|
export * from './a.cjs'
|
|
import * as inner from './inner.js'
|
|
export { inner }
|
|
`,
|
|
"inner.ts": `export * from './b.cjs'`,
|
|
"a.cjs": `exports.a = 'a'`,
|
|
"b.cjs": `exports.b = 'b'`,
|
|
"node.js": `
|
|
const out = require('./out.js')
|
|
if (out.a !== 'a' || out.inner === void 0 || out.inner.b !== 'b' || out.b !== void 0) throw 'fail'
|
|
`,
|
|
},
|
|
format: "cjs",
|
|
run: true,
|
|
});
|
|
// Validate internal and external export correctness regarding "__esModule".
|
|
// An ES module importing itself should not see "__esModule". But a CommonJS
|
|
// module importing an ES module should see "__esModule".
|
|
itBundled("extra/ESModuleSelfImport1", {
|
|
files: {
|
|
"in.ts": `
|
|
export * from './a.cjs'
|
|
import * as us from './in.js'
|
|
if (us.a !== 'a' || us.__esModule !== void 0) throw 'fail'
|
|
`,
|
|
"a.cjs": `exports.a = 'a'`,
|
|
"node.js": `
|
|
const out = require('./out.js')
|
|
if (out.a !== 'a' || out.__esModule !== true) throw 'fail'
|
|
`,
|
|
},
|
|
format: "cjs",
|
|
run: { file: "node.js" },
|
|
});
|
|
|
|
// Use "eval" to access CommonJS variables
|
|
itBundled("extra/CJSEval1", {
|
|
files: {
|
|
"in.js": `if (require('./eval').foo !== 123) throw 'fail'`,
|
|
"eval.js": `exports.foo=234;eval('exports.foo = 123')`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CJSEval2", {
|
|
files: {
|
|
"in.js": `if (require('./eval').foo !== 123) throw 'fail'`,
|
|
"eval.js": `module.exports={foo:234};eval('module.exports = {foo: 123}')`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/EnumerableFalse1", {
|
|
files: {
|
|
"in.js": `
|
|
import {foo} from './esm'
|
|
if (foo !== 123) throw 'fail'
|
|
`,
|
|
"esm.js": `Object.defineProperty(exports, 'foo', {value: 123, enumerable: false})`,
|
|
},
|
|
run: true,
|
|
});
|
|
// Test imports not being able to access the namespace object
|
|
itBundled("extra/EnumerableFalse2", {
|
|
files: {
|
|
"in.js": `
|
|
import * as ns from './esm'
|
|
if (ns[Math.random() < 2 && 'foo'] !== 123) throw 'fail'
|
|
`,
|
|
"esm.js": `Object.defineProperty(exports, 'foo', {value: 123, enumerable: false})`,
|
|
},
|
|
run: true,
|
|
});
|
|
// Test imports of properties from the prototype chain of "module.exports" for Webpack compatibility
|
|
itBundled("extra/PrototypeChain1", {
|
|
files: {
|
|
"in.js": `
|
|
import def from './cjs-proto'
|
|
import {prop} from './cjs-proto'
|
|
if (def.prop !== 123 || prop !== 123) throw 'fail'
|
|
`,
|
|
"cjs-proto.js": `module.exports = Object.create({prop: 123})`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/PrototypeChain2", {
|
|
files: {
|
|
"in.js": `
|
|
import def, {prop} from './cjs-proto' // The TypeScript compiler fails with this syntax
|
|
if (def.prop !== 123 || prop !== 123) throw 'fail'
|
|
`,
|
|
"cjs-proto.js": `module.exports = Object.create({prop: 123})`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/PrototypeChain2", {
|
|
files: {
|
|
"in.js": `
|
|
import * as star from './cjs-proto'
|
|
if (!star.default || star.default.prop !== 123 || star.prop !== 123) throw 'fail'
|
|
`,
|
|
"cjs-proto.js": `module.exports = Object.create({prop: 123})`,
|
|
},
|
|
run: true,
|
|
});
|
|
// This shouldn't cause a syntax error
|
|
// https://github.com/evanw/esbuild/issues/1082
|
|
itBundled("extra/ReturnDynamicImport", {
|
|
files: {
|
|
"in.js": `
|
|
async function foo() {
|
|
return import('./second.js')
|
|
}
|
|
`,
|
|
"second.js": `
|
|
export default 123
|
|
`,
|
|
"third.js": `
|
|
export default 123
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
// Check for file names of wrapped modules in non-minified stack traces (for profiling)
|
|
// Context: https://github.com/evanw/esbuild/pull/1236
|
|
itBundled("extra/UnminifiedNamedModuleFunctions1", {
|
|
todo: true,
|
|
files: {
|
|
"entry.js": `
|
|
try {
|
|
require('./src/a')
|
|
} catch (e) {
|
|
if (!e.stack.includes("__require") || !e.stack.includes("src/a.ts") || !e.stack.includes("src/b.ts"))
|
|
throw new Error(e.stack)
|
|
}
|
|
`,
|
|
"src/a.ts": `require('./b')`,
|
|
"src/b.ts": `throw new Error('fail')`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/UnminifiedNamedModuleFunctions2", {
|
|
minifyIdentifiers: true,
|
|
files: {
|
|
"entry.js": `
|
|
try {
|
|
require('./src/a')
|
|
} catch (e) {
|
|
if (e.stack.includes('__require') || e.stack.includes('src/a.ts') || e.stack.includes('src/b.ts'))
|
|
throw new Error(e.stack)
|
|
}
|
|
`,
|
|
"src/a.ts": `require('./b')`,
|
|
"src/b.ts": `throw new Error('fail')`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/UnminifiedNamedModuleFunctions3", {
|
|
todo: true,
|
|
files: {
|
|
"entry.js": `
|
|
try {
|
|
require('./src/a')
|
|
} catch (e) {
|
|
if (!e.stack.includes('__init') || !e.stack.includes('src/a.ts') || !e.stack.includes('src/b.ts'))
|
|
throw new Error(e.stack)
|
|
}
|
|
`,
|
|
"src/a.ts": `export let esm = true; require('./b')`,
|
|
"src/b.ts": `export let esm = true; throw new Error('fail')`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/UnminifiedNamedModuleFunctions4", {
|
|
minifyIdentifiers: true,
|
|
files: {
|
|
"entry.js": `
|
|
try {
|
|
require('./src/a')
|
|
} catch (e) {
|
|
if (e.stack.includes('__init') || e.stack.includes('src/a.ts') || e.stack.includes('src/b.ts'))
|
|
throw new Error(e.stack)
|
|
}
|
|
`,
|
|
"src/a.ts": `export let esm = true; require('./b')`,
|
|
"src/b.ts": `export let esm = true; throw new Error('fail')`,
|
|
},
|
|
run: true,
|
|
});
|
|
// Define shouldnt crash
|
|
// https://github.com/evanw/esbuild/issues/1080
|
|
itBundled("extra/DefineObject1", {
|
|
files: {
|
|
"in.js": `if (foo.x !== 0) throw 'fail'; this;`,
|
|
},
|
|
define: { foo: '{"x":0}' },
|
|
run: true,
|
|
});
|
|
itBundled("extra/DefineObject2", {
|
|
files: {
|
|
"in.js": `if (foo.bar.x !== 0) throw 'fail'; this;`,
|
|
},
|
|
define: { "foo.bar": '{"x":0}' },
|
|
run: true,
|
|
});
|
|
itBundled("extra/DefineModule1", {
|
|
files: {
|
|
"in.js": `if (module.x !== void 0) throw 'fail'; this;`,
|
|
},
|
|
define: { module: '{"x":0}' },
|
|
run: true,
|
|
});
|
|
itBundled("extra/DefineModule2", {
|
|
files: {
|
|
"in.js": `if (module.foo !== void 0) throw 'fail'; this;`,
|
|
},
|
|
define: { "module.foo": '{"x":0}' },
|
|
run: true,
|
|
});
|
|
itBundled("extra/DefineExports1", {
|
|
todo: true,
|
|
files: {
|
|
"in.js": `if (exports.x !== void 0) throw 'fail'; this;`,
|
|
},
|
|
define: { exports: '{"x":0}' },
|
|
run: true,
|
|
});
|
|
itBundled("extra/DefineExports2", {
|
|
todo: true,
|
|
files: {
|
|
"in.js": `if (exports.foo !== void 0) throw 'fail'; this;`,
|
|
},
|
|
define: { "exports.foo": '{"x":0}' },
|
|
run: true,
|
|
});
|
|
|
|
itBundled("extra/DefineArray", {
|
|
files: {
|
|
"in.js": `if (foo[0] !== 'x') throw 'fail'; this`,
|
|
},
|
|
define: { foo: '["x"]' },
|
|
run: true,
|
|
});
|
|
itBundled("extra/DefineArray2", {
|
|
files: {
|
|
"in.js": `if (foo.bar[0] !== 'x') throw 'fail'; this`,
|
|
},
|
|
define: { "foo.bar": '["x"]' },
|
|
run: true,
|
|
});
|
|
itBundled("extra/DefineModuleArray1", {
|
|
files: {
|
|
"in.js": `if (module[0] !== void 0) throw 'fail'; this`,
|
|
},
|
|
define: { module: '["x"]' },
|
|
run: true,
|
|
});
|
|
itBundled("extra/DefineModuleArray2", {
|
|
files: {
|
|
"in.js": `if (module.foo !== void 0) throw 'fail'; this`,
|
|
},
|
|
define: { "module.foo": '["x"]' },
|
|
run: true,
|
|
});
|
|
itBundled("extra/DefineExportsArray1", {
|
|
files: {
|
|
"in.js": `if (exports[0] !== void 0) throw 'fail'; this`,
|
|
},
|
|
define: { exports: '["x"]' },
|
|
run: true,
|
|
});
|
|
itBundled("extra/DefineExportsArray2", {
|
|
todo: true,
|
|
files: {
|
|
"in.js": `if (exports.foo !== void 0) throw 'fail'; this`,
|
|
},
|
|
define: { "exports.foo": '["x"]' },
|
|
run: true,
|
|
});
|
|
|
|
// Various ESM cases
|
|
itBundled("extra/CatchScope1", {
|
|
files: {
|
|
"in.js": `
|
|
var x = 0, y = []
|
|
try {
|
|
throw 1
|
|
} catch (x) {
|
|
y.push(x)
|
|
var x = 2
|
|
y.push(x)
|
|
}
|
|
y.push(x)
|
|
if (y + '' !== '1,2,0') throw 'fail: ' + y
|
|
`,
|
|
},
|
|
minifyIdentifiers: true,
|
|
minifySyntax: true,
|
|
minifyWhitespace: true,
|
|
run: true,
|
|
});
|
|
itBundled("extra/CatchScope2", {
|
|
files: {
|
|
"in.js": `
|
|
var x = 0, y = []
|
|
try {
|
|
throw 1
|
|
} catch (x) {
|
|
y.push(x)
|
|
var x = 2
|
|
y.push(x)
|
|
}
|
|
finally { x = 3 }
|
|
y.push(x)
|
|
if (y + '' !== '1,2,3') throw 'fail: ' + y
|
|
`,
|
|
},
|
|
minifyIdentifiers: true,
|
|
minifySyntax: true,
|
|
minifyWhitespace: true,
|
|
run: true,
|
|
});
|
|
itBundled("extra/CatchScope3", {
|
|
files: {
|
|
"in.js": `
|
|
var y = []
|
|
try {
|
|
throw 1
|
|
} catch (x) {
|
|
y.push(x)
|
|
var x = 2
|
|
y.push(x)
|
|
}
|
|
y.push(x)
|
|
if (y + '' !== '1,2,') throw 'fail: ' + y
|
|
`,
|
|
},
|
|
minifyIdentifiers: true,
|
|
minifySyntax: true,
|
|
minifyWhitespace: true,
|
|
run: true,
|
|
});
|
|
itBundled("extra/CatchScope4", {
|
|
files: {
|
|
"in.js": `
|
|
var y = []
|
|
try {
|
|
throw 1
|
|
} catch (x) {
|
|
y.push(x)
|
|
x = 2
|
|
y.push(x)
|
|
}
|
|
y.push(typeof x)
|
|
if (y + '' !== '1,2,undefined') throw 'fail: ' + y
|
|
`,
|
|
},
|
|
minifyIdentifiers: true,
|
|
minifySyntax: true,
|
|
minifyWhitespace: true,
|
|
run: true,
|
|
});
|
|
itBundled("extra/CatchScope5", {
|
|
files: {
|
|
"in.js": `
|
|
var y = []
|
|
try {
|
|
throw 1
|
|
} catch (x) {
|
|
y.push(x)
|
|
try {
|
|
throw 2
|
|
} catch (x) {
|
|
y.push(x)
|
|
var x = 3
|
|
y.push(x)
|
|
}
|
|
y.push(x)
|
|
}
|
|
y.push(x)
|
|
if (y + '' !== '1,2,3,1,') throw 'fail: ' + y
|
|
`,
|
|
},
|
|
minifyIdentifiers: true,
|
|
minifySyntax: true,
|
|
minifyWhitespace: true,
|
|
run: true,
|
|
});
|
|
itBundled("extra/CatchScope6", {
|
|
files: {
|
|
"in.js": `
|
|
var y = []
|
|
try { x; y.push('fail') } catch (e) {}
|
|
try {
|
|
throw 1
|
|
} catch (x) {
|
|
y.push(x)
|
|
}
|
|
try { x; y.push('fail') } catch (e) {}
|
|
if (y + '' !== '1') throw 'fail: ' + y
|
|
`,
|
|
},
|
|
minifyIdentifiers: true,
|
|
minifySyntax: true,
|
|
minifyWhitespace: true,
|
|
run: true,
|
|
});
|
|
|
|
// https://github.com/evanw/esbuild/issues/1812
|
|
itBundled("extra/CatchScope7", {
|
|
files: {
|
|
"in.js": `
|
|
let a = 1;
|
|
let def = "PASS2";
|
|
try {
|
|
throw [ "FAIL2", "PASS1" ];
|
|
} catch ({ [a]: b, 3: d = def }) {
|
|
let a = 0, def = "FAIL3";
|
|
if (b !== 'PASS1' || d !== 'PASS2') throw 'fail: ' + b + ' ' + d
|
|
}
|
|
`,
|
|
},
|
|
minifyIdentifiers: true,
|
|
minifySyntax: true,
|
|
minifyWhitespace: true,
|
|
run: true,
|
|
});
|
|
itBundled("extra/CatchScope8", {
|
|
files: {
|
|
"in.js": `
|
|
let a = 1;
|
|
let def = "PASS2";
|
|
try {
|
|
throw [ "FAIL2", "PASS1" ];
|
|
} catch ({ [a]: b, 3: d = def }) {
|
|
let a = 0, def = "FAIL3";
|
|
if (b !== 'PASS1' || d !== 'PASS2') throw 'fail: ' + b + ' ' + d
|
|
}
|
|
`,
|
|
},
|
|
minifyIdentifiers: true,
|
|
minifySyntax: true,
|
|
minifyWhitespace: true,
|
|
run: true,
|
|
});
|
|
itBundled("extra/CatchScope9", {
|
|
files: {
|
|
"in.js": `
|
|
try {
|
|
throw { x: 'z', z: 123 }
|
|
} catch ({ x, [x]: y }) {
|
|
if (y !== 123) throw 'fail'
|
|
}
|
|
`,
|
|
},
|
|
minifyIdentifiers: true,
|
|
minifySyntax: true,
|
|
minifyWhitespace: true,
|
|
run: true,
|
|
});
|
|
itBundled("extra/CyclicImport2", {
|
|
files: {
|
|
"entry.js": `import * as foo from './foo'; export default {foo, bar: require('./bar')}`,
|
|
"foo.js": `import * as a from './entry'; import * as b from './bar'; export default {a, b}`,
|
|
"bar.js": `const entry = require('./entry'); export function foo() { return entry }`,
|
|
},
|
|
});
|
|
|
|
// Test certain minification transformations
|
|
for (const minify of [
|
|
{
|
|
value: {
|
|
minifyIdentifiers: true,
|
|
minifySyntax: true,
|
|
minifyWhitespace: true,
|
|
},
|
|
label: "Minify",
|
|
},
|
|
{ value: {}, label: "" },
|
|
]) {
|
|
itBundled(`extra/${minify.label || "NoMinify"}1`, {
|
|
files: {
|
|
"in.js": `let fn = (x) => { if (x && y) return; function y() {} throw 'fail' }; fn(fn)`,
|
|
},
|
|
...minify.value,
|
|
});
|
|
itBundled(`extra/${minify.label || "NoMinify"}2`, {
|
|
files: {
|
|
"in.js": `let fn = (a, b) => { if (a && (x = () => y) && b) return; var x; let y = 123; if (x() !== 123) throw 'fail' }; fn(fn)`,
|
|
},
|
|
...minify.value,
|
|
});
|
|
|
|
for (const { access, label } of [
|
|
{
|
|
access: ".a",
|
|
label: minify.label + "DotAccess",
|
|
},
|
|
{
|
|
access: "['a']",
|
|
label: minify.label + "BracketAccess",
|
|
},
|
|
]) {
|
|
function add(n: number, files: Record<string, string>) {
|
|
itBundled(`extra/${label}${n}`, {
|
|
files,
|
|
run: true,
|
|
target: "bun",
|
|
});
|
|
}
|
|
add(1, {
|
|
"in.js": `if ({a: 1}${access} !== 1) throw 'fail'`,
|
|
});
|
|
add(2, {
|
|
"in.js": `if ({a: {a: 1}}${access}${access} !== 1) throw 'fail'`,
|
|
});
|
|
add(3, {
|
|
"in.js": `if ({a: {b: 1}}${access}.b !== 1) throw 'fail'`,
|
|
});
|
|
add(4, {
|
|
"in.js": `if ({b: {a: 1}}.b${access} !== 1) throw 'fail'`,
|
|
});
|
|
add(5, {
|
|
"in.js": `if ({a: 1, a: 2}${access} !== 2) throw 'fail'`,
|
|
});
|
|
add(6, {
|
|
"in.js": `if ({a: 1, [String.fromCharCode(97)]: 2}${access} !== 2) throw 'fail'`,
|
|
});
|
|
add(7, {
|
|
"in.js": `let a = {a: 1}; if ({...a}${access} !== 1) throw 'fail'`,
|
|
});
|
|
add(8, {
|
|
"in.js": `if ({ get a() { return 1 } }${access} !== 1) throw 'fail'`,
|
|
});
|
|
add(9, {
|
|
"in.js": `if ({ __proto__: {a: 1} }${access} !== 1) throw 'fail'`,
|
|
});
|
|
add(10, {
|
|
"in.js": `if ({ __proto__: null, a: 1 }${access} !== 1) throw 'fail'`,
|
|
});
|
|
add(11, {
|
|
"in.js": `if ({ __proto__: null, b: 1 }${access} !== void 0) throw 'fail'`,
|
|
});
|
|
add(12, {
|
|
"in.js": `if ({ __proto__: null }.__proto__ !== void 0) throw 'fail'`,
|
|
});
|
|
add(13, {
|
|
"in.js": `if ({ ['__proto__']: null }.__proto__ !== null) throw 'fail'`,
|
|
});
|
|
add(14, {
|
|
"in.js": `let x = 100; if ({ b: ++x, a: 1 }${access} !== 1 || x !== 101) throw 'fail'`,
|
|
});
|
|
add(15, {
|
|
"in.js": `if ({ a: function() { return this.b }, b: 1 }${access}() !== 1) throw 'fail'`,
|
|
});
|
|
add(16, {
|
|
"in.js": `if ({ a: function() { return this.b }, b: 1 }${access}\`\` !== 1) throw 'fail'`,
|
|
});
|
|
add(17, {
|
|
"in.js": `if (({a: 2}${access} = 1) !== 1) throw 'fail'`,
|
|
});
|
|
add(18, {
|
|
"in.js": `if ({a: 1}${access}++ !== 1) throw 'fail'`,
|
|
});
|
|
add(19, {
|
|
"in.js": `if (++{a: 1}${access} !== 2) throw 'fail'`,
|
|
});
|
|
add(20, {
|
|
"in.js": `
|
|
Object.defineProperty(Object.prototype, 'MIN_OBJ_LIT', {value: 1})
|
|
if ({}.MIN_OBJ_LIT !== 1) throw 'fail'
|
|
`,
|
|
});
|
|
add(21, {
|
|
"in.js": `
|
|
let x = false
|
|
function y() { x = true }
|
|
if ({ b: y(), a: 1 }${access} !== 1 || !x) throw 'fail'
|
|
`,
|
|
});
|
|
add(22, {
|
|
"in.js": `
|
|
try { new ({ a() {} }${access}); throw 'fail' }
|
|
catch (e) { if (e === 'fail') throw e }
|
|
`,
|
|
});
|
|
add(22, {
|
|
"in.js": `
|
|
let x = 1;
|
|
({ set a(y) { x = y } }${access} = 2);
|
|
if (x !== 2) throw 'fail'
|
|
`,
|
|
});
|
|
}
|
|
|
|
// Check try/catch simplification
|
|
itBundled(`extra/${minify.label || "NoMinify"}CatchScope1`, {
|
|
files: {
|
|
"in.js": `
|
|
try {
|
|
try {
|
|
throw 0
|
|
} finally {
|
|
var x = 1
|
|
}
|
|
} catch {
|
|
}
|
|
if (x !== 1) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/${minify.label || "NoMinify"}CatchScope2`, {
|
|
files: {
|
|
"in.js": `
|
|
let y
|
|
try {
|
|
throw 1
|
|
} catch (x) {
|
|
eval('y = x')
|
|
}
|
|
if (y !== 1) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/${minify.label || "NoMinify"}CatchScope3`, {
|
|
files: {
|
|
"in.js": `
|
|
try {
|
|
throw 0
|
|
} catch (x) {
|
|
var x = 1
|
|
}
|
|
if (x !== void 0) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/${minify.label || "NoMinify"}CatchScope4`, {
|
|
files: {
|
|
"in.js": `
|
|
let works
|
|
try {
|
|
throw { get a() { works = true } }
|
|
} catch ({ a }) {}
|
|
if (!works) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/${minify.label || "NoMinify"}CatchScope5`, {
|
|
files: {
|
|
"in.js": `
|
|
let works
|
|
try {
|
|
throw { *[Symbol.iterator]() { works = true } }
|
|
} catch ([x]) {
|
|
}
|
|
if (!works) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
|
|
// Check variable initializer inlining
|
|
itBundled(`extra/${minify.label || "NoMinify"}VariableInitializerInlining`, {
|
|
files: {
|
|
"in.js": `
|
|
function foo() {
|
|
if (this !== globalThis) throw 'fail'
|
|
}
|
|
function main() {
|
|
let obj = { bar: foo };
|
|
let fn = obj.bar;
|
|
(0, fn)();
|
|
}
|
|
main()
|
|
`,
|
|
},
|
|
});
|
|
// Check global constructor behavior
|
|
itBundled(`extra/${minify.label || "NoMinify"}GlobalConstructorBehavior1`, {
|
|
files: {
|
|
"in.js": `
|
|
const check = (before, after) => {
|
|
if (Boolean(before) !== after) throw 'fail: Boolean(' + before + ') should not be ' + Boolean(before)
|
|
if (new Boolean(before) === after) throw 'fail: new Boolean(' + before + ') should not be ' + new Boolean(before)
|
|
if (new Boolean(before).valueOf() !== after) throw 'fail: new Boolean(' + before + ').valueOf() should not be ' + new Boolean(before).valueOf()
|
|
}
|
|
check(false, false); check(0, false); check(0n, false)
|
|
check(true, true); check(1, true); check(1n, true)
|
|
check(null, false); check(undefined, false)
|
|
check('', false); check('x', true)
|
|
|
|
const checkSpread = (before, after) => {
|
|
if (Boolean(...before) !== after) throw 'fail: Boolean(...' + before + ') should not be ' + Boolean(...before)
|
|
if (new Boolean(...before) === after) throw 'fail: new Boolean(...' + before + ') should not be ' + new Boolean(...before)
|
|
if (new Boolean(...before).valueOf() !== after) throw 'fail: new Boolean(...' + before + ').valueOf() should not be ' + new Boolean(...before).valueOf()
|
|
}
|
|
checkSpread([0], false); check([1], true)
|
|
checkSpread([], false)
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/${minify.label || "NoMinify"}GlobalConstructorBehavior2`, {
|
|
files: {
|
|
"in.js": `
|
|
class ToPrimitive { [Symbol.toPrimitive]() { return '100.001' } }
|
|
const someObject = { toString: () => 123, valueOf: () => 321 }
|
|
|
|
const check = (before, after) => {
|
|
if (Number(before) !== after) throw 'fail: Number(' + before + ') should not be ' + Number(before)
|
|
if (new Number(before) === after) throw 'fail: new Number(' + before + ') should not be ' + new Number(before)
|
|
if (new Number(before).valueOf() !== after) throw 'fail: new Number(' + before + ').valueOf() should not be ' + new Number(before).valueOf()
|
|
}
|
|
check(-1.23, -1.23)
|
|
check('-1.23', -1.23)
|
|
check(123n, 123)
|
|
check(null, 0)
|
|
check(false, 0)
|
|
check(true, 1)
|
|
check(someObject, 321)
|
|
check(new ToPrimitive(), 100.001)
|
|
|
|
const checkSpread = (before, after) => {
|
|
if (Number(...before) !== after) throw 'fail: Number(...' + before + ') should not be ' + Number(...before)
|
|
if (new Number(...before) === after) throw 'fail: new Number(...' + before + ') should not be ' + new Number(...before)
|
|
if (new Number(...before).valueOf() !== after) throw 'fail: new Number(...' + before + ').valueOf() should not be ' + new Number(...before).valueOf()
|
|
}
|
|
checkSpread(['123'], 123)
|
|
checkSpread([], 0)
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/${minify.label || "NoMinify"}GlobalConstructorBehavior3`, {
|
|
files: {
|
|
"in.js": `
|
|
class ToPrimitive { [Symbol.toPrimitive]() { return 100.001 } }
|
|
const someObject = { toString: () => 123, valueOf: () => 321 }
|
|
|
|
const check = (before, after) => {
|
|
if (String(before) !== after) throw 'fail: String(' + before + ') should not be ' + String(before)
|
|
if (new String(before) === after) throw 'fail: new String(' + before + ') should not be ' + new String(before)
|
|
if (new String(before).valueOf() !== after) throw 'fail: new String(' + before + ').valueOf() should not be ' + new String(before).valueOf()
|
|
}
|
|
check('', '')
|
|
check('x', 'x')
|
|
check(null, 'null')
|
|
check(false, 'false')
|
|
check(1.23, '1.23')
|
|
check(-123n, '-123')
|
|
check(someObject, '123')
|
|
check(new ToPrimitive(), '100.001')
|
|
|
|
const checkSpread = (before, after) => {
|
|
if (String(...before) !== after) throw 'fail: String(...' + before + ') should not be ' + String(...before)
|
|
if (new String(...before) === after) throw 'fail: new String(...' + before + ') should not be ' + new String(...before)
|
|
if (new String(...before).valueOf() !== after) throw 'fail: new String(...' + before + ').valueOf() should not be ' + new String(...before).valueOf()
|
|
}
|
|
checkSpread([123], '123')
|
|
checkSpread([], '')
|
|
|
|
const checkAndExpectNewToThrow = (before, after) => {
|
|
if (String(before) !== after) throw 'fail: String(...) should not be ' + String(before)
|
|
try {
|
|
new String(before)
|
|
} catch (e) {
|
|
return
|
|
}
|
|
throw 'fail: new String(...) should not succeed'
|
|
}
|
|
checkAndExpectNewToThrow(Symbol('abc'), 'Symbol(abc)')
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
}
|
|
// Test minification of hoisted top-level symbols declared in nested scopes.
|
|
// Previously this code was incorrectly transformed into this, which crashes:
|
|
//
|
|
// var c = false;
|
|
// var d = function a() {
|
|
// b[a]();
|
|
// };
|
|
// for (var a = 0, b = [() => c = true]; a < b.length; a++) {
|
|
// d();
|
|
// }
|
|
// export default c;
|
|
//
|
|
// The problem is that "var i" is declared in a nested scope but hoisted to
|
|
// the top-level scope. So it's accidentally assigned a nested scope slot
|
|
// even though it's a top-level symbol, not a nested scope symbol.
|
|
itBundled(`extra/ToplevelSymbolHoisting`, {
|
|
files: {
|
|
"in.js": `
|
|
var worked = false
|
|
var loop = function fn() {
|
|
array[i]();
|
|
};
|
|
for (var i = 0, array = [() => worked = true]; i < array.length; i++) {
|
|
loop();
|
|
}
|
|
export default worked
|
|
`,
|
|
"node.js": `
|
|
import worked from './out.js'
|
|
if (!worked) throw 'fail'
|
|
`,
|
|
},
|
|
run: { file: "node.js" },
|
|
});
|
|
// Test hoisting variables inside for loop initializers outside of lazy ESM
|
|
// wrappers. Previously this didn't work due to a bug that considered for
|
|
// loop initializers to already be in the top-level scope. For more info
|
|
// see: https://github.com/evanw/esbuild/issues/1455.
|
|
itBundled(`extra/ForLoopInitializerHoisting1`, {
|
|
files: {
|
|
"in.js": `
|
|
if (require('./nested').foo() !== 10) throw 'fail'
|
|
`,
|
|
"nested.js": `
|
|
for (var i = 0; i < 10; i++) ;
|
|
export function foo() { return i }
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/ForLoopInitializerHoisting2`, {
|
|
files: {
|
|
"in.js": `
|
|
if (require('./nested').foo() !== 'c') throw 'fail'
|
|
`,
|
|
"nested.js": `
|
|
for (var i in {a: 1, b: 2, c: 3}) ;
|
|
export function foo() { return i }
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/ForLoopInitializerHoisting3`, {
|
|
files: {
|
|
"in.js": `
|
|
if (require('./nested').foo() !== 3) throw 'fail'
|
|
`,
|
|
"nested.js": `
|
|
for (var i of [1, 2, 3]) ;
|
|
export function foo() { return i }
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
|
|
// Test tree shaking
|
|
itBundled(`extra/TreeShaking1`, {
|
|
files: {
|
|
"entry.js": `import * as foo from './foo'; if (global.dce0 !== 123 || foo.abc !== 'abc') throw 'fail'`,
|
|
"foo/index.js": `global.dce0 = 123; export const abc = 'abc'`,
|
|
"foo/package.json": `{ "sideEffects": false }`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/TreeShaking2`, {
|
|
files: {
|
|
"entry.js": `import * as foo from './foo'; if (global.dce1 !== void 0) throw 'fail'`,
|
|
"foo/index.js": `global.dce1 = 123; export const abc = 'abc'`,
|
|
"foo/package.json": `{ "sideEffects": false }`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/TreeShaking3`, {
|
|
files: {
|
|
"entry.js": `import * as foo from './foo'; if (global.dce2 !== 123) throw 'fail'`,
|
|
"foo/index.js": `global.dce2 = 123; export const abc = 'abc'`,
|
|
"foo/package.json": `{ "sideEffects": true }`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/TreeShaking4`, {
|
|
files: {
|
|
"entry.js": `import foo from './foo'; if (global.dce3 !== 123 || foo.abc !== 'abc') throw 'fail'`,
|
|
"foo/index.js": `global.dce3 = 123; exports.abc = 'abc'`,
|
|
"foo/package.json": `{ "sideEffects": false }`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/TreeShaking5`, {
|
|
files: {
|
|
"entry.js": `import foo from './foo'; if (global.dce4 !== void 0) throw 'fail'`,
|
|
"foo/index.js": `global.dce4 = 123; exports.abc = 'abc'`,
|
|
"foo/package.json": `{ "sideEffects": false }`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/TreeShaking6`, {
|
|
files: {
|
|
"entry.js": `import foo from './foo'; if (global.dce5 !== 123) throw 'fail'`,
|
|
"foo/index.js": `global.dce5 = 123; exports.abc = 'abc'`,
|
|
"foo/package.json": `{ "sideEffects": true }`,
|
|
},
|
|
run: true,
|
|
});
|
|
// Note: Tree shaking this could technically be considered incorrect because
|
|
// the import is for a property whose getter in this case has a side effect.
|
|
// However, this is very unlikely and the vast majority of the time people
|
|
// would likely rather have the code be tree-shaken. This test case enforces
|
|
// the technically incorrect behavior as documentation that this edge case
|
|
// is being ignored.
|
|
itBundled(`extra/TreeShaking7`, {
|
|
files: {
|
|
"entry.js": `import {foo, bar} from './foo'; let unused = foo; if (bar) throw 'expected "foo" to be tree-shaken'`,
|
|
"foo.js": `module.exports = {get foo() { module.exports.bar = 1 }, bar: 0}`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/TreeShaking8`, {
|
|
files: {
|
|
"entry.js": `import './foo'; if (global.dce6 !== 123) throw 'fail'`,
|
|
"foo/dir/x.js": `global.dce6 = 123`,
|
|
"foo/package.json": `{ "main": "dir/x", "sideEffects": ["x.*"] }`,
|
|
},
|
|
skipIfWeDidNotImplementWildcardSideEffects: true,
|
|
run: true,
|
|
});
|
|
itBundled(`extra/TreeShaking9`, {
|
|
files: {
|
|
"entry.js": `import './foo'; if (global.dce6 !== 123) throw 'fail'`,
|
|
"foo/dir/x.js": `global.dce6 = 123`,
|
|
"foo/package.json": `{ "main": "dir/x", "sideEffects": ["**/x.*"] }`,
|
|
},
|
|
skipIfWeDidNotImplementWildcardSideEffects: true,
|
|
run: true,
|
|
});
|
|
itBundled(`extra/TreeShaking10`, {
|
|
todo: true,
|
|
files: {
|
|
"entry.js": `
|
|
let [a] = {}; // This must not be tree-shaken
|
|
`,
|
|
"node.js": `
|
|
pass: {
|
|
try {
|
|
require('./out.js')
|
|
} catch (e) {
|
|
break pass
|
|
}
|
|
throw 'fail'
|
|
}
|
|
`,
|
|
},
|
|
run: { file: "node.js" },
|
|
});
|
|
itBundled(`extra/TreeShaking11`, {
|
|
todo: true,
|
|
files: {
|
|
"entry.js": `
|
|
let sideEffect = false
|
|
let { a } = { // This must not be tree-shaken
|
|
get a() {
|
|
sideEffect = true
|
|
},
|
|
};
|
|
if (!sideEffect) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
|
|
// Test obscure CommonJS symbol edge cases
|
|
itBundled(`extra/CommonJSSymbol1`, {
|
|
files: {
|
|
"in.js": `const ns = require('./foo'); if (ns.foo !== 123 || ns.bar !== 123) throw 'fail'`,
|
|
"foo.js": `var exports, module; module.exports.foo = 123; exports.bar = exports.foo`,
|
|
},
|
|
});
|
|
itBundled(`extra/CommonJSSymbol2`, {
|
|
files: {
|
|
"in.js": `require('./foo'); require('./bar')`,
|
|
"foo.js": `let exports; if (exports !== void 0) throw 'fail'`,
|
|
"bar.js": `let module; if (module !== void 0) throw 'fail'`,
|
|
},
|
|
});
|
|
itBundled(`extra/CommonJSSymbol3`, {
|
|
files: {
|
|
"in.js": `const ns = require('./foo'); if (ns.foo !== void 0 || ns.default.foo !== 123) throw 'fail'`,
|
|
"foo.js": `var exports = {foo: 123}; export default exports`,
|
|
},
|
|
});
|
|
itBundled(`extra/CommonJSSymbol4`, {
|
|
files: {
|
|
"in.js": `const ns = require('./foo'); if (ns !== 123) throw 'fail'`,
|
|
"foo.ts": `let module = 123; export = module`,
|
|
},
|
|
});
|
|
itBundled(`extra/CommonJSSymbol5`, {
|
|
files: {
|
|
"in.js": `require('./foo')`,
|
|
"foo.js": `var require; if (require !== void 0) throw 'fail'`,
|
|
},
|
|
});
|
|
itBundled(`extra/CommonJSSymbol6`, {
|
|
files: {
|
|
"in.js": `require('./foo')`,
|
|
"foo.js": `var require = x => x; if (require('does not exist') !== 'does not exist') throw 'fail'`,
|
|
},
|
|
});
|
|
itBundled(`extra/CommonJSSymbol7`, {
|
|
files: {
|
|
"in.js": `const ns = require('./foo'); if (ns.a !== 123 || ns.b.a !== 123) throw 'fail'`,
|
|
"foo.js": `exports.a = 123; exports.b = this`,
|
|
},
|
|
});
|
|
itBundled(`extra/CommonJSSymbol8`, {
|
|
files: {
|
|
"in.js": `const ns = require('./foo'); if (ns.a !== 123 || ns.b !== void 0) throw 'fail'`,
|
|
"foo.js": `export let a = 123, b = this`,
|
|
},
|
|
});
|
|
|
|
// Function hoisting tests
|
|
itBundled(`extra/FunctionHoisting1`, {
|
|
files: {
|
|
"in.js": `
|
|
if (1) {
|
|
function f() {
|
|
return f
|
|
}
|
|
f = null
|
|
}
|
|
if (typeof f !== 'function' || f() !== null) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting2`, {
|
|
files: {
|
|
"in.js": `
|
|
'use strict'
|
|
if (1) {
|
|
function f() {
|
|
return f
|
|
}
|
|
f = null
|
|
}
|
|
if (typeof f !== 'undefined') throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting3`, {
|
|
files: {
|
|
"in.js": `
|
|
export {}
|
|
if (1) {
|
|
function f() {
|
|
return f
|
|
}
|
|
f = null
|
|
}
|
|
if (typeof f !== 'undefined') throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting4`, {
|
|
files: {
|
|
"in.js": `
|
|
if (1) {
|
|
function f() {
|
|
return f
|
|
}
|
|
f = null
|
|
}
|
|
if (typeof f !== 'function' || f() !== null) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting5`, {
|
|
files: {
|
|
"in.js": `
|
|
var f
|
|
if (1) {
|
|
function f() {
|
|
return f
|
|
}
|
|
f = null
|
|
}
|
|
if (typeof f !== 'function' || f() !== null) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting6`, {
|
|
files: {
|
|
"in.js": `
|
|
'use strict'
|
|
if (1) {
|
|
function f() {
|
|
return f
|
|
}
|
|
}
|
|
if (typeof f !== 'undefined') throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting7`, {
|
|
files: {
|
|
"in.js": `
|
|
export {}
|
|
if (1) {
|
|
function f() {
|
|
return f
|
|
}
|
|
}
|
|
if (typeof f !== 'undefined') throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting8`, {
|
|
files: {
|
|
"in.js": `
|
|
var f = 1
|
|
if (1) {
|
|
function f() {
|
|
return f
|
|
}
|
|
f = null
|
|
}
|
|
if (typeof f !== 'function' || f() !== null) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting9`, {
|
|
files: {
|
|
"in.js": `
|
|
'use strict'
|
|
var f = 1
|
|
if (1) {
|
|
function f() {
|
|
return f
|
|
}
|
|
}
|
|
if (f !== 1) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting10`, {
|
|
files: {
|
|
"in.js": `
|
|
export {}
|
|
var f = 1
|
|
if (1) {
|
|
function f() {
|
|
return f
|
|
}
|
|
}
|
|
if (f !== 1) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting11`, {
|
|
files: {
|
|
"in.js": `
|
|
import {f, g} from './other'
|
|
if (f !== void 0 || g !== 'g') throw 'fail'
|
|
`,
|
|
"other.js": `
|
|
'use strict'
|
|
var f
|
|
if (1) {
|
|
function f() {
|
|
return f
|
|
}
|
|
}
|
|
exports.f = f
|
|
exports.g = 'g'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting12`, {
|
|
files: {
|
|
"in.js": `
|
|
let f = 1
|
|
// This should not be turned into "if (1) let f" because that's a syntax error
|
|
if (1)
|
|
function f() {
|
|
return f
|
|
}
|
|
if (f !== 1) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting13`, {
|
|
files: {
|
|
"in.js": `
|
|
x: function f() { return 1 }
|
|
if (f() !== 1) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting14`, {
|
|
files: {
|
|
"in.ts": `
|
|
if (1) {
|
|
var a = 'a'
|
|
for (var b = 'b'; 0; ) ;
|
|
for (var c in { c: 0 }) ;
|
|
for (var d of ['d']) ;
|
|
for (var e = 'e' in {}) ;
|
|
function f() { return 'f' }
|
|
}
|
|
const observed = JSON.stringify({ a, b, c, d, e, f: f() })
|
|
const expected = JSON.stringify({ a: 'a', b: 'b', c: 'c', d: 'd', e: 'e', f: 'f' })
|
|
if (observed !== expected) throw observed
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoisting15`, {
|
|
files: {
|
|
"in.ts": `
|
|
if (1) {
|
|
var a = 'a'
|
|
for (var b = 'b'; 0; ) ;
|
|
for (var c in { c: 0 }) ;
|
|
for (var d of ['d']) ;
|
|
for (var e = 'e' in {}) ;
|
|
function f() { return 'f' }
|
|
}
|
|
const observed = JSON.stringify({ a, b, c, d, e, f: f() })
|
|
const expected = JSON.stringify({ a: 'a', b: 'b', c: 'c', d: 'd', e: 'e', f: 'f' })
|
|
if (observed !== expected) throw observed
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoistingKeepNames1`, {
|
|
todo: true, // keepNames requires Object.defineProperty implementation
|
|
files: {
|
|
"in.js": `
|
|
var f
|
|
if (1) function f() { return f }
|
|
if (typeof f !== 'function' || f.name !== 'f') throw 'fail: ' + f.name
|
|
`,
|
|
},
|
|
keepNames: true,
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoistingKeepNames2`, {
|
|
todo: true, // keepNames requires Object.defineProperty implementation
|
|
files: {
|
|
"in.js": `
|
|
var f
|
|
if (1) function f() { return f }
|
|
if (typeof f !== 'function' || f.name !== 'f') throw 'fail: ' + f.name
|
|
`,
|
|
},
|
|
keepNames: true,
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoistingKeepNames3`, {
|
|
files: {
|
|
"in.ts": `
|
|
if (1) {
|
|
var a = 'a'
|
|
for (var b = 'b'; 0; ) ;
|
|
for (var c in { c: 0 }) ;
|
|
for (var d of ['d']) ;
|
|
for (var e = 'e' in {}) ;
|
|
function f() {}
|
|
}
|
|
const observed = JSON.stringify({ a, b, c, d, e, f: f.name })
|
|
const expected = JSON.stringify({ a: 'a', b: 'b', c: 'c', d: 'd', e: 'e', f: 'f' })
|
|
if (observed !== expected) throw observed
|
|
`,
|
|
},
|
|
keepNames: true,
|
|
run: true,
|
|
});
|
|
itBundled(`extra/FunctionHoistingKeepNames4`, {
|
|
files: {
|
|
"in.ts": `
|
|
if (1) {
|
|
var a = 'a'
|
|
for (var b = 'b'; 0; ) ;
|
|
for (var c in { c: 0 }) ;
|
|
for (var d of ['d']) ;
|
|
for (var e = 'e' in {}) ;
|
|
function f() {}
|
|
}
|
|
const observed = JSON.stringify({ a, b, c, d, e, f: f.name })
|
|
const expected = JSON.stringify({ a: 'a', b: 'b', c: 'c', d: 'd', e: 'e', f: 'f' })
|
|
if (observed !== expected) throw observed
|
|
`,
|
|
},
|
|
keepNames: true,
|
|
run: true,
|
|
});
|
|
// Object rest pattern tests
|
|
// Test the correctness of side effect order for the TypeScript namespace exports
|
|
itBundled(`extra/ObjectRestPattern1`, {
|
|
files: {
|
|
"in.ts": `
|
|
function fn() {
|
|
let trail = []
|
|
let t = k => (trail.push(k), k)
|
|
let [
|
|
{ [t('a')]: a } = { a: t('x') },
|
|
{ [t('b')]: b, ...c } = { b: t('y') },
|
|
{ [t('d')]: d } = { d: t('z') },
|
|
] = [{ a: 1 }, { b: 2, bb: 3 }]
|
|
return JSON.stringify({a, b, c, d, trail})
|
|
}
|
|
namespace ns {
|
|
let trail = []
|
|
let t = k => (trail.push(k), k)
|
|
export let [
|
|
{ [t('a')]: a } = { a: t('x') },
|
|
{ [t('b')]: b, ...c } = { b: t('y') },
|
|
{ [t('d')]: d } = { d: t('z') },
|
|
] = [{ a: 1 }, { b: 2, bb: 3 }]
|
|
export let result = JSON.stringify({a, b, c, d, trail})
|
|
}
|
|
if (fn() !== ns.result) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/ObjectRestPattern2`, {
|
|
files: {
|
|
"in.ts": `
|
|
let obj = {};
|
|
({a: obj.a, ...obj.b} = {a: 1, b: 2, c: 3});
|
|
[obj.c, , ...obj.d] = [1, 2, 3];
|
|
({e: obj.e, f: obj.f = 'f'} = {e: 'e'});
|
|
[obj.g, , obj.h = 'h'] = ['g', 'gg'];
|
|
namespace ns {
|
|
export let {a, ...b} = {a: 1, b: 2, c: 3};
|
|
export let [c, , ...d] = [1, 2, 3];
|
|
export let {e, f = 'f'} = {e: 'e'};
|
|
export let [g, , h = 'h'] = ['g', 'gg'];
|
|
}
|
|
if (JSON.stringify(obj) !== JSON.stringify(ns)) throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/ObjectRestPattern3`, {
|
|
files: {
|
|
"in.ts": `
|
|
var z = {x: {z: 'z'}, y: 'y'}, {x: z, ...y} = z
|
|
if (y.y !== 'y' || z.z !== 'z') throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled(`extra/ObjectRestPattern4`, {
|
|
files: {
|
|
"in.ts": `
|
|
var z = {x: {x: 'x'}, y: 'y'}, {[(z = {z: 'z'}, 'x')]: x, ...y} = z
|
|
if (x.x !== 'x' || y.y !== 'y' || z.z !== 'z') throw 'fail'
|
|
`,
|
|
},
|
|
run: true,
|
|
});
|
|
|
|
itBundled("extra/CaseSensitiveImport", {
|
|
files: {
|
|
"in.js": `
|
|
import x from "./File1.js"
|
|
import y from "./file2.js"
|
|
if (x !== 123 || y !== 234) throw 'fail'
|
|
`,
|
|
"file1.js": `export default 123`,
|
|
"File2.js": `export default 234`,
|
|
},
|
|
run: true,
|
|
});
|
|
itBundled("extra/CaseSensitiveImport2", {
|
|
todo: true,
|
|
files: {
|
|
"in.js": `
|
|
import x from "./File1.js"
|
|
import y from "./file2.js"
|
|
import z from "./File3.js"
|
|
console.log(x, y, z)
|
|
`,
|
|
"file1.js": `export default 123`,
|
|
"File1.js": `export default 234`,
|
|
"file2.js": `export default 345`,
|
|
"File2.js": `export default 456`,
|
|
"File3.js": `export default 567`,
|
|
},
|
|
run: {
|
|
stdout: "234 345 567",
|
|
},
|
|
});
|
|
itBundled("extra/CaseSensitiveImport3", {
|
|
todo: true,
|
|
files: {
|
|
"in.js": `
|
|
import x from "./Dir1/file.js"
|
|
import y from "./dir2/file.js"
|
|
if (x !== 123 || y !== 234) throw 'fail'
|
|
`,
|
|
"dir1/file.js": `export default 123`,
|
|
"Dir2/file.js": `export default 234`,
|
|
},
|
|
bundleErrors: {
|
|
"/in.js": [`Could not resolve: "./Dir1/file.js"`, `Could not resolve: "./dir2/file.js"`],
|
|
},
|
|
run: true,
|
|
});
|
|
// Warn when importing something inside node_modules
|
|
itBundled("extra/CaseSensitiveImport4", {
|
|
files: {
|
|
"in.js": `
|
|
import x from "pkg/File1.js"
|
|
import y from "pkg/file2.js"
|
|
if (x !== 123 || y !== 234) throw 'fail'
|
|
`,
|
|
"node_modules/pkg/file1.js": `export default 123`,
|
|
"node_modules/pkg/File2.js": `export default 234`,
|
|
},
|
|
run: true,
|
|
});
|
|
});
|