Module not found error using Yarn 2 to link React components - reactjs

I've created a repository which contains a React app (created with create-react-app) and a components directory which contains a simple Material UI button. The folder structure is:
/components
/react-app
Both directories are set up to use Yarn 2, and are not in a workspace (as I'm trying to simulate projects in separate directories and simplify my real world scenario).
I build the components:
$ cd ~/components && yarn build
I then Yarn link the components to the React app:
$ cd ~/react-app & yarn link ../components -r -p
This results in a modification to package.json file in the react-app directory:
{
"name": "react-app",
...
"resolutions": {
"components": "portal:../components"
}
}
My App.tsx file looks like this:
import './App.css';
import { Button } from 'components';
import React from 'react';
function App() {
return (
<Button>Test</Button>
);
}
export default App;
However, when I run the React app using yarn start I get the following error:
./src/App.tsx
Module not found: Your application tried to access components, but it isn't declared in your dependencies; this makes the require call ambiguous and unsound.
I'm not sure what I'm doing wrong. If I add an explicit reference to the components directory within dependencies (which I don't believe I should have to do because I've already linked it) such as:
"dependencies": {
"components": "portal:../components"
}
Then I get the error:
./src/App.tsx
Module not found: You attempted to import ~/react-app/.yarn/$$virtual/components-virtual-de9a8055ab/2/components which falls outside of the project src/ directory. Relative imports outside of src/ are not supported.
Surely, I don't have to eject the app and find a way to bypass this error?
EDIT: According to the Yarn documentation "Webpack 5 will support PnP natively, but if you use Webpack 4 you'll need to add the pnp-webpack-plugin plugin yourself". At the time of writing, the latest version of create-react-app relies on v3.4.1 of react-scripts which in turn relies on Webpack 4. I therefore ejected my app to inspect the Webpack configuration, and it appears that this plugin is already installed. It's therefore not a CRA/Webpack issue. I also upgraded my version of Node from v10.16.0 to v12.16.3 to no avail.

TLDR; Add the package as a dependency then modify your React setup to import files outside of the /src directory.
In my case, it doesn't look like yarn link is doing anything other than adding a resolutions entry in package.json, which according to the documentation is only used to specify the version to resolve. Perhaps my understanding of Yarn link is wrong (even though it held up in v1).
To fix the issue I had to add the reference to dependencies in package.json (even though I'd already run yarn link):
"dependencies": {
"components": "portal:../components"
}
This caused the aforementioned You attempted to import components which falls outside of the project src/ directory error. To resolve this we either need to eject the React app and disable the ModuleScopePlugin in Webpack (therefore allowing the import of files outside the /src folder), or use craco with custom configuration. I've created yarn-eject and craco branches to demonstrate both.
It's not a particularly elegant solution, and I'm hoping someone can post a better alternative. I switched to Yarn 2 so that I could utilise the "Improved Peer Dependency Links" feature (so that I'm only relying on one version of react across my applications and shared components packages). I'd rather not have to eject my React app or use custom configuration if possible.

Related

React can't resolve .tsx files

I made a new react project using npx create-react-app app-name --template typescript and it ran fine until I imported another .tsx file. Now when I try to import another .tsx file it gives me an error: Module not found: Error: Can't resolve './components/component' in '/Users/benbaldwin/Desktop/project/frontend/teacher/src'
The only thing I'm doing differently than normal is the file structure. Since I have multiple react projects in the frontend folder that share dependancies I moved the node_modules to cover all of those subdirectories. my file structure looks like this:
- frontend
- node_modules
- project-1
- public
- src
index.tsx
react-app-env.d.ts
- components
component.tsx
- project-2
package.json
tsconfig.json
the component file looks like this:
import React from 'react';
const Component: React.FC = () => {
return <div>hello</div>;
};
export default Component;
and the index file looks like this:
import React from 'react';
import ReactDOM from 'react-dom';
import Component from './components/component';
import './index.css';
ReactDOM.render(
<React.StrictMode>
<Component />
</React.StrictMode>,
document.querySelector('#root')
);
do I need to eject create-react-app and write out my own web pack config or is there a better way to fix this?
the full source code is on my GitHub: https://github.com/baldwin-dev-co/mountaineer
I just came across this problem as well, with a brand new React project.
Temporarily downgrading react-scripts from version 5.0.0 to 4.0.3 solved it for me, because after invoking npm install and npm run start this added the missing tsconfig.json file - which was the actual cause of the problem.
After this I could safely go back to 5.0.0 and everything worked fine.
I've run into this issue as well upgrading a project to typescript, using react-scripts 5.0.1.
In an attempt to diagnose what was going on, since there are only a few hits on this searching the problem, I setup 2 new projects.
First, is the npx create-react-app myapp non-typescript-template project;
Let it run and install as is
Builds as expected
Run npm install --save typescript #types/node #types/react #types/react-dom #types/jest
Change a file to tsx, didn't matter which one, any change had the same result
No tsconfig.json generated (as spoken about in the documentation), build fails with errors Module not found: Error: Can't resolve './App' in 'E:\create-react-tsx\myappts\src'
Second, run npx create-react-app myappts --template typescript typescript-template project;
Let it run and install as is
Builds as expected
Observations;
Copying the generated tsconfig.json from the typescript-template project (myappts) to the non-typescript-template project (myapp) didn't resolve the issue. The errors relate to the svg import etc. Cannot find module './logo.svg' or its corresponding type declarations.
Additionally copying the typescript-template project (myappts) react-app-env.d.ts over to the non-typescript-template project (myapp) got the project to build.
As an alternative, downgrading to react-scripts#4 in the non-typescript-template project (myapp), then changing a file to tsx and run a build generates the two required files. A subsequent upgrade to react-scripts continues to generate successful builds.
There is a bug filed for this issue;
https://github.com/facebook/create-react-app/issues/10951
Further links;
What is the react-app-env.d.ts in a react typescript project for
https://create-react-app.dev/docs/adding-typescript/

How to use Apollo/Graphql hooks within external React.js component library

I have 3 React.js projects and I'm trying to consolidate the reused components across all 3 projects into one shared component library.
This component library is hosted on Github and imported into all 3 projects through the package.json file like this:
"components": "github:myusername/components"`
I am then able to import the components from the library into each project like such:
import { Form } from "components"
but when the component being imported (in this case a Form) uses an Apollo/Graphql hook (useQuery, or useMutation) I get this error:
Uncaught Invariant Violation: Could not find "client" in the context or passed in as an option. Wrap the root component in an <ApolloProvider>, or pass an ApolloClient instance in via options.
I've found countless questions about this same error, but the solutions are either already in place or irrelevant. Here is what I've found so far:
Wrap the App with the <ApolloProvider client={client}> tag (this is already there in the 3 projects - but not in the "components" library)
use the #apollo/client package instead of the #apollo/react-hooks - link (already using that package)
The solution that worked for me was to define #apollo/client as a peer dependency in the root package.json of my shared lirbary (I'm using lerna).
It didn't work for me to just link the package with yarn link but I had to publish it on npm.
I looked into this a little more given Kn3cht's information that the shared component works if published to NPM and found a beautiful solution. There is a utility called yalc that works perfectly. The problem with npm/yarn link is that symlinked dependencies are not deduped. To use, install yalc globally (eg, npm install yalc --global or yarn global add yalc).
Let's say the component to be shared is called shared-component.
In the root folder of shared_component, type 'yalc push'.
In the root folder of component that will import the shared_component, type 'yalc link shared-component'
That's it. I can't believe this utility is not more widely known. I believe you still need to designate react and apollo dependencies as peerDependencies in package.json.
As for Lerna, there is a small wiring needed to have updates automatically pushed whenever the shared-component changes. Essentially, install watch at the root of the mono repo as a dev dependency, then add scripts to the root and all component package.json files that you want to have automatically pushed. For example, the target I create is called "watch:push". In the root package.json:
"scripts": {
"watch:push": "lerna run watch:push"
}
For each component that I want to share I add this target to its package.json:
"scripts": {
"push": "yalc push",
"watch:push": "watch 'yarn push' ./dist",
}
Here you can manually push your shared-component via yarn push, or you can have the shared-component pushed whenever the contents of the build folder have changed (in my case the folder is dist).
So to make this fully automatic, you need to run run a command separately that executes a build whenever source code changes.

Using StorybookJS in a component library package with React as peer dependency

I am creating my own React component library. The package.json is using react and react-dom as peer dependencies because I only want to ship code for components only. This package will then be used in other React-based projects so I will provide React myself in those projects.
I've decided for adding StorybookJS in the package for two reasons
Help during development to preview my components
Create a static storybook site that I will host somewhere showcasing the components
My project structure:
package.json
src/
components/ // contains components
storybook/ // <-- not part of the package
index.ts // <-- this is entry point of the package
However this introduces a problem. Since StorybookJS requires react and react-dom to run, I would have to include them as dependencies of the library.
I will be using webpack to actually bundle only the component code so it can be distributed. Should I just use externals property in configuration to exclude react and react-dom from the bundle? I guess it would work but then the package.json would still list React as its dependencies (which I do not want).
What is the correct approach here?
I ended up putting react and react-dom into peerDependencies as that is correct approach in my opinion. Since that package file belongs to library itself (and not Storybook), it should correctly state its dependencies.
To avoid bundling React into my library bundle, I put it into externals webpack configuration like so:
externals: {
react: 'react',
'react-dom': 'react-dom',
},
This way, React or ReactDOM does not end up in the bundle. The library is React component library so it can't be used without React anyway and you should provide it.
The final step to make Storybook work, I added prepare script that installs peer dependencies when you install the package. That way React and ReactDOM are provided for Storybook application.
This is the prepare script in my package.json:
"scripts": {
"prepare": "install-peers && npm run build"
}
I used install-peers package to install React and ReactDOM. For newer version of NPM client, you will not need this package and running npm install should install peer dependencies as well.

jest: Cannot find module 'react', and other node_modules

I am trying to write some tests for a library I'm making. This is not a creat-react-app project, but something built from scratch with babel. I'm using jest, and I'm having this problem. I saw similar quesitons, but nothing quite like this. I have "test": "jest" in my package.json scripts. I have a __tests__ directory in my root folder. One of my test files, called Component.js, starts with some simple imports:
import React from 'react';
import { MyComponent } from '../src/MyComponent.js';
When I run npm run test, I get this error:
Cannot find module 'react' from '__tests__/MyComponent.js'
> 1 | import React from 'react';
Even if I comment out the react import, I get this error:
Cannot find module 'react' from 'src/MyComponent.js'
Require stack:
src/MyComponent.js
__tests__/MyComponent.js
> 1 | import React from 'react';
This is happening not just with react, but with any node modules imported in the test, or imported in any module that the test imports. I don't understand. Does jest not know to look for these modules in the node_modules folder? I do not have any jest.config.js or any jest property in my package.json - I didn't think I needed one to specify that jest should be looking for these modules in the node_moduldes folder. Do I need to set up some config for jest to look in the node_modules for these type of imports? I feel like there's a simple fix here that's alluding me. Thanks for reading.
Edit: Mocha giving me a similar issue
I thought I woul try and bypass this whole issue by using mocha instead. With a test script of "mocha": "mocha --require #babel/register '**/__tests__/*' -R spec", I get a similar error:
Error: Cannot find module 'react'
Require stack:
- /Users/seth/Documents/GitHub/react-esri-leaflet/__tests__/MyComponent.js
- /Users/seth/Documents/GitHub/react-esri-leaflet/node_modules/mocha/lib/esm-utils.js
- /Users/seth/Documents/GitHub/react-esri-leaflet/node_modules/mocha/lib/mocha.js
- /Users/seth/Documents/GitHub/react-esri-leaflet/node_modules/mocha/lib/cli/one-and-dones.js
- /Users/seth/Documents/GitHub/react-esri-leaflet/node_modules/mocha/lib/cli/options.js
- /Users/seth/Documents/GitHub/react-esri-leaflet/node_modules/mocha/bin/mocha
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:713:15)
What the heck is going on here? Shouldn't these testing libraries know to look in my rootFolder/node_modules for these things? Might something in my package.json or (now obsolete) webpack.config.js file be interfering? Rather than copy those files into this post, you can see them, with the whole project directory, at the repo here: https://github.com/slutske22/react-esri-leaflet
The more I thought about this the more I realized that this is a peerDependencies issue. The dependencies that jest couldn't find were all peerDependencies. If I were to run my package through travisci, it would run npm install and npm test, and it would never install these dependencies or be able to use them in the tests.
Thanks to this question, I copied all my peerDependencies into the devDependencies, reran npm install, and then the tests started working.
Rather than delete the question, I'll leave this here if anyone ever has this specific case happen to them.

Getting error when using FormattedMessage inside a module: Error: [React Intl] Could not find required `intl` object

I have a monorepo which exposes a TypeScript module, which is consumed & used by a React TypeScript project.
When the module inserts arbitrary React elements to the virtual DOM - everything works as expected, including when I try to use React Router (which was initially problematic but I was able to fix that).
However, when I try to use react-intl, via FormattedMessage, I get the error:
Error: [React Intl] Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry.
Which is especially annoying as I see this printed in the console logs:
The above error occurred in the <Context.Consumer> component:
in FormattedMessage
in h2
in div
in Loading (at App.tsx:11)
in IntlProvider (at App.tsx:8)
in App (at src/index.tsx:9)
in StrictMode (at src/index.tsx:8)
(note the IntlProvider wrapping Loading - which is the element that uses FormattedMessage which can't find IntlProvider).
I imagine this is somehow related to versioning, or having 2 instances of React / React DOM / IntlProvider, but I have no idea to how solve this, and I have spent quite a lot of time trying everything I could think of.
For what it's worth, here's what I use:
TypeScript - for both module and project
Webpack to pack the module, where I declared React, ReactDOM and react-intl as externals and added them as peerDependencies rather than direct dependencies
create-react-app for the project
I was able to create a minimal repro repository, here's how to repro my issue:
<cd somewhere>
git clone https://github.com/chakaz/repro-repo .
cd repro-lib
npm install
npm run build:dev
cd ../project
npm install
npm run start
Anyone has any idea? Tons of thanks in advance!
With your above way in order to make it work, you have to delete node_modules in your repro-lib dir cause it will install dependencies in both dirs.
So in order to resolve problem of monorepo, I'd like to suggest you use yarn's workspace functionality as described carefully here: https://classic.yarnpkg.com/en/docs/workspaces/
To summary, it's a great functionality to help working with multiple workspaces by just only yarn install once.
Here are a few steps to make your repo working:
Put package.json at the root level of the project with following content:
{
"private": true,
"workspaces": ["project", "repro-lib"]
}
Go to project dir and replace following line in package.json:
"pf-common": "file:../repro-lib"
to
"pf-common": "1.0.0"
Finally, just go back to root top level install deps again:
yarn install
That's it! Now you can re-run your application to see how it works.
NOTE: In terms of having interest in monorepo, lerna is also great tool comes to help by providing great CLI.

Resources