Appearance
之前已经提到了ESM和CommonJS模块系统的几个重要差异,比如,ESM导入时必须显式的带上文件扩展名,而CJS的 require()
文件扩展名完全是可选的。 下面接着讨论一下ESM和CJS的其它重要差异以及2个模块系统之间在必要时如何结合使用。
ESM默认严格模式
ESM隐式的运行在严格模式下。这意味着我们不必在文件的最开头添加 "use strict;"
语句。并且ESM严格模式是不能被禁用的,因此我们不能使用未声明的变量,或者 with
语句,或其它只能在非严格模式才允许的功能,这绝对是一件好事,因为严格模式更加的安全。
⭐ ESM中缺少的引用
在ESM中,一些CommonJS中比较重要的引用是不存在的😅。
- 比如
require
&exports
&module.exports
&__dirname
&__filename
- 因为ESM运行在严格模式下,如果你直接使用这些不存在变量,会直接抛出
ReferenceError
js
// ❌ ReferenceError: exports is not defined in ES module scope
console.log(exports)
// ❌ ReferenceError: module is not defined in ES module scope
console.log(module)
// ❌ ReferenceError: __filename is not defined in ES module scope
console.log(__filename)
// ❌ ReferenceError: __dirname is not defined in ES module scope
console.log(__dirname)
对CommonJS中的 exports
和 module
我们已经讨论很多了; __filename
和 __dirname
表示相对于当前模块文件的绝对路径和相对于父文件夹的绝对路径。当我们需要构建一个相对当前文件的相对路径时,这2个特殊的变量会非常的有用。
TIP
在ESM中,可以通过特殊的 i
对象来获取当前文件URL的引用。具体来讲,i
是对当前模块文件的引用,格式类似是 file:///path/to/current_module.js
。通过这个值我们可以通过绝对路径的方式重建 __filename
& __dirname
功能🎉。
js
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log(__filename)
console.log(__dirname)
同样可以通过下面方式重建 require()
函数:
js
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
🌰 ./cjs/math.js
是一个 CommonJS
模块,注意 cjs
文件夹中的 package.json
的 type
字段不要声明为 "module"
js
module.exports = function(a, b) {
return a + b
}
在ESM中导入CommonJS模块:
js
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
// 使用定义的 require导入CJS模块
const add = require('./cjs/math')
console.log(add(1, 2))
另一个差异就是,ESM中的 this
关键词:
- 因为ESM是严格模式,
this
表示undefined
- 而CJS中,
this
表示对exports
的引用
js
// this.js - ESM
console.log(this) // undefined
// this.js - CJS
console.log(this === exports) // true
⭐ 互操性
上面我们已经讨论了,如果通过 module.createRequire
函数在ESM中导入CJS模块。除了上面那种方式外,还可以使用标准的 import
语法导入CJS模块,但仅限于 默认导出
😎:
js
// ✅ 导入CJS中的默认导出
import packageMain from 'commonjs-package'
// ❌ 导入CJS中的有名导出
import { method } from 'commonjs-package'
因此上面的例子可以写为:
js
import add from './cjs/math.js'
console.log(add(1, 2))
WARNING
🚨 不能在CommonJS模块中导入ESM模块
另外,ESM不能直接以模块形式导入JSON文件,这在CommonJS中是用得很多一个功能。下面 import
语句将失败:
js
// ❌ TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".json" for
import data from './data.json'
为了突破这个限制,我们可以再次使用 module.createRequire()
工具💡:
js
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
const data = require('./data.json')
console.log(data)
ESM原生支持JSON模块的能力还在开发中,因此,在未来我们可能可以不需要依赖 createRequire()
的帮助,就可以直接导入JSON模块了😎。
fileURLToPath & pathToFileURL
译者补充:关于ESM中的 fileURLToPath
& pathToFileURL
定义和用法:
typescript
/**
* This function ensures the correct decodings of percent-encoded characters as
* well as ensuring a cross-platform valid absolute path string.
*
* ```js
* import { fileURLToPath } from 'url';
*
* const __filename = fileURLToPath(import.meta.url);
*
* new URL('file:///C:/path/').pathname; // Incorrect: /C:/path/
* fileURLToPath('file:///C:/path/'); // Correct: C:\path\ (Windows)
*
* new URL('file://nas/foo.txt').pathname; // Incorrect: /foo.txt
* fileURLToPath('file://nas/foo.txt'); // Correct: \\nas\foo.txt (Windows)
*
* new URL('file:///你好.txt').pathname; // Incorrect: /%E4%BD%A0%E5%A5%BD.txt
* fileURLToPath('file:///你好.txt'); // Correct: /你好.txt (POSIX)
*
* new URL('file:///hello world').pathname; // Incorrect: /hello%20world
* fileURLToPath('file:///hello world'); // Correct: /hello world (POSIX)
* ```
* @since v10.12.0
* @param url The file URL string or URL object to convert to a path.
* @return The fully-resolved platform-specific Node.js file path.
*/
function fileURLToPath(url: string | URL): string;
/**
* This function ensures that `path` is resolved absolutely, and that the URL
* control characters are correctly encoded when converting into a File URL.
*
* ```js
* import { pathToFileURL } from 'url';
*
* new URL('/foo#1', 'file:'); // Incorrect: file:///foo#1
* pathToFileURL('/foo#1'); // Correct: file:///foo%231 (POSIX)
*
* new URL('/some/path%.c', 'file:'); // Incorrect: file:///some/path%.c
* pathToFileURL('/some/path%.c'); // Correct: file:///some/path%25.c (POSIX)
* ```
* @since v10.12.0
* @param path The path to convert to a File URL.
* @return The file URL object.
*/
function pathToFileURL(path: string): URL;
2022年10月22日21:26:20