Search code examples
node.jses6-modulescommonjs

What is the difference between module.exports=require('other') and the same with a temporary variable?


reproduce link https://stackblitz.com/edit/node-sp5xay?file=index.mjs

Assume we have a project like this:

.
├── dep
│   ├── a.mjs
│   ├── b.js
│   └── c.js
└── entry.mjs


// entry.mjs
import { foo } from "./dep/a.mjs";
console.log(foo);

// dep/a.mjs
export * from './b.js'

// dep/b.js
module.exports = require("./c.js"); // 💯

// why this not working ❌
// const m = require("./c.js"); 
// module.exports = m;


// dep/c.js
exports.foo = "foo";

and we run in terminal

node entry.mjs

it's very confuse it will throw error if in dep/b.js we use:

// why this not working ❌
const m = require("./c.js"); 
module.exports = m;

enter image description here

if in dep/b.js we use:

module.exports = require("./c.js");

it will work expect!

enter image description here

module.exports=require have something magic? like symlink? if any docs i miss?

The origin of this problem is that I saw the vue3 source code vue3 core source code export


Solution

  • module.exports=require have something magic?

    Yes, it does have some magic. The problem is that you are importing a CommonJS module into an ES module. The latter require a static declaration of exported names, which the former do not provide. See the node.js documentation:

    When importing CommonJS modules, the module.exports object is provided as the default export.

    So just don't do export * from './b.js', rather import b from './b.js' then refer to b.foo on the CommonJS module object.

    However,

    For better compatibility with existing usage in the JS ecosystem, Node.js in addition attempts to determine the CommonJS named exports of every imported CommonJS module to provide them as separate ES module exports using a static analysis process.

    […]

    The detection of named exports is based on common syntax patterns but does not always correctly detect named exports. In these cases, using the default import form described above can be a better option.

    Named exports detection covers many common export patterns, reexport patterns and build tool and transpiler outputs. See cjs-module-lexer for the exact semantics implemented.

    (emphasis mine)

    And indeed,

    module.exports = require("./c.js");
    

    is one of the "reexport patterns" that is detected, but using a temporary variable

    const m = require("./c.js"); 
    module.exports = m;
    

    is not. You just can't use named imports from a CommonJS module that does this. The proper solution to fix this is of course to rewrite the module to ESM syntax and use export * from "./c.js";, not any module.exports assignments.