一文搞懂 cjs 和 mjs 如何相互使用

10,743 阅读2分钟

概念

Node.js 里可分为 CommonJS 模块和 ECMAScript 模块(ESM)两种不同的模块系统。

CommonJS 模块是 Node.js 最初支持的模块系统,它使用 require() 函数来导入模块,使用 module.exportsexports 对象来导出模块。这种模块系统通常只能在 Node.js 环境下使用,并且不允许在浏览器环境中使用。

ECMAScript 模块是 JavaScript 的标准模块系统,它使用 importexport 关键字来导入和导出模块。它可以在 Node.js 环境下和现代浏览器环境中使用,具有更好的跨平台兼容性和可移植性。Node.js 从版本12开始支持 ECMAScript 模块作为实验性功能,并在版本14中正式支持。

如何将 js 文件变成 mjs 模块

  • package.jsontype 属性的值改成 module
  • 执行命令 node xxx.js --input-type=module

cjs 引入 mjs 并使用

  1. 安装第三方插件
npm install esm
// test.mjs

export const name = "berserker";
export default "fate";
// index.js

require = require("esm")(module);
const Test = require("./test.mjs");
console.log(Test); // { name: 'berserker', default: 'fate' }

首先安装 esm 模块依赖包, 传入 module 对象 创建一个新的 require,接着就可以使用 esm 模块文件了

  1. 采用动态加载
import('test.mjs').then(res => {
  console.log(res) // { default: 'fate', name: 'berserker' }
})

mjs 引入 cjs 并使用

在 Node.js 中,使用 ECMAScript 模块(ESM)的文件通常使用 .mjs 扩展名。要在 .mjs 文件中引入 CommonJS 模块(CJS),可以使用 Node.js 提供的 createRequire() 函数。

例如,在 .mjs 文件中可以这样引入一个 CJS 模块:

import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const cjsModule = require('./cjs-module');
  • 示例
// test.js

const saber = "saber";
const berserker = "berserker";

exports.saber = saber;
exports.berserker = berserker;
module.exports = "fate"; // 猜猜 去掉这行 和 不去掉这行 分别打印什么
// index.mjs

import { createRequire } from "node:module";

const require = createRequire(import.meta.url);
const Test = require("./test.js");

console.log(Test); 

额外知识

esm 模块 里没有 __dirname__filename,也不能导入 json 文件,可以改写一下:

import url from "node:url";
import path from "node:path";
import { createRequire } from "node:module";

Object.defineProperty(global, "loadJSON", {
  get() {
    return (filepath, importMetaUrl = import.meta.url) => {
      const reg = /\S+.json$/g;
      if (reg.test(filepath)) {
        const require = createRequire(importMetaUrl);
        return require(filepath);
      } else {
        throw new Error("loadJSON 的参数必须是一个json文件");
      }
    };
  },
  enumerable: true,
  configurable: false,
  // writable: false,
});

Object.defineProperty(global, "getFileName", {
  get() {
    return (importMetaUrl = import.meta.url) => {
      return url.fileURLToPath(importMetaUrl);
    };
  },
  enumerable: true,
  configurable: false,
  // writable: false,
});

Object.defineProperty(global, "getDirName", {
  get() {
    return (importMetaUrl = import.meta.url) => {
      return path.dirname(url.fileURLToPath(importMetaUrl));
    };
  },
  enumerable: true,
  configurable: false,
  // writable: false,
});

总结

现在已经有很多 npm 包已逐渐采用 ESM 规范开发,日后 浏览器Node 肯定会保持相同的规范达成天下大同,学习 mjscjs 的相互使用,也是有必要的一个过程。文中都是笔者的实践经验总结,有不足的地方欢迎指正和补充。