Search code examples
rollupjs

getting rollup to resolve extensionless imports in node_modules for browser bundling


rollup gives me errors like these when trying to import an NPM module:

main.js → stdout...
[!] Error: Could not load myfolder\node_modules\three\examples\jsm\renderers\CSS2DRenderer (imported by node_modules/openbim-components/core/SimpleRenderer/index.js): ENOENT: no such file or directory, open 'myfolder\node_modules\three\examples\jsm\renderers\CSS2DRenderer'
Error: Could not load myfolder\node_modules\three\examples\jsm\renderers\CSS2DRenderer (imported by node_modules/openbim-components/core/SimpleRenderer/index.js): ENOENT: no such file or directory, open 'myfolder\node_modules\three\examples\jsm\renderers\CSS2DRenderer'

The immediate cause is, that e.g. node_modules/openbim-components/core/SimpleRenderer/index.js contains imports like import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";

That is, Typescript style, or nodeJS style, without the .js suffix. (If I manually edit every single such import to include the .js suffix, the error goes away, but that is not the way..)

To clarify: The actual file is called CSS2DRenderer.js, WITH suffix.

I have tried figuring out from rollup.js documentation, what controls the handling/matching of suffix. It seems commonJS mode would support omitting the suffix? But openbim-components, to my knowledge, is not a commonJS module? (If I am mistaken here, that may be the answer?).

I am using "@rollup/plugin-node-resolve": "^15.2.1" in my rollup.config.mjs, as is apparently the way to use npm modules with rollup..? But its documentation does not mention any mechanisms regarding default/missing .js suffix.

Somewhere in rollup's internals, lives a function called addJsExtensionIfNecessary(), but it is apparently not assisting me here.. presumably, because it is tied to commonJS..?

I have tried to solve the issue using the additional rollup plugins 'rollup-plugin-includepaths' and 'rollup-plugin-resolve', which sound like they might help with this issue, but instead they just make it worse(?)

My goal is to use rollup to build the bundle.js file for a web page. Weirdly, the tool vite seems to handle this case, and vite internally uses rollup. But I am not in a position where I can just tell people to switch to vite (my proper goal is to make sure we can use the openbim-components tool generally in my company, so it must 'work in general', not just in a special case.)

I feel like I am missing some knowledge on how to use rollup properly.


Solution

  • There are two issues here.

    The openbim-components is published as ESM-only, however, the package.json file doesn't specify "type": "module". Either files should have *.mjs extension or type: "module" should be specified. However, since you are using bundler, that won't cause this problem. It becomes a problem only when you intend to run this code on Node.js runtime.

    Now, the next issue is problematic. The package three on which openbim-components depends on is using ESM and correctly specifies "type": "module" in the package.json file. However at the same time, it is using the new exports field. This is how it is specified:

    {
      "exports": {
        ".": {
          "import": "./build/three.module.js",
          "require": "./build/three.cjs"
        },
        "./examples/fonts/*": "./examples/fonts/*",
        "./examples/jsm/*": "./examples/jsm/*",
        "./addons/*": "./examples/jsm/*",
        "./src/*": "./src/*",
        "./nodes": "./examples/jsm/nodes/Nodes.js"
      },
    }
    

    This is the problem. These are not properly specified. As per the spec, the extension-less imports should specify the extension on the right side like this:

    {
      "exports": {
        "./examples/jsm/*": "./examples/jsm/*.js",
        "./addons/*": "./examples/jsm/*.js",
        "./src/*": "./src/*.js",
      }
    }
    

    Either, there should be extension on both side or if the author of the library wants the user to use extension-less imports, then only on the right hand side should be specified. The point being that the exports map should correctly map to the correct file with its extension.

    Webpack and Vite provides a alias resolution which you could have used here like. The Rollup doesn't provide anything out of box for this but it has an official plugin - @rollup/plugin-alias that may help you achieve this:

    import alias from '@rollup/plugin-alias';
    
    export default {
      // ...other configuration
      plugins: [
        alias({
          entries: [
            // Or something similar to this configuration
            {
              find: 'three/examples/jsm/renderers/CSS2DRenderer',
              replacement: 'three/examples/jsm/renderers/CSS2DRenderer.js'
            },
          ]
        })
      ]
    };
    

    I never used aliasing with Rollup before; but that is the only way for now. Also, consider raising issues with both the repositories.