motivation
Since updating nodejs@18 and switching to esm only, many libraries have been replaced to support esm import, but fs-extra has not been The use of esm is correctly supported, and no suitable replacement has been found. After the a PR proposed by us was rejected, I decided to re-release a fs-extra-unified that correctly supports the use of esm module.
If you don't know what fs-extra is, here is a brief introduction: it is a tool library related to nodejs file operations, which is used to completely replace the fs module. Before the existence of fs/promises, it has all the fs Asynchronous callback functions are converted to Promises. At the same time, some other very useful utility functions are provided for use, such as pathExists
, remove
, mkdirp
, copy
.
For example after deleting a temporary directory and then rebuilding it
import { remove, mkdirp } from 'fs-extra'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const tempPath = path.resolve(__dirname, '.temp')
await remove(tempPath)
await mkdirp(tempPath)
But in the esm module, it currently does not support the use of ts correctly. For example, the above code can only be used normally in the cjs module. In the esm module, the following imports must be used
import fsExtra from 'fs-extra'
const { remove, mkdirp } = fsExtra
Even though fs-extra@11 claims to support esm, it is supported by another entry fs-extra/esm
, and the ts type definition has not been updated yet, so it cannot actually be used in ts. For example the above import can be converted to the following import
import { remove, mkdirp } from 'fs-extra/esm'
In addition, it has another troublesome problem, that is, the functions exported by fs are not supported, for example, the following code will report an error
import { readFile } from 'fs-extra/esm'
import { fileURLToPath } from 'node:url'
console.log(await readFile(fileURLToPath(import.meta.url), 'utf-8'))
The official claims that only fs-extra/esm
will only export some unique functions. The functions originally exported by fs need to use the fs/promises
module, which needs to be modified to the following imports
import { readFile } from 'fs/promises'
import { fileURLToPath } from 'node:url'
console.log(await readFile(fileURLToPath(import.meta.url), 'utf-8'))
Ok, looks like esm/ts support is second class citizen, let me summarize known issues
- The default
fs-extra
entry does not support esm named imports -
fs-extra/esm
does not support the original functions of fs -
fs-extra/esm
does not correctly declare the ts type definition - cjs/esm use different behavior
It is precisely because it is a commonly used tool library that we republish it.
Republish
The basic idea is very simple. Scan the modules exported by fs-extra
through the script, then generate an esm entry, and finally declare it correctly in the exports
of package.json, so that there is no difference between esm and cjs at the usage level.
Desired result
-
esm supports named import and default import
import { readdir } from 'fs-extra' import fsExtra from 'fs-extra' import { fileURLToPath } from 'url' import path from 'path' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) console.log(await readdir(__dirname)) console.log(await fsExtra.readdir(__dirname))
-
cjs supports named import and default import
import { readdir } from 'fs-extra' import fsExtra from 'fs-extra' const { readdir: readdirCjs } = require('fs-extra') const fsExtraCjs = require('fs-extra') ;(async () => { console.log(await readdir(__dirname)) console.log(await readdirCjs(__dirname)) console.log(await fsExtra.readdir(__dirname)) console.log(await fsExtraCjs.readdir(__dirname)) })()
Correctly support the use of ts, esm no longer uses a separate entry
Final implementation method
-
Use the generate script to generate esm entry
const fsExtra = require('./lib/index') const path = require('path') const { difference } = require('lodash') function scan() { const excludes = [ 'FileReadStream', 'FileWriteStream', '_toUnixTimestamp', 'F_OK', 'R_OK', 'W_OK', 'X_OK', 'graceful', ] return difference(Object. keys(fsExtra), excludes) } function generate(list) { return ( "import fsExtra from './index'\n" + list.map((item) => `export const ${item} = fsExtra.${item}\n`).join('') + `export default {${list .map((item) => `${item}: fsExtra.${item},`) .join('')}}` ) } async function build() { const list = scan() const code = generate(list) await fsExtra.writeFile(path.resolve(__dirname, 'lib/esm.mjs'), code) } build()
-
Then add the dependency of
@types/fs-extra
and re-export it inindex.d.ts
export * from 'fs-extra'
-
Declare the correct
exports/types
field in package.json
{ "exports": { ".": { "import": "./lib/esm.mjs", "require": "./lib/index.js" } }, "types": "./index.d.ts" }
Conclusion
If fs-extra finally supports esm/ts correctly, we will also delete this module to avoid trouble, but before that, we can only use this module first.
Top comments (0)