evanw / esbuild

An extremely fast bundler for the web
https://esbuild.github.io/
MIT License
37.93k stars 1.13k forks source link

Add entryPoint field to args of onLoad callback #873

Open pandagood opened 3 years ago

pandagood commented 3 years ago

Adds a entryPoint field in the args of onLoad callback:

{
  entryPoint: 'react-dom/server',
  path: '/Volumes/private/github/package/node_modules/react-dom/server.browser.js',
  namespace: 'file',
  pluginData: undefined
}

This is useful, here is an example, transform npm module to esm(solve the #864 problem):

1,This is entryPoints:

const entryPoints = [
  'react',
  'react-dom',
  'react-dom/server',
  'react-router-dom/server'
]

2, Collect the real entryPoints of npm module, it's very fast

The first build is to collect real entryPoints.

const entries = {};  // real entryPoints collection

esbuild.build({
  write: false,
  entryPoints,
  plugins: [{
    name: 'collect-module-entry',
    setup(build) { 
      build.onLoad({ filter: /.*/, namespace: 'file' }, async (args) => {

        // This is important
        if(entryPoints.includes(args.entryPoint)){
          entries[args.entryPoint] = args.path;
        }

        return { contents:"export {};" }
      })
    }
  }],
})

The collect result :

{
  "react": '/Volumes/private/github/package/node_modules/react/index.js',
  "react-dom": '/Volumes/private/github/package/node_modules/react-dom/index.js',
  "react-dom/server": '/Volumes/private/github/package/node_modules/react-dom/server.browser.js'
}

3, Start the final build, transform npm module to esm

const realEntries = Object.values(entries);

esbuild.build({
  ...
  splitting:true,
  format: 'esm',
  entryPoints: realEntries,
  plugins: [{
    name: 'cjs-to-esm',
    setup(build) { 
      build.onLoad({ filter: /.*/, namespace: 'file' }, async (args) => {

        if(realEntries.includes(args.entryPoint)){
          const keys = Object.keys(require(args.path))
            .filter(i=>i!="default")
            .join(', ')

          return { 
            contents: `export { ${keys} } from "${args.path}";import m from "${args.path}";export default m;`, 
            resolveDir: process.cwd() 
          }
        }        
      })
    }
  }],
})
hardfist commented 3 years ago

since esbuild already supports plugindata, i think you can put entryPoint in pluginData yourself https://github.com/evanw/esbuild/blob/master/CHANGELOG.md#0838

pandagood commented 3 years ago

@hardfist Thanks. I know this, but cannot match entryPoint and real entryPoint in OnResolve or onLoad's args.

{
  path: 'react',
  importer: 'react',
  namespace: 'transform',
  resolveDir: '/Volumes/private/github/package',
  pluginData: undefined
}
....
{
  path: './cjs/react.development.js',
  importer: '/Volumes/private/github/package/node_modules/react/index.js',
  namespace: 'file',
  resolveDir: '/Volumes/private/github/package/node_modules/react',
  pluginData: undefined
}

The entryPoint and real entryPoint can only get one of them. If you know how to get them at the same time, please let me know.

hardfist commented 3 years ago

you can save origin entrypath in plugindata in resolve hooks,then it will carry the origin path to onload hooks by plugindata

pandagood commented 3 years ago

@hardfist Can you provide sample code?

I know that parameters can be passed between plugins through plugindata, but the origin entryPoint already exists, there is no need to pass it.

const entryPoints = [ 'react', 'react-dom', 'react-dom/server', 'react-router-dom/server' ]

I need to collect the real entryPoint of the module through the first build.

This is what i want:

{
  "react": '/Volumes/private/github/package/node_modules/react/index.js',
  "react-dom": '/Volumes/private/github/package/node_modules/react-dom/index.js',
  "react-dom/server": '/Volumes/private/github/package/node_modules/react-dom/server.browser.js'
}

But in onLoad or onResolve, the origin entryPoint and real entryPoint will not exist at the same time, only one of them will appear.

// onResolve args
{
  path: 'react',
  importer: '',
  namespace: '',
  resolveDir: '/Volumes/private/github/package',
  pluginData: undefined
}

// onLoad args, 
{
  path: 'react',
  namespace: 'transform',
  pluginData: undefined
}

Cannot match the origin entryPoint and real entryPoint.

ggoodman commented 3 years ago

@pandagood I know that this isn't specifically what you want but a work-around might be to use the clever hack proposed here to explicitly resolve the entrypoint using esbuild: https://github.com/evanw/esbuild/issues/814#issuecomment-780951812.

pandagood commented 3 years ago

@ggoodman Thanks, this is useful.

rizrmd commented 3 years ago

This line is SSR:

const keys = Object.keys(require(args.path))

It would fail if we try to build browser specific package that includes window or self variables. To make it work, we need to polyfill our nodejs env with browser polyfill like jsdom. From my experience, any kind of browser polyfill will bloat global vars, make everything slower and create some unexpected behavior.

Unfortunately my project stuck in this state >.<

rizrmd commented 3 years ago

I think I will try using JS parser like @babel/parser to parse the import keys.