Open remorses opened 4 years ago
I could do this in a js plugin that changes the entrypoint in a fake ESM module that exports from the commonjs module (we are already doing the same thing in snowpack with rollup) but it would be cool to have this feature built into the bundler
this is useful for me, i created a cdn using esbuild to bundle npm package to esm version:
https://github.com/postui/esm.sh
to get export names of a commonjs module, i created apeer.js
like:
const mod = require('xxx')
fs.writeFileSync('./peer.exports.json', JSON.stringify({exports: Object.keys(mod)}))
then run node peer.js
.
I wonder why my module exports only a default binding even if I passed --format=esm
and my module(in esm) exports many named bindings. Does it because I import commonjs module?
@remorses, I noticed that you're doing a lot of work in exporting npm packages as ESM. Have you been able to work out a fully working solution?
I've also been looking to accomplish the same. I started with esinstall, but it's way too slow to experiment with. So, I've been trying to do the same with esbuild.
So far, it seems to more or less just work except for named exports from CJS.
I like the workaround from @ije, in just evaluating the module to get its export names. But, that doesn't account for packages that export different code for browsers.
After reading evanw/esbuild#532, I found guybedford/cjs-module-lexer. Combining this with webpack/enhanced-resolve does the trick for me:
import {promisify} from 'util'
import enhancedResolve from 'enhanced-resolve'
import * as moduleLexer from 'cjs-module-lexer'
const resolve = promisify(
enhancedResolve.create({
mainFields: ['browser', 'module', 'main']
})
)
let lexerInitialized = false
async function getExports(modulePath) {
if (!lexerInitialized) {
await moduleLexer.init()
lexerInitialized = true
}
try {
const exports = []
const paths = []
paths.push(await resolve(process.cwd(), modulePath))
while (paths.length > 0) {
const currentPath = paths.pop()
const results = moduleLexer.parse(await fs.readFile(currentPath, 'utf8'))
exports.push(...results.exports)
for (const reexport of results.reexports) {
paths.push(await resolve(path.dirname(currentPath), reexport))
}
}
return `{ ${exports.join(', ')} }`
} catch (e) {
return '*'
}
}
Then, since plugins aren't applied to entry points (evanw/esbuild#546), I use this to write export
statements in temporary files. Passing these temporary files as entry points into esbuild allows me to accomplish what esinstall does.
@vinsonchuong it's amazing, i will try your reslution!
Any progress on this?
since the guybedford/cjs-module-lexer @vinsonchuong suggested above can't handle some edge cases, i created another cjs export parser that uses swc ast walker as a wasm module, it is used by esm.sh CDN, for now it is good: https://www.npmjs.com/package/esm-cjs-lexer
@ije Nice work! Could you share your setup of using cjs-esm-exports
to compile cjs
module to esm
module? I checked esm.sh and I believe that's what I'm looking for, but I wanna run it locally.
@ije @ruanyl What was the last word on this? What's the best way to fix transforming packages like "react" erasing the esm named exports? Thanks!
@ije That works great, thanks, with one issue: https://github.com/esm-dev/esm.sh/issues/713 - exportDefault: true is not set for React for some reason.
Currently a module that uses commonjs features like
exports
andrequire
will translate to a default export exporting themodule.exports
object.But some packages like
react
rely on bundlers like webpack to allow you to use named imports likeimport { useEffect } from 'react'
, event thoughtreact
is a commonjs module.It would be cool to detect the cjs named exports and reexport them in the ESM output.
Code
currently executing
esbuild --bundle --outdir=out --format=esm x.js
onbecomes
what i am asking for is to have this output
Nodejs is trying to allow the same interopability and they are using a lexer to detect commonjs named exports: https://github.com/guybedford/cjs-module-lexer
That package implements the lexer in C and uses WASM to execute it in js, we could use the same code nodejs uses to detect commonjs named exports and reexport them as ESM