diff --git a/packages/plugin-vite/src/plugins/patches/commonjs.ts b/packages/plugin-vite/src/plugins/patches/commonjs.ts index c6e49350e6f..7109244a922 100644 --- a/packages/plugin-vite/src/plugins/patches/commonjs.ts +++ b/packages/plugin-vite/src/plugins/patches/commonjs.ts @@ -7,6 +7,8 @@ export function cjsPlugin( const REQUIRE_CALLS = "requireCalls"; const ROOT_SCOPE = "rootScope"; const EXPORTED = "exported"; + const EXPORTED_NAMESPACES = "exported_namespaces"; + const ALIASED = "aliased"; return { name: "fresh-cjs-esm", @@ -15,6 +17,7 @@ export function cjsPlugin( enter(path, state) { state.set(ROOT_SCOPE, path.scope); state.set(EXPORTED, new Set()); + state.set(EXPORTED_NAMESPACES, new Set()); }, exit(path, state) { const body = path.get("body"); @@ -33,7 +36,24 @@ export function cjsPlugin( } const exported = state.get(EXPORTED); - if (exported.size > 0) { + const exportedNs = state.get(EXPORTED_NAMESPACES); + + const mappedNs: string[] = []; + + for (const spec of exportedNs.values()) { + const id = path.scope.generateUidIdentifier("__ns"); + mappedNs.push(id.name); + + path.unshiftContainer( + "body", + t.importDeclaration( + [t.importNamespaceSpecifier(id)], + t.stringLiteral(spec), + ), + ); + } + + if (exported.size > 0 || exportedNs.size > 0) { path.unshiftContainer( "body", t.variableDeclaration("var", [ @@ -94,7 +114,7 @@ export function cjsPlugin( ); } - if (exportNamed.size > 0) { + if (exportNamed.size > 0 || exportedNs.size > 0) { const id = path.scope.generateUidIdentifier("__default"); path.pushContainer( @@ -142,6 +162,22 @@ export function cjsPlugin( ); } + for (let i = 0; i < mappedNs.length; i++) { + const mapped = mappedNs[i]; + path.pushContainer( + "body", + t.expressionStatement( + t.callExpression( + t.memberExpression( + t.identifier("Object"), + t.identifier("assign"), + ), + [t.cloneNode(id, true), t.identifier(mapped)], + ), + ), + ); + } + path.pushContainer("body", t.exportDefaultDeclaration(id)); } @@ -158,6 +194,7 @@ export function cjsPlugin( path.remove(); return; } + if ( t.isIdentifier(path.node.callee) && path.node.callee.name === "require" @@ -226,7 +263,7 @@ export function cjsPlugin( }, }, ExpressionStatement: { - enter(path) { + enter(path, state) { if ( t.isExpressionStatement(path.node) && t.isCallExpression(path.node.expression) && @@ -242,7 +279,20 @@ export function cjsPlugin( path.node.expression.arguments[0].arguments[0], true, ); + state.get(EXPORTED_NAMESPACES).add(spec.value); path.replaceWith(t.exportAllDeclaration(spec)); + } else if ( + t.isExpressionStatement(path.node) && + t.isCallExpression(path.node.expression) && + t.isFunctionExpression(path.node.expression.callee) + ) { + if ( + path.node.expression.callee.params.length > 0 && + t.isIdentifier(path.node.expression.callee.params[0]) + ) { + const alias = path.node.expression.callee.params[0].name; + state.set(ALIASED, alias); + } } }, exit(path, state) { @@ -258,6 +308,22 @@ export function cjsPlugin( } else if (left.isMemberExpression()) { if (isModuleExports(t, left.node)) { exported.add("default"); + + if (t.isObjectExpression(expr.node.right)) { + const properties = expr.node.right.properties; + for (let i = 0; i < properties.length; i++) { + const prop = properties[i]; + if (t.isObjectProperty(prop)) { + if (t.isIdentifier(prop.key)) { + if (prop.key.name === "__esModule") { + continue; + } + + exported.add(prop.key.name); + } + } + } + } } else { const named = getExportsAssignName(t, left.node); if (named === null) return; @@ -272,6 +338,48 @@ export function cjsPlugin( } }, }, + VariableDeclaration(path) { + if (path.node.declarations.length === 0) { + path.remove(); + } + }, + VariableDeclarator(path) { + // Esbuild CJS shims + if ( + t.isIdentifier(path.node.id) && + (path.node.id.name === "__exportStar" || + path.node.id.name === "__createBinding") + ) { + path.remove(); + } + }, + ConditionalExpression(path) { + if ( + t.isBinaryExpression(path.node.test) && + t.isUnaryExpression(path.node.test.left) && + path.node.test.left.operator === "typeof" && + t.isIdentifier(path.node.test.left.argument) && + path.node.test.left.argument.name === "exports" && + path.node.test.operator === "===" + ) { + path.replaceWith(t.cloneNode(path.node.alternate, true)); + } + }, + AssignmentExpression(path, state) { + const exported = state.get(EXPORTED); + const aliased = state.get(ALIASED); + if (aliased === undefined) return; + + if ( + path.node.operator === "=" && t.isMemberExpression(path.node.left) && + t.isIdentifier(path.node.left.object) && + path.node.left.object.name === aliased && + t.isIdentifier(path.node.left.property) + ) { + const name = path.node.left.property.name; + exported.add(name); + } + }, }, }; } diff --git a/packages/plugin-vite/src/plugins/patches/commonjs_test.ts b/packages/plugin-vite/src/plugins/patches/commonjs_test.ts index 2cd0d057037..ed16b86fd59 100644 --- a/packages/plugin-vite/src/plugins/patches/commonjs_test.ts +++ b/packages/plugin-vite/src/plugins/patches/commonjs_test.ts @@ -323,7 +323,12 @@ ${DEFAULT_EXPORT_END}`, Deno.test("commonjs - detect esbuild shims", () => { runTest({ input: `__exportStar(require("./globalThis"), exports);`, - expected: `export * from "./globalThis";`, + expected: `${INIT} +import * as _ns from "./globalThis"; +export * from "./globalThis"; +${DEFAULT_EXPORT} +Object.assign(_default, _ns); +${DEFAULT_EXPORT_END}`, }); }); @@ -389,8 +394,7 @@ ${DEFAULT_EXPORT_END}`, }); }); -// TODO -Deno.test.ignore("commonjs - export default object", () => { +Deno.test("commonjs - export default object", () => { runTest({ input: `Object.defineProperty(exports, '__esModule', { value: true }); module.exports = { foo: 'bar' }; @@ -399,10 +403,51 @@ module.exports = { foo: 'bar' }; module.exports = { foo: 'bar' }; -var _foo = module.exports.foo; -export let foo = _foo; +var _foo = exports.foo; +export { _foo as foo }; +${DEFAULT_EXPORT} +_default.foo = _foo; +${DEFAULT_EXPORT_END}`, + }); +}); + +Deno.test("commonjs - detect iife wrapper", () => { + runTest({ + input: `;(function (sax) { + sax.foo = "foo"; +})(typeof exports === 'undefined' ? this.sax = {} : exports);`, + expected: `${INIT} +(function (sax) { + sax.foo = "foo"; +})(exports); +var _foo = exports.foo; +export { _foo as foo }; ${DEFAULT_EXPORT} _default.foo = _foo; ${DEFAULT_EXPORT_END}`, }); }); + +Deno.test("commonjs - re-export", () => { + runTest({ + input: + `;var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./node"), exports);`, + expected: `${INIT} +import * as _ns from "./node"; +export * from "./node"; +${DEFAULT_EXPORT} +Object.assign(_default, _ns); +${DEFAULT_EXPORT_END}`, + }); +});