Skip to content
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

does not provide an export named "xxx" #744

Closed
sessionboy opened this issue Feb 3, 2021 · 11 comments
Closed

does not provide an export named "xxx" #744

sessionboy opened this issue Feb 3, 2021 · 11 comments

Comments

@sessionboy
Copy link

sessionboy commented Feb 3, 2021

I transform react library to esm through the following operation:

service.build({
    target: 'esnext',
    format: 'esm',
    bundle: true,
    loader: loaders,
    entryPoints: [ 
      require.resolve("react"),
      require.resolve("react-dom")
    ],
    outdir: chunkPath,
    define: { 
      'process.env.NODE_ENV': JSON.stringify('development')
    }
  }),

But I get an error while importing:

import { useState } from './@chunk/react/index.js'
         ^^^^^^^^
SyntaxError: The requested module './@chunk/react/index.js' does not provide an export named 'useState'

How can i solve this problem ?

node: v15.5.0 (Native support for esm)

esbuild": "^0.8.31"

@evanw
Copy link
Owner

evanw commented Feb 3, 2021

The problem is that when CommonJS modules are converted to ESM, they only export a single default export. It's not just esbuild that behaves this way; node itself does as well. This is because the exports of CommonJS modules can depend on run-time behavior.

You can do two things to fix this:

  1. You can change how you import this module to match the semantics of CommonJS-to-ESM:

    import React from './@chunk/react/index.js'
  2. You can write an ESM entry point with named re-exports from the CommonJS module. This can be done automatically with a plugin:

    const cjs_to_esm_plugin = {
      name: 'cjs-to-esm',
      setup(build) {
        build.onResolve({ filter: /.*/ }, args => {
          if (args.importer === '') return { path: args.path, namespace: 'c2e' }
        })
        build.onLoad({ filter: /.*/, namespace: 'c2e' }, args => {
          const keys = Object.keys(require(args.path)).join(', ')
          const path = JSON.stringify(args.path)
          const resolveDir = __dirname
          return { contents: `export { ${keys} } from ${path}`, resolveDir }
        })
      },
    }
    
    require('esbuild').build({
      target: 'esnext',
      format: 'esm',
      bundle: true,
      entryPoints: [
        'react',
        'react-dom',
      ],
      outdir: chunkPath,
      define: {
        'process.env.NODE_ENV': JSON.stringify('development')
      },
      plugins: [cjs_to_esm_plugin],
    })

@evanw
Copy link
Owner

evanw commented Feb 3, 2021

Closing because I believe the solutions described above to be sufficient. Either that or this is a duplicate of #442.

@evanw evanw closed this as completed Feb 3, 2021
@sessionboy
Copy link
Author

@evanw Thanks, this can solve my problem. 🍻
But I found another problem:

Two output files share the same path but have different contents: _dist/@chunk/index.js

This should be caused by different modules but using the same basename.
E.g:

entryPoints: [ 
      "....node_module/react/index.js",
     "....node_module/react-dom/index.js"
],

They have the same index.js, which caused a conflict.

I understand that you are preparing the onEmit method to solve this kind of problem, but how can I solve this problem before implementing the onEmit method ?

@evanw
Copy link
Owner

evanw commented Feb 4, 2021

That wasn't a problem with the code I posted because the entry points were the package names directly instead of the result of calling require.resolve on the package names. So that would work.

The underlying reason for this collision is that the path for the entry point ends in /index.js. But if the module is a virtual module (created by a plugin instead of being an actual file on the file system), then the path of the virtual module can be anything you want. It's just an arbitrary string that esbuild uses to identify the virtual module. In the code I posted it was the package name instead of the fully-resolved package path. You could also set it to something else if you wanted to by returning it in the path variable from the onResolve callback.

@sessionboy
Copy link
Author

@evanw Thanks, I understand, this is my negligence.

@sessionboy
Copy link
Author

sessionboy commented Feb 4, 2021

@evanw

2, You can write an ESM entry point with named re-exports from the CommonJS module. This can be done automatically with a plugin:

const cjs_to_esm_plugin = {
 name: 'cjs-to-esm',
 setup(build) {
   build.onResolve({ filter: /.*/ }, args => {
     if (args.importer === '') return { path: args.path, namespace: 'c2e' }
   })
   build.onLoad({ filter: /.*/, namespace: 'c2e' }, args => {
     const keys = Object.keys(require(args.path)).join(', ')
     const path = JSON.stringify(args.path)
     const resolveDir = __dirname
     return { contents: `export { ${keys} } from ${path}`, resolveDir }
   })
 },
}

@evanw Thank you for the plugin, but it has several problems.

1,Output path missing or wrong

When I build @material-ui/core:

require('esbuild').build({
  format: 'esm',
  entryPoints: [
     "@material-ui/core"
  ],
  ....
  plugins: [cjs_to_esm_plugin],
})

When the plugin is not used, the output path is ..../@chunk/@material-ui/core/esm/index.js. (Suppose it is P1)

After using the plugin, the output path becomes ..../@chunk/core.js. (Suppose it is P2).

The path P1 is a wrong path because it changed the import indicator:

import { Button } from "@material-ui/core"  // correct

// becomes:

import { Button } from "@material-ui/core/esm"  // wrong

The path P2 is also a wrong path, it is missing @material-ui/ path.

The correct output path should be ..../@chunk/@material-ui/core/index.js or ..../@chunk/@material-ui/core.js.

2,does not provide an export named 'default'

After using this plugin, export default is lost.

Good:

import { useState, useContext } from "./@chunk/react.js"

Bad:

import React from "./@chunk/react.js"
       ^^^^^
SyntaxError: The requested module './@chunk/react.js' does not provide an export named 'default'

The plugin is a good solution, but it still needs to be improved.

@sessionboy
Copy link
Author

@evanw please give me some help 🙏

@evanw
Copy link
Owner

evanw commented Feb 5, 2021

The plugin was just a starting point to work from. You should be able to take it from here. You can add the default key yourself, for example.

@sessionboy
Copy link
Author

@evanw Yeah, as you said, I added default key:

return { 
   contents: `
     import * as m from ${path};
     import { ${keys} } from ${path};
     export { ${keys} };
     export default m;
   `, 
   resolveDir 
}

Now it works.

But it will still cause the output path to be wrong or missing.

The P1 error path appears, I guess it is because esbuild does not generate the output path according to entryPoint, but generates the path according to the module file path, which causes this problem.

The P2 error path appeared because of the export code added using the above plugin. I don't know the specific reason behind it.

I don't know how to solve this problem, please give me some help. 🙏

@hwlv
Copy link

hwlv commented Aug 20, 2021

target: 'esnext',

Closing because I believe the solutions described above to be sufficient. Either that or this is a duplicate of #442.

I know that now there is no solution, I do not know how vite solve this

1 similar comment
@hwlv
Copy link

hwlv commented Aug 20, 2021

target: 'esnext',

Closing because I believe the solutions described above to be sufficient. Either that or this is a duplicate of #442.

I know that now there is no solution, I do not know how vite solve this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants