590 lines
16 KiB
TypeScript
590 lines
16 KiB
TypeScript
import { describe, expect } from "bun:test";
|
|
import { itBundled } from "./expectBundled";
|
|
|
|
const fakeReactNodeModules = {
|
|
"/node_modules/react/index.js": /* js */ `
|
|
module.exports = { react: "react" }
|
|
`,
|
|
"/node_modules/react/package.json": /* json */ `
|
|
{
|
|
"name": "react",
|
|
"version": "2.0.0",
|
|
"main": "index.js"
|
|
}
|
|
`,
|
|
};
|
|
|
|
describe("bundler", () => {
|
|
itBundled("cjs2esm/ModuleExportsFunction", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import { foo } from 'lib';
|
|
console.log(foo());
|
|
`,
|
|
"/node_modules/lib/index.js": /* js */ `
|
|
module.exports.foo = function() {
|
|
return 'foo';
|
|
}
|
|
`,
|
|
},
|
|
cjs2esm: true,
|
|
run: {
|
|
stdout: "foo",
|
|
},
|
|
});
|
|
itBundled("cjs2esm/ImportNamedFromExportStarCJSModuleRef", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import { foo } from './foo';
|
|
console.log(foo);
|
|
`,
|
|
"/foo.js": /* js */ `
|
|
export * from './bar.cjs';
|
|
`,
|
|
"/bar.cjs": /* js */ `
|
|
module.exports.foo = 'bar';
|
|
`,
|
|
},
|
|
run: {
|
|
stdout: "bar",
|
|
},
|
|
});
|
|
itBundled("cjs2esm/ImportNamedFromExportStarCJS", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import { foo } from './foo';
|
|
console.log(foo);
|
|
`,
|
|
"/foo.js": /* js */ `
|
|
export * from './bar.cjs';
|
|
`,
|
|
"/bar.cjs": /* js */ `
|
|
exports.foo = 'bar';
|
|
`,
|
|
},
|
|
run: {
|
|
stdout: "bar",
|
|
},
|
|
});
|
|
itBundled("cjs2esm/BadNamedImportNamedReExportedFromCommonJS", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import {bad} from './foo';
|
|
console.log(bad);
|
|
`,
|
|
"/foo.js": /* js */ `
|
|
export {bad} from './bar.cjs';
|
|
`,
|
|
"/bar.cjs": /* js */ `
|
|
exports.foo = 'bar';
|
|
`,
|
|
},
|
|
run: {
|
|
stdout: "undefined",
|
|
},
|
|
});
|
|
itBundled("cjs2esm/ExportsFunction", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import { foo } from 'lib';
|
|
console.log(foo());
|
|
`,
|
|
"/node_modules/lib/index.js": /* js */ `
|
|
exports.foo = function() {
|
|
return 'foo';
|
|
}
|
|
`,
|
|
},
|
|
cjs2esm: true,
|
|
run: {
|
|
stdout: "foo",
|
|
},
|
|
});
|
|
itBundled("cjs2esm/ModuleExportsFunctionTreeShaking", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import { foo } from 'lib';
|
|
console.log(foo());
|
|
`,
|
|
"/node_modules/lib/index.js": /* js */ `
|
|
module.exports.foo = function() {
|
|
return 'foo';
|
|
}
|
|
module.exports.bar = function() {
|
|
return 'remove_me';
|
|
}
|
|
`,
|
|
},
|
|
cjs2esm: true,
|
|
dce: true,
|
|
treeShaking: true,
|
|
run: {
|
|
stdout: "foo",
|
|
},
|
|
});
|
|
itBundled("cjs2esm/ModuleExportsEqualsRequire", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import { foo } from 'lib';
|
|
console.log(foo);
|
|
`,
|
|
"/node_modules/lib/index.js": /* js */ `
|
|
// bundler should see through this
|
|
module.exports = require('./library.js')
|
|
`,
|
|
"/node_modules/lib/library.js": /* js */ `
|
|
module.exports.foo = 'bar';
|
|
`,
|
|
},
|
|
cjs2esm: true,
|
|
run: {
|
|
stdout: "bar",
|
|
},
|
|
});
|
|
itBundled("cjs2esm/ModuleExportsBasedOnNodeEnvProduction", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import { foo } from 'lib';
|
|
console.log(foo);
|
|
`,
|
|
"/node_modules/lib/index.js": /* js */ `
|
|
// bundler should see through this
|
|
if (process.env.NODE_ENV === 'production') {
|
|
module.exports = require('./library.prod.js')
|
|
} else {
|
|
module.exports = require('./library.dev.js')
|
|
}
|
|
`,
|
|
"/node_modules/lib/library.prod.js": /* js */ `
|
|
module.exports.foo = 'production';
|
|
`,
|
|
"/node_modules/lib/library.dev.js": /* js */ `
|
|
module.exports.foo = 'FAILED';
|
|
`,
|
|
},
|
|
cjs2esm: true,
|
|
minifySyntax: true,
|
|
env: {
|
|
NODE_ENV: "production",
|
|
},
|
|
run: {
|
|
stdout: "production",
|
|
},
|
|
});
|
|
itBundled("cjs2esm/ModuleExportsBasedOnNodeEnvDevelopment", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import { foo } from 'lib';
|
|
console.log(foo);
|
|
`,
|
|
"/node_modules/lib/index.js": /* js */ `
|
|
if (process.env.NODE_ENV === 'production') {
|
|
module.exports = require('./library.prod.js')
|
|
} else {
|
|
module.exports = require('./library.dev.js')
|
|
}
|
|
`,
|
|
"/node_modules/lib/library.prod.js": /* js */ `
|
|
module.exports.foo = 'FAILED';
|
|
`,
|
|
"/node_modules/lib/library.dev.js": /* js */ `
|
|
module.exports.foo = 'development';
|
|
`,
|
|
},
|
|
cjs2esm: true,
|
|
minifySyntax: true,
|
|
env: {
|
|
NODE_ENV: "development",
|
|
},
|
|
run: {
|
|
stdout: "development",
|
|
},
|
|
});
|
|
itBundled("cjs2esm/ModuleExportsEqualsRuntimeCondition", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import { foo } from 'lib';
|
|
console.log(foo);
|
|
`,
|
|
"/node_modules/lib/index.js": /* js */ `
|
|
// if the branch is unknown, we have to include both.
|
|
if (globalThis.USE_PROD) {
|
|
module.exports = require('./library.prod.js')
|
|
} else {
|
|
module.exports = require('./library.dev.js')
|
|
}
|
|
`,
|
|
// these should have the cjs transform
|
|
"/node_modules/lib/library.prod.js": /* js */ `
|
|
module.exports.foo = 'production';
|
|
`,
|
|
"/node_modules/lib/library.dev.js": /* js */ `
|
|
module.exports.foo = 'development';
|
|
`,
|
|
},
|
|
cjs2esm: {
|
|
unhandled: [
|
|
"/node_modules/lib/index.js",
|
|
"/node_modules/lib/library.prod.js",
|
|
"/node_modules/lib/library.dev.js",
|
|
],
|
|
},
|
|
run: {
|
|
stdout: "development",
|
|
},
|
|
});
|
|
itBundled("cjs2esm/UnwrappedModuleRequireAssigned", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
const react = require("react");
|
|
console.log(react.react);
|
|
|
|
const react1 = (console.log(require("react").react), require("react"));
|
|
console.log(react1.react);
|
|
|
|
const react2 = (require("react"), console.log(require("react").react));
|
|
console.log(react2);
|
|
|
|
let x = {};
|
|
x.react = require("react");
|
|
console.log(x.react.react);
|
|
|
|
console.log(require("react").react);
|
|
|
|
let y = {};
|
|
y[require("react")] = require("react");
|
|
console.log(y[require("react")].react);
|
|
|
|
let r = require("react");
|
|
console.log(r.react);
|
|
r = require("react");
|
|
console.log(r.react);
|
|
|
|
let n = 1;
|
|
n = require("react");
|
|
console.log(n.react);
|
|
|
|
let m = 1,
|
|
o = require("react");
|
|
console.log(m, o.react);
|
|
|
|
let h = Math.random() > 0.5;
|
|
let p = require(h ? "react" : "react");
|
|
console.log(p.react);
|
|
|
|
console.log(require(h ? "react" : "react").react);
|
|
`,
|
|
...fakeReactNodeModules,
|
|
},
|
|
onAfterBundle: api => {
|
|
const code = api.readFile("out.js");
|
|
expect(code).toContain("__toESM(");
|
|
},
|
|
run: {
|
|
stdout: "react\nreact\nreact\nreact\nundefined\nreact\nreact\nreact\nreact\nreact\nreact\n1 react\nreact\nreact",
|
|
},
|
|
});
|
|
// A require() of an unwrapped package that initializes a destructuring
|
|
// declaration is kept as a require expression that remembers it was
|
|
// unwrapped, and prints as the namespace object. One inside try/catch is
|
|
// never unwrapped and prints as an ordinary require.
|
|
itBundled("cjs2esm/UnwrappedModuleRequireDestructuredAndInTry", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
const { react: named } = require("react");
|
|
console.log(named);
|
|
|
|
let inTry = "missing";
|
|
try {
|
|
inTry = require("react").react;
|
|
} catch {}
|
|
console.log(inTry);
|
|
`,
|
|
"/node_modules/react/index.js": /* js */ `
|
|
exports.react = "react";
|
|
`,
|
|
"/node_modules/react/package.json": /* json */ `
|
|
{
|
|
"name": "react",
|
|
"version": "2.0.0",
|
|
"main": "index.js"
|
|
}
|
|
`,
|
|
},
|
|
onAfterBundle: api => {
|
|
const code = api.readFile("out.js");
|
|
expect(code).toMatch(/\{ react: named \} = \(?exports_react\)?;/);
|
|
expect(code).toContain("__toCommonJS(exports_react)).react");
|
|
},
|
|
run: {
|
|
stdout: "react\nreact",
|
|
},
|
|
});
|
|
itBundled("cjs2esm/ReactSpecificUnwrapping", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import { renderToReadableStream } from "react";
|
|
console.log(renderToReadableStream());
|
|
`,
|
|
"/node_modules/react/index.js": /* js */ `
|
|
console.log('side effect');
|
|
module.exports = require('./main');
|
|
`,
|
|
"/node_modules/react/main.js": /* js */ `
|
|
"use strict";
|
|
var REACT_ELEMENT_TYPE = Symbol.for("pass");
|
|
exports.renderToReadableStream = (e, t) => {
|
|
return REACT_ELEMENT_TYPE;
|
|
}
|
|
`,
|
|
},
|
|
run: {
|
|
stdout: "side effect\nSymbol(pass)",
|
|
},
|
|
minifySyntax: true,
|
|
});
|
|
itBundled("cjs2esm/ReactSpecificUnwrapping2", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import * as react from "react-dom";
|
|
console.log(react);
|
|
`,
|
|
"/node_modules/react-dom/index.js": /* js */ `
|
|
export const stuff = [
|
|
require('./a.js'),
|
|
require('./b.js')
|
|
];
|
|
`,
|
|
"/node_modules/react-dom/a.js": /* js */ `
|
|
(function () {
|
|
var React = require('react');
|
|
var stream = require('stream');
|
|
|
|
console.log([React, stream]);
|
|
|
|
exports.version = null;
|
|
})();
|
|
`,
|
|
"/node_modules/react-dom/b.js": /* js */ `
|
|
(function () {
|
|
var React = require('react');
|
|
var util = require('util');
|
|
|
|
console.log([React, util]);
|
|
|
|
exports.version = null;
|
|
})();
|
|
`,
|
|
"/node_modules/react/index.js": /* js */ `
|
|
module.exports = 123;
|
|
`,
|
|
},
|
|
run: true,
|
|
minifyIdentifiers: true,
|
|
target: "bun",
|
|
});
|
|
itBundled("cjs2esm/ModuleExportsRenamingNoDeopt", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
eval('exports = { xyz: 123 }; module.exports = { xyz: 456 }');
|
|
let w = () => [module.exports, module.exports.xyz]; // rewrite to exports, exports.xyz
|
|
let x = () => [exports, exports.xyz]; // keep as is
|
|
console.log(JSON.stringify([w(), x()]));
|
|
`,
|
|
},
|
|
run: {
|
|
stdout: '[[{"xyz":123},123],[{"xyz":123},123]]',
|
|
},
|
|
});
|
|
itBundled("cjs2esm/ModuleExportsRenamingAssignDeOpt", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
eval('exports = { xyz: 123 }');
|
|
let w = () => [module.exports, module.exports.xyz]; // keep as is
|
|
let x = () => [exports, exports.xyz]; // keep as is
|
|
module.exports = { xyz: 456 };
|
|
let y = () => [module.exports, module.exports.xyz]; // keep as is
|
|
let z = () => [exports, exports.xyz]; // keep as is
|
|
console.log(JSON.stringify([w(), x(), y(), z()]));
|
|
`,
|
|
},
|
|
run: {
|
|
stdout: '[[{"xyz":456},456],[{"xyz":123},123],[{"xyz":456},456],[{"xyz":123},123]]',
|
|
},
|
|
});
|
|
itBundled("cjs2esm/ModuleExportsRenamingAssignExportsDeOpt", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
eval('module.exports = { xyz: 456 }');
|
|
let w = () => [module.exports, module.exports.xyz];
|
|
let x = () => [exports, exports.xyz];
|
|
exports = { xyz: 123 };
|
|
let y = () => [module.exports, module.exports.xyz];
|
|
let z = () => [exports, exports.xyz];
|
|
console.log(JSON.stringify([w(), x(), y(), z()]));
|
|
`,
|
|
},
|
|
run: {
|
|
stdout: '[[{"xyz":456},456],[{"xyz":123},123],[{"xyz":456},456],[{"xyz":123},123]]',
|
|
},
|
|
});
|
|
// `delete (null ?? exports.a)` evaluates to a value, so the result is `true`
|
|
// with no effect on the property. Under cjs2esm, `exports.a` is rewritten to
|
|
// an `ECommonjsExportIdentifier`; the printer has to re-wrap it as `(0, ...)`
|
|
// so `delete` still sees a value instead of the hoisted binding.
|
|
itBundled("cjs2esm/DeleteFoldedExportsPropertyRef", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
exports.a = 1;
|
|
console.log(delete (null ?? exports.a), exports.a);
|
|
console.log(delete (0, exports.a), exports.a);
|
|
`,
|
|
},
|
|
onAfterBundle: api => {
|
|
const code = api.readFile("out.js");
|
|
expect(code).not.toMatch(/^[^"]*delete\s+\$a\b/m);
|
|
},
|
|
run: { stdout: "true 1\ntrue 1" },
|
|
});
|
|
itBundled("cjs2esm/DeleteFoldedExportsPropertyRefConsumer", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import { a } from "./lib.js";
|
|
console.log(a);
|
|
`,
|
|
"/lib.js": /* js */ `
|
|
exports.a = 1;
|
|
console.log(delete (null ?? exports.a), exports.a);
|
|
`,
|
|
},
|
|
run: { stdout: "true 1\n1" },
|
|
});
|
|
// `module.exports` under cjs2esm rewrites to ESpecial::ModuleExports, which
|
|
// prints as the `exports_*` namespace symbol. Output-shape check only: a bare
|
|
// `module.exports` value-read under cjs2esm currently emits a reference that
|
|
// has no declaration (a separate pre-existing issue), so the bundle cannot be
|
|
// executed here.
|
|
itBundled("cjs2esm/DeleteFoldedModuleExportsRef", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
exports.a = 1;
|
|
globalThis.r = delete (null ?? module.exports);
|
|
`,
|
|
},
|
|
onAfterBundle: api => {
|
|
const code = api.readFile("out.js");
|
|
expect(code).toMatch(/delete\s+\(0,\s*exports_\w+\)/);
|
|
expect(code).not.toMatch(/delete\s+exports_\w+\b/);
|
|
},
|
|
});
|
|
// https://github.com/oven-sh/bun/issues/4565
|
|
// `exports.x = ...` as the unbraced body of if/while/do/else must not be
|
|
// converted to `var $x = ...; export { $x as x };` because `export` is only
|
|
// valid at the top level of a module.
|
|
const noNestedExport = (api: { readFile(path: string): string }) => {
|
|
// Before the fix, the output contained `export { $x as x };` nested inside
|
|
// a block, which is a syntax error. Any indented `export {` is wrong here.
|
|
expect(api.readFile("out.js")).not.toMatch(/^\s+export\s*\{/m);
|
|
};
|
|
itBundled("cjs2esm/ExportsAssignInSingleStmtIf#4565", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import lib from './lib.js';
|
|
console.log(JSON.stringify([lib.always, lib.cond]));
|
|
`,
|
|
"/lib.js": /* js */ `
|
|
exports.always = 1;
|
|
if (process.env.NEVER_SET_4565) exports.cond = 2;
|
|
`,
|
|
},
|
|
onAfterBundle: noNestedExport,
|
|
run: { stdout: "[1,null]" },
|
|
});
|
|
itBundled("cjs2esm/ExportsAssignInSingleStmtElse", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import lib from './lib.js';
|
|
console.log(JSON.stringify([lib.a, lib.b]));
|
|
`,
|
|
"/lib.js": /* js */ `
|
|
exports.a = 1;
|
|
if (process.env.NEVER_SET_4565) void 0;
|
|
else exports.b = 2;
|
|
`,
|
|
},
|
|
onAfterBundle: noNestedExport,
|
|
run: { stdout: "[1,2]" },
|
|
});
|
|
itBundled("cjs2esm/ExportsAssignInSingleStmtWhile", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import lib from './lib.js';
|
|
console.log(JSON.stringify([lib.a, lib.b]));
|
|
`,
|
|
"/lib.js": /* js */ `
|
|
exports.a = 1;
|
|
let i = 0;
|
|
while (i++ < 1) exports.b = 2;
|
|
`,
|
|
},
|
|
onAfterBundle: noNestedExport,
|
|
run: { stdout: "[1,2]" },
|
|
});
|
|
itBundled("cjs2esm/ExportsAssignInSingleStmtDoWhile", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import lib from './lib.js';
|
|
console.log(JSON.stringify([lib.a, lib.b]));
|
|
`,
|
|
"/lib.js": /* js */ `
|
|
exports.a = 1;
|
|
do exports.b = 2; while (false);
|
|
`,
|
|
},
|
|
onAfterBundle: noNestedExport,
|
|
run: { stdout: "[1,2]" },
|
|
});
|
|
itBundled("cjs2esm/ModuleExportsAssignInSingleStmtIf", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import lib from './lib.js';
|
|
console.log(JSON.stringify([lib.a, lib.b]));
|
|
`,
|
|
"/lib.js": /* js */ `
|
|
module.exports.a = 1;
|
|
if (process.env.NEVER_SET_4565) module.exports.b = 2;
|
|
`,
|
|
},
|
|
onAfterBundle: noNestedExport,
|
|
run: { stdout: "[1,null]" },
|
|
});
|
|
itBundled("cjs2esm/ExportsAssignInNestedSingleStmtIf", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import lib from './lib.js';
|
|
console.log(JSON.stringify([lib.a, lib.b]));
|
|
`,
|
|
"/lib.js": /* js */ `
|
|
exports.a = 1;
|
|
if (!process.env.NEVER_SET_4565) if (!process.env.NEVER_SET_4565) exports.b = 2;
|
|
`,
|
|
},
|
|
onAfterBundle: noNestedExport,
|
|
run: { stdout: "[1,2]" },
|
|
});
|
|
itBundled("cjs2esm/ExportsAssignTopLevelStillConverts", {
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import { a, b } from './lib.js';
|
|
console.log(JSON.stringify([a, b]));
|
|
`,
|
|
"/lib.js": /* js */ `
|
|
exports.a = 1;
|
|
exports.b = 2;
|
|
`,
|
|
},
|
|
cjs2esm: true,
|
|
run: { stdout: "[1,2]" },
|
|
});
|
|
});
|