SSR with Lerna + React + Styled components - reactjs

I trying to build project using Lerna and its hoist feature. Everything worked great, before i decided to move ui components from site to packages, for reuse purposes.
I have file structure like this
root
├── node_modules
└── packages
├── admin
├── site
└── ui
where ui is react + styled-components components library, and it used as dependency in site
// site packages.json
...,
"dependencies": {
...,
"#root/ui": "^1.0.0"
}
ui have only one file index.tsx
import React, { FC } from "react";
import styled from "styled-components";
const SharedComponent: FC = () => {
return (
<Wrapper>Hello from SharedComponent</Wrapper>
);
};
const Wrapper= styled.div`
color: red;
`;
export default SharedComponent;
then i use tsc to compile tsx
trying to use it inside site
import React, { FC } from 'react';
import SharedComponentfrom '#root/ui/dist';
const App: FC = () => {
return (
<>
<SharedComponent />
<div>Hello from Site App</div>
</>
);
};
export default App;
So if i start devServer everything is OK.
But when i try to SSR, i got server side error
UnhandledPromiseRejectionWarning: Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
i tried to declare alias property in wepback settings as mentioned here https://github.com/facebook/react/issues/14257#issuecomment-508808246
// site webpack.config.js
module.exports = {
...,
resolve: {
...,
alias: {
react: path.resolve('../../node_modules/react') // path to root node_modules
}
},
}
but error is still in present
Also, if i remove styled-components from ui it works fine. It's seems like the problem is somewhere in styled-components
So, how can i pair lerna + styled-components + react + SSR ?

I managed to get it work. All i need to do is set externals field in webpack config. like this:
externals: [
nodeExternals(),
nodeExternals({ additionalModuleDirs: [path.resolve(__dirname, '../../node_modules')] }),
'express'
]

Try to use dangerouseInnerHtml to solve css probleum. otherwise if you use emtion.css then you can try #emotion/server
try to use next/dynamic or React.lazy to load dynamic
I still confused duplicate React. how to resolve this.

Related

How to React Typescript Functional Component Library?

I hope someone can tell me where I am going wrong in trying to get usable libraries
I have created a NPM project using create-react-app --format typescript, I then created the following structure:
|->tsconfig.json
|->package.json
|->config
|->tsconfig.base.json
|->tsconfig.cjs.json
|->tsconfig.esm.json
|->src
|->index.tsx
|->components
|->index.ts
|->ExampleComponent
|->ExampleComponent.component.tsx
|->ExampleComponent.stories.tsx
|->ExampleComponent.types.ts
|->index.ts
In this example the Example Component looks something like the following:
|->tscon
import React, { FC } from 'react';
// Contact specific icons
import Container from 'react-bootstrap/Container';
// Footer Properties
import { ExampleProperties } from './ExampleComponent.types';
export const ExampleComponent: FC<ExampleProperties> = ({ text }) => {
return (<Container fluid>{text}</Container>);
};
for the tsconfig files in 'config' I've lifted the Synk recommendation directly, while tsconfig.json is fairly simple like so:
{
"extends": "./configs/tsconfig.esm.json",
}
If I start the application via 'npm start' I get a website and the component correctly appears, but the issue is trying to import into another project.
I using npm link and npm link #Example/ExampleProject to bring the project in to another website and the index.tsx of that project looks like so:
import React from 'react';
import { createRoot } from 'react-dom/client';
import { ExampleComponent } from '#Example/ExampleProject';
const container = document.getElementById('root');
if (!container) throw new Error('Failed to find the root element') const root = createRoot(container);
root.render(
<React.StrictMode>
<main role={"main"} >
<ExampleComponent/>
</main>
</React.StrictMode> );
But when it starts I am getting this error:
Module not found: Error: Can't resolve './ExampleComponent/index' in '/home/user/IdeaProjects/ExampleProject/dist/esm' Did you mean 'index.mjs'? BREAKING CHANGE: The request './Common/index' failed to resolve only because it was resolved as fully specified (probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"'). The extension in the request is mandatory for it to be fully specified. Add the extension to the request.
The only thing I can think is tsc generates src/components/index.ts as a /src/component/index.js (which I rename). But ExampleComponent has .js and .mjs files within its directory

"Module not found" or "Import path cannot end with tsx" using Storybook with TypeScript

I am writing a component that makes use of another component, so I've written
import { Text } from "../Text/Text"
in the file /src/stories/TextArea/TextArea.tsx.
However, this gives the error
Module not found: Can't resolve '../Text/Text' in '/Users/username/project/src/stories/TextArea'
Changing the import statement to
import { Text } from "../Text/Text.tsx"
makes it work just fine. Instead, the linter complains:
An import path cannot end with a '.tsx' extension. Consider importing '../Text/Text.js' instead.ts(2691)
As I understand it, .tsx endings are forbidden in TypeScript so reconfiguring the linter doesn't seem to be the best option.
Obviously, importing Text.js instead doesn't work as it doesn't exist. Storybook is supposed to work out of the box with TypeScript, so I'm unsure of what I have to do.
In the .mdx files I am using as stories (like Text.stories.mdx), imports including .tsx are accepted without linter complaints. Removing the extension produces a similar Module not found error.
The project was created with create-react-app and is running Storybook 6.5.15.
your import import { Text } from "../Text/Text" should work fine.
Make sure you are using the latest version of Storybook - 6.5.15.
...
I tried to reproduce your issue and failed to do so. I did not get the same error and the import worked just fine. Let me describe what steps I took:
I installed Storybook in my project using npx storybook init --type react. This installed Storybook 6.5.15 for React.
I created a simple component in ./project/src/Button.tsx
import React from 'react'
export const Button = () => {
return <div>MY BUTTON</div>
}
I created a simple story in ./project/stories/Button.stories.jsx like this:
import React from 'react';
import { Button } from '../src/Button';
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
title: 'Example/Button',
component: Button,
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
argTypes: {
backgroundColor: { control: 'color' },
},
};
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Primary.args = {
primary: true,
label: 'Button',
};
And there are no issues. The import works as it should. If this does not help you, tell us more about your project setup (e.g. are you using Create-react-app), etc. So this issue is easier to reproduce.

Re-exports not properly bundled with Webpack 5

When bundling my web-application, I have realised that re-exports of (some) modules do not work as expected. I have tried multiple optimization settings but so far with no success.
Setup
So basically I have the following setup
config/
webpack.config.ts
package.json
frontend/
apps/
app1/
src/
index.tsx
App.tsx
...
packages/
test/
index.ts
testFunc.ts
test1Func.ts
Test.tsx
Test1.tsx
So I run webpack from config with the entry point frontend/apps/app1/index.tsx which imports App.tsx (standard React application).
This all works fine but I have realised that when produce a production build from app1, unused exports in my App.tsx appear in the bundle. To clarify
import { testFunc } from 'packages/test' // <- `packages` is an alias
const App: React:FC = () => {
const t = testFunc();
return <>Hello World!</>;
}
will include Test.tsx, Test1.tsx and 'test1Func.ts' in the bundle. My index.ts in test/ looks like
export { testFunc } from './testFunc';
export { test1Func } from './test1Func';
export { Test } from './Test';
export { Test1 } from './Test1';
I should mention that testFunc1.ts contains a useEffect hook because I found that as soon as I have react related code, there is no tree shaking for the source anymore. So
// test1Func.ts
export const test1Func = () => {
useEffect(() => {
// do nothing
}, []);
return "Test 1";
}
However, if I import my files directly, eg. import { testFunc } from 'packages/test/testFunc', everything works as expected and only test appears in the bundle. This also applies to the other test components:
App import tests
testFunc
import via index.ts & use in App.tsx => bundles all files inside test/ ❌
import via import { testFunc } from 'packages/test/testFunc' & use in App.tsx => only testFunc.ts is included in the bundle ✅
import via import { testFunc } from 'packages/test/testFunc' & don't use in App.tsx => nothing gets included in the bundle ✅
testFunc1 | Test | Test1 <- they all behave the same
import via index.ts & use in App.tsx => bundles all but testFunc ❌
import via import { test1Func } from 'packages/test/test1Func' & use in App.tsx => only test1Func.ts is included in the bundle ✅
import via import { test1Func } from 'packages/test/testFunc' & don't use in App.tsx => nothing gets included in the bundle ✅
I guess this is just a configuration error, although I have already tried multiple different optimisation settings. The closest probably is the sideEffects option but so far, this also did not have any effect.
Any ideas?
Thank you very much!
I found the solution! 🙌
As guessed, I had Webpack's optimization.sideEffects property misconfigured. sideEffects is necessary for proper tree shaking.
However, in order to get this work properly there are 2 destinations where you have to set the sideEffects property:
your package.json (in my case in every package I use in my monorepo)
and in the webpack.config.ts
Solution
The admittedly very confusing thing is that you will have to set
sideEffects: false in the package.json
and sideEffects: true in the optimization section in your webpack.config.ts
Here's a bit more if you are interested.

How do I share context between a library component, and my application component?

I'm using lerna to create a monorepo where I'd have a structure like this:
root
packages
application - Our root application
components - Just some react components, that are to be used by the application
Here is a working Github with a simple example for this.
The issue I've run into, is I'm using Material-UI and its theming functionality, where at the application root we'll have a ThemeProvider:
import { ThemeProvider } from '#material-ui/styles';
//...
function App() {
return (
<ThemeProvider theme={theme}>
<MyMaterialComponent/>
</ThemeProvider>
);
}
And later in the library component, we consume the theme, in our case using the makeStyles hook.
import React from 'react';
import { makeStyles } from '#material-ui/styles';
import Card from "#material-ui/core/Card";
const useStyles = makeStyles(theme => {
console.log(theme); //When this component doesn't have access to the theme, this is `{}`
return {
root: {
//color: theme.palette.primary.main //will error
}
}
});
export function MyMaterialComponent({ }) {
const classes = useStyles();
return (<Card>
<span className={classes.root}>This is some component</span>
</Card>
);
}
Now this seems all pretty straight forward. When we run this code all within the same package, it works fine. That styles function has access to the theme.
But when I'm running from another package (our application package), the the component library no longer has access to the theme (the theme is just an empty object).
The only way I currently know how to solve this, is the same way I've solved a similar hooks issue, which is to setup a webpack alias configuration in my application, to direct the component library to share the same same node module. (See this Github thread and the suggested solution).
ie. using react-app-rewired and customize-cra I have a config-overrides.js that looks like this:
const {
override,
addWebpackAlias,
} = require("customize-cra");
const path = require('path');
module.exports = override(
addWebpackAlias({
react: path.resolve('./node_modules/react'),
//comment out the line below to reproduce the issue
"#material-ui/styles": path.resolve("./node_modules/#material-ui/styles")
})
)
or you could be managing your webpack manually to do a similar thing.
So this works fine, but this isn't a particularly satisfying solution.
Especially for a library like Material-UI, you want users to be able to use your component library without having them to mess with their webpack configuration.
So I figure I must be doing something wrong here - can you tell me what?
You can achieve that by placing your #material-ui/core dependency from devDependencies to peerDependencies in your library's project. In my case, that solved my problem.
More information on peerDependencies :
https://classic.yarnpkg.com/en/docs/dependency-types/#toc-peerdependencies

TypeError: styleSheet.getName is not a function when importing components from npm based react libs using styled-components

Context
I'm writing a react component library based on Styled Components in NPM to be shared across my downstream React projects.
All of my projects are isomorphic React apps. They implement Styled components server-side rendering (SSR) as outlined in the official documentation as such:
import { ServerStyleSheet } from 'styled-components';
const sheet = new ServerStyleSheet();
const html = ReactDOMServer.renderToString(sheet.collectStyles(App));
const css = sheet.getStyleElement().map((el, idx) => (el ? React.cloneElement(el, { key: idx }) : el));
const page = <Html content={html} state={apolloState} assetMap={assetMap} css={css} helmet={helmet} />;
res.send(`<!doctype html>\n${ReactDOMServer.renderToStaticMarkup(page)}`);
res.end();
Problem
When I import a component from my aforementioned react component library from npm and call it in my project, I get the following error:
TypeError: styleSheet.getName is not a function
at ComponentStyle.generateAndInjectStyles (/project/node_modules/my-react-component-library/node_modules/styled-components/lib/models/ComponentStyle.js:95:37)
at StyledComponent.generateAndInjectStyles (/project/node_modules/my-react-component-library/node_modules/styled-components/lib/models/StyledComponent.js:154:40)
at StyledComponent.componentWillMount (/project/node_modules/my-react-component-library/node_modules/styled-components/lib/models/StyledComponent.js:192:40)
...
Questions
Is this problem coming from my npm library not enabling SSR or the end-user implementation of the styled components ssr?
How can this be solved?
I solved this by exporting the stylesheet via webpack's ExtractTextPlugin in the upstream npm react library and calling it from my downstream app.

Resources