Skip to content

Commit

Permalink
feat: support interop for namespace importing cjs function exports (v…
Browse files Browse the repository at this point in the history
…ercel/turborepo#7914)

### Description

Some libraries unfortunately use the "deprecated" babel way to import
cjs dependencies.

`a.js`
```js
module.exports = () => {};
```

`b.js`
```js
import * as ns from "./a.cjs";

ns();
```
  • Loading branch information
ForsakenHarmony authored Apr 9, 2024
1 parent f25e81e commit 0339a11
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/// A 'base' utilities to support runtime can have externals.
/// Currently this is for node.js / edge runtime both.
/// If a fn requires node.js specific behavior it should be placed in `node-external-utils` instead.
/// If a fn requires node.js specific behavior, it should be placed in `node-external-utils` instead.

async function externalImport(id: ModuleId) {
let raw;
Expand All @@ -17,7 +17,7 @@ async function externalImport(id: ModuleId) {
}

if (raw && raw.__esModule && raw.default && "default" in raw.default) {
return interopEsm(raw.default, {}, true);
return interopEsm(raw.default, createNS(raw), true);
}

return raw;
Expand All @@ -42,7 +42,7 @@ function externalRequire(
return raw;
}

return interopEsm(raw, {}, true);
return interopEsm(raw, createNS(raw), true);
}

externalRequire.resolve = (
Expand Down
14 changes: 12 additions & 2 deletions crates/turbopack-ecmascript-runtime/js/src/shared/runtime-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type EsmNamespaceObject = Record<string, any>;
const REEXPORTED_OBJECTS = Symbol("reexported objects");

interface BaseModule {
exports: Exports | Promise<Exports> | AsyncModulePromise;
exports: Function | Exports | Promise<Exports> | AsyncModulePromise;
error: Error | undefined;
loaded: boolean;
id: ModuleId;
Expand Down Expand Up @@ -198,6 +198,16 @@ function interopEsm(
return ns;
}

function createNS(raw: BaseModule["exports"]): EsmNamespaceObject {
if (typeof raw === "function") {
return function (this: any, ...args: any[]) {
return raw.apply(this, args);
};
} else {
return Object.create(null);
}
}

function esmImport(
sourceModule: Module,
id: ModuleId
Expand All @@ -212,7 +222,7 @@ function esmImport(
const raw = module.exports;
return (module.namespaceObject = interopEsm(
raw,
{},
createNS(raw),
raw && (raw as any).__esModule
));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,23 @@ function createGetter(obj, key) {
esm(ns, getters);
return ns;
}
function createNS(raw) {
if (typeof raw === "function") {
return function(...args) {
return raw.apply(this, args);
};
} else {
return Object.create(null);
}
}
function esmImport(sourceModule, id) {
const module = getOrInstantiateModuleFromParent(id, sourceModule);
if (module.error) throw module.error;
// any ES module has to have `module.namespaceObject` defined.
if (module.namespaceObject) return module.namespaceObject;
// only ESM can be an async module, so we don't need to worry about exports being a promise here.
const raw = module.exports;
return module.namespaceObject = interopEsm(raw, {}, raw && raw.__esModule);
return module.namespaceObject = interopEsm(raw, createNS(raw), raw && raw.__esModule);
}
// Add a simple runtime require so that environments without one can still pass
// `typeof require` CommonJS checks so that exports are correctly registered.
Expand Down Expand Up @@ -299,7 +308,7 @@ relativeURL.prototype = URL.prototype;
/// <reference path="../shared/runtime-utils.ts" />
/// A 'base' utilities to support runtime can have externals.
/// Currently this is for node.js / edge runtime both.
/// If a fn requires node.js specific behavior it should be placed in `node-external-utils` instead.
/// If a fn requires node.js specific behavior, it should be placed in `node-external-utils` instead.
async function externalImport(id) {
let raw;
try {
Expand All @@ -312,7 +321,7 @@ async function externalImport(id) {
throw new Error(`Failed to load external module ${id}: ${err}`);
}
if (raw && raw.__esModule && raw.default && "default" in raw.default) {
return interopEsm(raw.default, {}, true);
return interopEsm(raw.default, createNS(raw), true);
}
return raw;
}
Expand All @@ -330,7 +339,7 @@ function externalRequire(id, esm = false) {
if (!esm || raw.__esModule) {
return raw;
}
return interopEsm(raw, {}, true);
return interopEsm(raw, createNS(raw), true);
}
externalRequire.resolve = (id, options)=>{
return require.resolve(id, options);
Expand Down
Loading

0 comments on commit 0339a11

Please sign in to comment.