-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Export commonjs named exports in ESM #442
Comments
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: to get export names of a commonjs module, i created a const mod = require('xxx')
fs.writeFileSync('./peer.exports.json', JSON.stringify({exports: Object.keys(mod)})) then run |
I wonder why my module exports only a default binding even if I passed |
@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 #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 (#546), I use this to write |
@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 |
@ije That works great, thanks, with one issue: esm-dev/esm.sh#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
The text was updated successfully, but these errors were encountered: