esm only 的宣言
一年多以来,许多项目开始转向了 esm only,即仅支持 esm 而不支持 cjs,以此来迫使整个生态更快的迁移到 esm only。
一些流行的项目已经这样做了
sindresorhus 维护的上千个 npm 包
node-fetch
remark 系列
更多。。。
它们声称:你可以仍然使用现有版本而不升级到最新版,大版本更新不会影响到你。事实如何?
吾辈之前碰到过几次无法使用 esm only 包的问题,每当吾辈想尝试 esm only 时,总是还有一些问题,最痛苦的是,一些包是 esm only,而另一些是 cjs only,总要选择放弃一边,fuck esm only。主要问题一些是 cjs only 的包,以及必须兼容的包 typescript/jest/ts-jest/wallaby 未能正确支持 esm。当然,吾辈可以选择寻找 esm only 包的替代品,例如 globby => fast-glob、remark => markdown-it、node-fetch =>
[email protected]
,lodash-es => lodash,但这终究不是一个长久的选择,更何况有些包很难真正找到替代品,例如 remark 系列。
那么,使用旧版本的包有什么问题呢?
主要问题是很难找到正确的版本,当然,如果使用的是相对独立的包,例如 node-fetch 这个,就可以直接使用 v2 版本即可。但如果使用的是 vuepress/remark 这种 monorepo 中包含许多小型包的项目,你很难找到每个子项目正确的版本。
吾辈最近在做 epub 生成器的时候需要从 markdown 并操作 ast 做一些转换,最后转换为 html,因此再次使用 remark,也决定真正尝试使用 esm,下面是一些尝试的过程。
ts 4.7 发布文档
1 2
|
import { helper } from './foo.js' helper()
|
看起来是否会很奇怪,但现在只能这样写,typescript 甚至会这样提示
https://github.com/esbuild-kit/tsx/issues/38
现在需要修改为
1 2 3 4
|
import fsExtra from 'fs-extra' import path from 'path'
console.log(await fsExtra.readdir(path.resolve()))
|
或者修改为以下代码使用
ts-node --esm <file>
运行(tsx 不支持这种方式)
1 2 3 4
|
import fsExtra = require('fs-extra') import path from 'path'
console.log(await fsExtra.readdir(path.resolve()))
|
https://flaviocopes.com/fix-dirname-not-defined-es-module-scope/
,之后在谈到 esbuild 时再说打包 cjs bundle 如何处理
import.meta.url
(在 cjs 中不支持,又是二选一)。
https://stackoverflow.com/a/70020984
这个 issue 中作者的回答
,修改命令
1
|
esbuild src/bin.ts --platform=node --outfile=dist/bin.cjs --inject:./import-meta-url.js --define:import.meta.url=import_meta_url --bundle --sourcemap --format=cjs
|
遗憾的是,这不再生效,bundle 的代码如下
1 2 3 4 5 6 7 8 9 10 11 12
|
var import_meta_url2 = require('url').pathToFileURL(__filename) console.log(import_meta_url2)
var import_path = __toESM(require('path'), 1) var import_url = require('url') ;(async () => { const __filename2 = (0, import_url.fileURLToPath)(import_meta_url) const __dirname = import_path.default.dirname(__filename2) console.log(__dirname) })()
|
可以明显看到,注入的脚本的变量名被修改了,从
import_meta_url
=>
import_meta_url2
,这是奇怪的问题。。。
或许可以替换
--inject
=>
--banner
1
|
esbuild src/bin.ts --platform=node --outfile=dist/bin.cjs --define:import.meta.url=import_meta_url --bundle --sourcemap --banner:js="var import_meta_url = require('url').pathToFileURL(__filename)" --format=cjs
|
这样就生效了
那么,运行 esm bundle 呢?
也会出现错误
1 2 3
|
throw new Error('Dynamic require of "' + x + '" is not supported')
Error: Dynamic require of "fs" is not supported
|
按照
这里
的解决方法修改命令
1
|
esbuild src/bin.ts --platform=node --outfile=dist/bin.esm.js --bundle --sourcemap --banner:js="import { createRequire } from 'module';const require = createRequire(import.meta.url);" --format=esm
|
现在,bundle 后的代码可以终于运行了。