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
enginesin the Parcel config tobrowsers: ['> 0.5%', 'last 2 versions', 'not dead'],node: '>= 12', and alsoparcel: '>2.5' - Adjust the
contextfield tobrowser/node - Add
targetsfield, 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 includereactinincludeModulesfield or setincludeModulesto true, it produced the error"Can't have more than 1 copy of React...". - Remove
targets, addmainandmoduletopackage.json(ran to same issue above) - Adjust
outputFormattoesmodule/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, pointaliasof react toreactinnode_modulesinwebpackfield, also sometimes it couldn't findstyled-componentseven thatstyledComponentsis set incompilerfield. 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:
- Parcel bundling sample script.
build.tsin the root folder is a script to bundle modules with Parcel APIsetupPackage.tsinsrc/folder is a script to adjust the content ofpackage.jsonand will be called at the end of the bundling process, it will copy it over todistfolder.
- Next.js project consume bundled Parcel library.
Step to reproduce:
- Clone this React project "Parcel bundling sample script" and run the command below to generate bundled modules in
distfolder
yarn && yarn build
- Clone this project "Next.js project consume bundled Parcel library" (created with the
create-next-app). - Copy generated
distfolder from "Parcel bundling sample script" project over the Next.js project, run command below in Next.js project's terminal to install local module in project's dependenciesnode_modulesand build dev server:
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 .