在之前 js 的模块化迟迟没有落地,所以 NodeJS 自己整了个 commonjs
,也就是我们代码里常看到 require('moduleName')
。后来浏览器支持了 esm 标准也就是代码里的 import export
,NodeJS 也给加上了。
在不支持的年代,我们代码能直接写上模块化语句,都是 webpack 和 babel 的功劳。无论是 import 还是 require,最后都会处理成 _webpack_require_
(应该是这个 api)。
这里记录下 NodeJS 使用 commonjs、esm 的一些注意点,以及 package.json 一些看了老是记不住的字段。
一、文件格式区分
NodeJS 要使用 esm 模块,文件必须声明为 .mjs 后缀。依然使用 .js 或者声明为 .cjs 都是使用 commonjs。
当然硬要在 .js/.cjs 文件使用 esm 语句(也就是 import)的话会报错:
SyntaxError: Cannot use import statement outside a module
反过来在 .mjs 文件使用 commonjs 语句(也就是 require)的话会报错:
ReferenceError: require is not defined in ES module scope, you can use import instead
二、混搭使用
正常使用当然是两厢安好,但是可能还是会出现一些旧模块是 commonjs 写的,新的又想用 esm 去写的情况。
- 在 .js/.cjs 文件引用了 esm 模块的话会报错:
require() of ES Module /xxx/yyy/zzz/utils.mjs not supported.Instead change the require of /xxx/yyy/zzz/utils.mjs to a dynamic import() which is available in all CommonJS modules.
按照指示,改成动态引入 import()
就可以了。
import('./utils.mjs').then(console.log);
// [Module: null prototype] {
// default: [Function: runEsm],
// runSub: [Function: runSub]
// }
可以看出,export default
导出的东西被挂载 default
字段上。上面说了在标准还没落地时,webpack 就是这么做的。不知道是不是 NodeJS 参考了这个实现。
- 在 .mjs 文件 文件引用了 commonjs 模块的话就没问题。= =!
只不过 commonjs 里并没有 default
这个概念,所以如 import runCommonDefault, { runExport } from './utils.cjs';
里,runCommonDefault
就是整个 module.exports
导出的对象。而 runExport
就是纯粹对 module.exports
导出对象的解构,属于其中一个字段内容。
三、esm 模块不支持 __filename,__dirname
esm 模块里是没有 __filename
,__dirname
这两个全局变量的(其实不是全局变量,和 require 一样是外部传进来,姑且当其是个全局变量)。可以这样实现:
import path from 'path';
import url from 'url';
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);