"Error: Cannot find module 'react'" when consuming Parcel's React component library in Next.js project

Viewed 515

Context:
I try to create a public react component library and use Parcel API to bundle it. The idea is to have a shared React components library to be consumed by other Next.js projects. And when the external project just wants one component in the library, it can import directly to the component it needs without importing the whole library.
What I want to achieve is to be able to import components from the reproduce-parcel-issue React library in the Next.js project:

/* Import Button component from "reproduce-parcel-issue/index.js" */
import { Button } from "reproduce-parcel-issue";
// or
/* Import Button component from "reproduce-parcel-issue/components/Button/Button.js" */
import { Button } from "reproduce-parcel-issue/components/Button/Button"
// or
/* Import Button component from "reproduce-parcel-issue/components/Button/Anchor.js" */
import { Anchor } from "reproduce-parcel-issue/components/Button/Anchor" 

Issue:
Parcel is able to bundle the library into multiple modules perfectly. But when I try to consume it in the Next.js project that is generated by the "create-next-app" command, it ran into error "Cannot find module 'react'" if I don't includeModules react in the Parcel options configuration script. And when I includeModule react in the config, then the error changes to "You might have more than one copy of React in the same app".

Attempted:
I created a script build.ts that will loop through a list of Parcel config and call to bundle API to generate multiple modules.
In Parcel, I tried to do these in the configuration:

  • Adjust the engines in the Parcel config to browsers: ['> 0.5%', 'last 2 versions', 'not dead'], node: '>= 12', and also parcel: '>2.5'
  • Adjust the context field to browser/node
  • Add targets field, but it doesn't seem to work (it can build but Next.js couldn't consume it - alway get an error like "Can't find module '...'"), also when I include react in includeModules field or set includeModules to true, it produced the error "Can't have more than 1 copy of React...".
  • Remove targets, add main and module to package.json (ran to same issue above)
  • Adjust outputFormat to esmodule/commonjs.

package.json

{
  "name": "reproduce-parcel-issue",
  "version": "1.0.0",
  "description": "Code configuration to reproduce parcel issue",
  "engines": {
    "node": ">= 12"
  },
  "browserslist": "> 0.5%, last 2 versions, not dead",
  "author": "Thien Huynh",
  "license": "MIT",
  "private": false,
  "devDependencies": {
    "@parcel/core": "^2.5.0",
    "@parcel/packager-ts": "2.5.0",
    "@parcel/transformer-typescript-tsc": "^2.5.0",
    "@parcel/transformer-typescript-types": "2.5.0",
    "@parcel/types": "^2.5.0",
    "@types/node": "^17.0.31",
    "@types/react": "17.0.41",
    "@types/react-dom": "17.0.13",
    "@types/styled-components": "5.1.24",
    "eslint": "7.32.0",
    "eslint-config-airbnb": "18.2.0",
    "eslint-config-airbnb-typescript": "^12.3.1",
    "eslint-config-prettier": "^6.11.0",
    "eslint-import-resolver-parcel2": "^1.0.1",
    "eslint-plugin-import": "2.25.4",
    "eslint-plugin-jest": "24.7.0",
    "eslint-plugin-jsx-a11y": "6.5.1",
    "eslint-plugin-prettier": "3.4.1",
    "eslint-plugin-react": "7.29.4",
    "eslint-plugin-react-hooks": "4.3.0",
    "eslint-plugin-simple-import-sort": "^7.0.0",
    "@typescript-eslint/eslint-plugin": "4.33.0",
    "@typescript-eslint/parser": "4.33.0",
    "dotenv": "^16.0.0",
    "postcss": "8.4.12",
    "postcss-preset-env": "6.7.1",
    "parcel": "^2.5.0",
    "prettier": "2.6.0",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "react-hotkeys-hook": "3.4.4",
    "react-is": "17.0.2",
    "react-tooltip": "4.2.21",
    "styled-components": "5.3.3",
    "ttypescript": "^1.5.13",
    "typescript": "4.6.2"
  },
  "resolutions": {
    "@types/react": "17.0.41",
    "@types/react-dom": "17.0.13"
  },
  "peerDependencies": {
    "framer-motion": "^4.x",
    "react": "^17.x",
    "react-dom": "^17.x",
    "react-hotkeys-hook": "^3.x",
    "react-is": "^17.x",
    "react-tooltip": "^4.x",
    "styled-components": "^5.x"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "scripts": {
    "prebuild": "rm -rf ./dist .parcel-cache && ttsc build.ts --outDir . --module es2022 --sourceMap true && mv build.js build.mjs",
    "build": "node build.mjs && yarn build:dts",
    "postbuild": "yarn build:prep:publish",
    "prebuild:dts": "echo 'building dts'",
    "build:dts": "ttsc --emitDeclarationOnly",
    "posbuild:dts": "echo 'done build dts'",
    "prebuild:prep:publish": "echo 'building for publishing'",
    "build:prep:publish": "ttsc src/setupPackage.ts --sourceMap false && mv dist/setupPackage.js ./dist/setupPackage.mjs && node dist/setupPackage.mjs",
    "postbuild:prep:publish": "echo 'done building for publishing'"
  },
  "exports": {
    ".": {
      "import": "./module/index.js",
      "node": "./module/index.js",
      "module": "./module/index.js",
      "default": "./index.js"
    },
    "./components/*": {
      "import": "./module/components/**/*.js",
      "node": "./module/components/**/*.js",
      "module": "./module/components/**/*.js",
      "default": "./components/**/*.js"
    },
    "./icons/*": {
      "import": "./module/icons/*.js",
      "node": "./module/icons/*.js",
      "module": "./module/icons/*.js",
      "default": "./icons/*.js"
    }
  }
}

Current Parcel option configuration:

{
    detailedReport: { assetsPerBundle: 20 },
    entries: 'src/index.ts' // or 'src/icons/*.tsx' or 'src/components/**/*.tsx',
    targets: {
      main: {
        distDir: 'dist',
        context: 'browser',
        engines: {
          browsers: ['> 0.5%', 'last 2 versions', 'not dead'],
          parcel: '>2.5',
        },
      },
      module: {
        distDir: 'dist/module',
        context: 'node',
        engines: {
          node: '>= 12',
          parcel: '>2.5',
        },
      },
    },
    defaultTargetOptions: {
      isLibrary: true,
      sourceMaps: false,
      shouldOptimize: false,
      outputFormat: 'esmodule',
      engines: {
        parcel: '>2.5',
      },
    },
    shouldPatchConsole: true,
    shouldAutoInstall: true,
    shouldContentHash: false,
    additionalReporters: [
      {
        packageName: '@parcel/reporter-cli',
        resolveFrom: fileURLToPath(import.meta.url),
      },
    ],
    defaultConfig: '@parcel/config-default',
    mode: 'production',
  }

In Next.js project, I tried:

  • Adjust next.config.js, point alias of react to react in node_modules in webpack field, also sometimes it couldn't find styled-components even that styledComponents is set in compiler field. So I think this may be an issue with Parcel bundled script, not from Next.js side.
/** @type {import('next').NextConfig} */
const path = require('path');
const nextConfig = {
  reactStrictMode: true,
  compiler: {
    styledComponents: true
  },
  webpack: (config, { isServer }) => {
    if (isServer) {
      console.log('config', path.resolve(process.cwd() + "/node_modules/react"))
      // don't resolvew 'fs' module on the client to prevent this error on build --> Error: Can't resolve 'fs'
      config.resolve.alias.react = path.resolve(process.cwd() + "/node_modules/react")
    }

    if (!isServer) {
      config.resolve.fallback.react = path.resolve(process.cwd() + "/node_modules/react")
    }

    return config;
  }
}
module.exports = nextConfig

Repositories:

Step to reproduce:

yarn && yarn build
yarn && yarn dev

Possible solution:
I don't know if this is the issue with Parcel or Next.js configuration. Maybe either making the right configuration options in the Parcel API to generate the bundled script can be used on the client-side (be able to use shared modules (react, react-dom, styled-components, ...) on the browser) or making Next.js to be able to consume the library directly (like importing the library as if it's an internal code/component in Next.js project) before building pages.

I already spent a week just fixing this build, so I really appreciate any help/suggestion to make this work .

0 Answers
Related