developit / microbundle

📦 Zero-configuration bundler for tiny modules.
https://npm.im/microbundle
MIT License
8.06k stars 361 forks source link

UMD file does not work in nextjs. #860

Closed amitpareek49 closed 3 years ago

amitpareek49 commented 3 years ago

Hi Team,

I am trying to build a npm module using microbundle. I have to make it work for both react js (also other front end frameworks) apps and next js apps. However, I am not able to achieve it. Below is the configuration that I am using -

"scripts": {
    "build": "microbundle --sourcemap false && npm run microbundle-standalone",
    "microbundle-standalone": "microbundle -o dist/index.min.js --external none --format umd --sourcemap false",
    "dev": "microbundle watch",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

This script generates a "umd" file which is what I am eventually using for npm module in the reactjs based application. The same file doesn't work for nextjs. Issue that I face using using the npm package in nextjs -

Error: Cannot find module '<path-to-node-modules>/dist/index.modern.js'
    at createEsmNotFoundErr (internal/modules/cjs/loader.js:842:15)
    at finalizeEsmResolution (internal/modules/cjs/loader.js:835:15)
    at resolveExports (internal/modules/cjs/loader.js:424:14)
    at Function.Module._findPath (internal/modules/cjs/loader.js:464:31)
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:802:27)
    at Function.Module._load (internal/modules/cjs/loader.js:667:27)

When I add the same modern js file in the npm module, it stops working for react js projects.

Is there a generic guidance to make a npm module work for both next js and other standard front end frameworks?

rschristian commented 3 years ago

Can you provide your package's package.json file, or at least the portions that refer to the module exports please? It seems you have provided an export that Next tries to consume (that export being /dist/index.modern.js) but you haven't uploaded that file to NPM.

What are you referring to as "react js projects"? Do you mean Create-React-App?

amitpareek49 commented 3 years ago

Hi @rschristian - Thank you for looking into the issue. Yes, for "react js project" I mean any app react based app which is not server side rendered. It could also be a Create-React-App. I have not provided /dist/index.modern.js. Nextjs is trying to locate the /dist/index.modern.js file which is not present in the npm module's dist folder. Below are the package json file contents:

  "source": "src/index.ts",
  "main": "dist/index.umd.js",
  "exports": "./dist/index.modern.js",
  "module": "dist/index.module.js",
  "unpkg": "dist/index.umd.js",
  "scripts": {
    "build": "microbundle --sourcemap false && npm run microbundle-standalone",
    "microbundle-standalone": "microbundle -o dist/index.min.js --external none --format umd --sourcemap false",
  },
  "files": [
    "dist/index.umd.js"
  ]

When the below config in package.json the npm module works for nextjs but not for "Create React App" -

  "source": "src/index.ts",
  "main": "dist/index.js",
  "exports": "./dist/index.modern.js",
  "module": "dist/index.module.js",
  "unpkg": "dist/index.umd.js",
  "scripts": {
    "build": "microbundle --sourcemap false && npm run microbundle-standalone",
    "microbundle-standalone": "microbundle -o dist/index.min.js --external none --format umd --sourcemap false",
  }
    "files": [
    "dist/index.umd.js",
    "dist/index.modern.js",
    "dist/index.module.js",
  ],

It is a strange behaviour as the main file points to index.js but it is not present in the published dist folder. When I publish the npm module with dist/index.js file it I get the following error -

<path-to-dist>/dist/index.modern.js:1
SyntaxError: Cannot use import statement outside a module
    at wrapSafe (internal/modules/cjs/loader.js:979:16)
    at Module._compile (internal/modules/cjs/loader.js:1027:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)

What I am basically looking for is that is there a way where just one UMD file would work for nextjs app and create react app (any app not using server side rendering).

Let me know if this information is enough. I can share more info if you need. Thanks in advance.

rschristian commented 3 years ago

Well, from build tool to build tool, how package exports are consumed will change. There's a good half-dozen popular tools that use React, all of which can (and do!) consume exports differently. This isn't a client-side React vs server-side React issue. It has nothing to do with React at all, actually. Just the build tool in question.

I have not provided /dist/index.modern.js. Nextjs is trying to locate the /dist/index.modern.js file which is not present in the npm module's dist folder.

Well that'd be an issue. Why do you have exports set to that path if you're not uploading that file?

<path-to-dist>/dist/index.modern.js:1
SyntaxError: Cannot use import statement outside a module
   at wrapSafe (internal/modules/cjs/loader.js:979:16)
   at Module._compile (internal/modules/cjs/loader.js:1027:27)
   at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
   at Module.load (internal/modules/cjs/loader.js:928:32)
   at Function.Module._load (internal/modules/cjs/loader.js:769:14)

Is this coming from CRA? The issue is that /dist/index.modern.js is ESM, but tools will expect it to be CJS by default as you have not set "type": "module" or the file extension to .mjs. Without this, CJS is assumed. So CRA tries to load it as if it was CJS, but finds an import statement, which, like the message says, cannot be used outside of a module.

What I am basically looking for is that is there a way where just one UMD file would work for nextjs app and create react app

Why UMD? Neither of these tools at the moment are touching the UMD, if you didn't realize this. Nor should UMD really be the go-to these days.

Include your whole /dist folder, and then rename index.modern.js -> index.modern.mjs. Make sure to update the file path in your pkg.json too. .mjs specifies that this is a module, so it shouldn't be loaded as if it was CJS

amitpareek49 commented 3 years ago

Thank you @rschristian for your prompt reply. I agree that the it is the issue with the build tool not with react at all. I mentioned the details to explain the exact issue that I am seeing right now. So basically you are suggesting the below steps -

Is this the right understanding? I will try to make changes as per your suggestions.

<path-to-dist>/dist/index.modern.js:1
SyntaxError: Cannot use import statement outside a module
   at wrapSafe (internal/modules/cjs/loader.js:979:16)
   at Module._compile (internal/modules/cjs/loader.js:1027:27)
   at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
   at Module.load (internal/modules/cjs/loader.js:928:32)
   at Function.Module._load (internal/modules/cjs/loader.js:769:14)

is coming from nextjs app not from CRA. CRA works fine when dist folder only exposing the UMD file. Why is the nextjs app expecting the dist/index.modern.js file but CRA is not.

The reason why I used the UMD file was that same file could be used in both script tag (CDN hosted file) and the npm module.

Thank you for the help :)

rschristian commented 3 years ago

So basically you are suggesting the below steps -

No, don't remove "exports", just ensure the path it points to exists in your published module and ensure it's a module.

Basically, the way this works is that your export is assumed to be CJS by default with the .js file extension. You can either set your module type with "type": "module" so that now the .js is assumed to be ESM OR you can use the .mjs extension which is explicitly ESM, regardless of the overall module type. Your index.modern.js is ESM, but because you've done neither of those two things, NextJS assumes it's CJS and tries to import it as such, but gets confused.

CRA works fine when dist folder only exposing the UMD file. Why is the nextjs app expecting the dist/index.modern.js file but CRA is not.

I'm not a CRA user, so I'd have to check, but what it might be doing is checking to see if the paths exist, otherwise falling back. Don't remove files from being published, that's just not the "workaround" to take away from here, and won't work in most tools. Instead, fix your export paths.

These build tools look at certain keys in your pkg.json to choose which file to read from. Which keys these choose is completely up to them, and there might not be any consistency across any tools. This is just the way it is, though with exports being standardized, the ecosystem is getting better.

So Next might read "foo" and CRA could read "bar". Point is, they might look for different keys and follow the values you've set for them.

The reason why I used the UMD file was that same file could be used in both script tag (CDN hosted file) and the npm module.

You can use ESM via script tag too, and I'd suggest doing so. Skypack is an ESM CDN, works anywhere you can use <script type="module"> (93% global adoption, according to caniuse)

rschristian commented 3 years ago

If your library is open source, if you provide a link I could try to get that working for you.

I get that the CJS + ESM mix along with export maps and various build tool shenanigans isn't a simple thing to jump straight into.

rschristian commented 3 years ago

Hopefully this is answered. Let me know if it should be reopened.