最近社区出现了一个 Pure ESM package 的讨论,作为没有经历过前端模块化方案百花齐放年代的前端,对 CJS 这种 “方言” 解决方案不爽很久了。因此我毫不犹豫的将自己的 npm 包模块方案切换成了 ESM。

在迁移的过程中遇到过很多问题。接下来,我把自己遇到的一些问题,记录下来。

注意时间:本文写于 2022.02.25。

Node 部分

Node 版本

node 版本必须要大于 12

修改 package.json

  1. package.json 中有一个名为 type 的字段,其默认值是 commonjs,我们需要将其修改为 module
  2. 替换 "main": "index.js""exports": "./index.js"
    1. {
    2. "name": "test",
    3. "version": "1.0.0",
    4. "main": "./index.js",
    5. "description": "test",
    6. "type": "module",
    7. "keywords": [],
    8. "author": "meakle",
    9. "license": "MIT"
    10. }

动态引入

如果使用 CommonJS 作为模块方案,那么你可以在代码中这样进行引入

  1. const func = function() {
  2. // 动态的引入文件
  3. const obj = require('.....')
  4. console.log(obj)
  5. }
  6. func()

如果我们使用 ESM 规范的语法引入,将会报错

  1. const func = function() {
  2. // const obj = require('....')
  3. import { obj } from '....'
  4. // An import declaration can only be used at the top level of a module.
  5. console.log(obj)
  6. }
  7. func()

image.png

此时必须要动态的引入模块

  1. const func = async function() {
  2. const { obj } = await import('...')
  3. console.log(obj)
  4. }
  5. func()

__filename__dirname 失效

如下图所示,无法在 ESM 中使用 __fileename 或者 __dirname
image.png

我们可以通过 [import.meta.url](https://nodejs.org/api/esm.html#importmetaurl) 获取到 URL 对象,再通过 [fileURLToPath](https://nodejs.org/api/url.html#urlfileurltopathurl) 方法将 URL 转换为路径。

  1. import { fileURLToPath } from 'url'
  2. import { dirname } from 'path'
  3. const __filename = fileURLToPath(import.meta.url)
  4. const __dirname = dirname(__filename)

平常使用的时候,可以提前封装成一个函数。

路径需要写全

使用 require 引入路径时,路径可以简写,而使用 import 引入时,必须写全

  1. import x from '.'; // 不行
  2. import x from './index.js'; //

更多

关于 CJS 和 ESM 中的区别可以查阅官方文档:
👉 https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs

Typescript 部分

TS 版本

截止目前,若需要 TS 支持 ESM,必须使用 TS 的 nightly build 版本。

关于 nightly build 参考:https://www.typescriptlang.org/docs/handbook/nightly-builds.html

tsconfig 设置

设置 modulemoduleResolution 字段

  1. {
  2. "compilerOptions": {
  3. "module": "nodenext",
  4. "moduleResolution": "nodenext"
  5. }
  6. }

路径要写全

需要写成 import "./foo.js" 而不能写成 import "./foo"

注意:即使实际引入的文件是 ts 文件,但在 import 语句中引入的文件后缀依然是 .js 而不是 .ts

某些全局变量无法使用

某些类似全局的值,如 require()process 不能直接使用

我试了下, process 还是能用的,这里是 ts 文档里面提到的

更多

更多信息可以查阅 TS 文档: https://www.typescriptlang.org/docs/handbook/esm-node.html

最后

对于 ESM,我个人认为是大势所趋,虽然就目前来看依然不够成熟,还需要手动的配置才能使用,但是我相信不久的将来可以省去这些配置的功夫,前端的模块化方案也能趋于统一。