I have the following architecture:
main-frontend
foo-frontend
bar-frontend
baz-frontend
main-frontend
, foo-frontend
, bar-frontend
and baz-frontend
are all standard React apps generated using create-react-app
.
All of them except main-frontend
publish their UI as a React component, wrapped in a Web component. These Web components can be loaded into the main-frontend
to weave all 3 sub-frontends into one super-frontend.
The problem I am facing is that in order for this approach to work, all 4 frontends have to externalize react
, react-dom
, react-router
, and any CSS-in-JS frameworks being used.
I am able to modify Webpack configuration for create-react-app
generated React apps, using the rewire
package and a couple of scripts. I am reproducing these below for others facing a similar problem.
In each of foo-frontend
, bar-frontend
, and baz-frontend
,
package.json
"dependencies": {
...
"rewire": "^6.0.0"
...
},
"scripts": {
"start": "node start.js",
"build": "node build.js",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"webpackConfig": {
"externals": {
"react": "react",
"react-dom": "react-dom",
"@emotion/react": "@emotion/react",
"@emotion/styled": "@emotion/styled"
}
}
build.js
const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/build.js');
const {
webpackConfig: {
externals
}
} = require('./package.json');
const config = defaults.__get__('config');
config.externals = externals;
start.js
const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/start.js');
const webpackConfig = require('react-scripts/config/webpack.config');
const {
webpackConfig: {
externals
}
} = require('./package.json');
defaults.__set__('configFactory', (webpackEnv) => {
const config = webpackConfig(webpackEnv);
config.externals = externals;
return config;
});
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
...
<title>Atlas Reviews</title>
<script src="https://cdn.jsdelivr.net/npm/react@18.1.0/umd/react.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18.1.0/umd/react-dom.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@emotion/react@11.9.0/dist/emotion-react.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@emotion/styled@11.8.1/dist/emotion-styled.umd.min.js"></script>
</head>
<body>
...
</body>
</html>
The problem I am getting is that @emotion/react
and @emotion/styled
aren't being externalized correctly.
Uncaught ReferenceError: emotion is not defined
at Object.@emotion/react (external var "emotion":1:1)
at Object.options.factory (react refresh:6:1)
at __webpack_require__ (bootstrap:24:1)
at fn (hot module replacement:62:1)
at Module../src/components/App/styles.tsx (index.tsx:25:1)
at Module.options.factory (react refresh:6:1)
at __webpack_require__ (bootstrap:24:1)
at fn (hot module replacement:62:1)
at Module../src/components/App/index.tsx (logo.svg:34:1)
at Module.options.factory (react refresh:6:1)
How do I fix this?
I managed to solve this, not with @emotion/react
and @emotion/styled
, but with styled-components
.
To be fair, that was my initial preference for a CSS-in-JS framework.
In my previous attempts, I had been using the wrong version of the styled-components
library.
Here's how I managed to do it:
package.json in all 4 React apps should be modified like so.
{
...
"dependencies": {
...
"@types/styled-components": "^5.1.25",
"rewire": "^6.0.0",
"styled-components": "^5.3.5",
...
},
"scripts": {
"start": "PORT=3004 node start.js",
"build": "PORT=3004 node build.js",
"test": "PORT=3004 react-scripts test",
"eject": "PORT=3004 react-scripts eject"
},
"webpackConfig": {
"externals": {
"react": "React",
"react-dom": "ReactDOM",
"react-is": "ReactIS",
"styled-components": "styled"
}
},
...
}
public/index.html in the 3 sub-frontends should be modified like so:
<!DOCTYPE html>
<html lang="en">
<head>
...
<title>Atlas Reviews</title>
<script src="https://cdn.jsdelivr.net/npm/react@18.1.0/umd/react.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18.1.0/umd/react-dom.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-is@18.1.0/umd/react-is.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/styled-components@5.3.5/dist/styled-components.min.js"></script>
...
</head>
<body>
...
</body>
</html>
The public/index.html
for the super-frontend will be similar, but will also include script
tags to import the sub-frontends:
<!DOCTYPE html>
<html lang="en">
<head>
...
<title>Atlas Reviews</title>
<script src="https://cdn.jsdelivr.net/npm/react@18.1.0/umd/react.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18.1.0/umd/react-dom.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-is@18.1.0/umd/react-is.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/styled-components@5.3.5/dist/styled-components.min.js"></script>
<script src="http://localhost:3001/static/js/bundle.js"></script>
<script src="http://localhost:3002/static/js/bundle.js"></script>
<script src="http://localhost:3003/static/js/bundle.js"></script>
...
</head>
<body>
...
</body>
</html>
TL;DR;: Note the externals for react-is
and styled-components
and the version of the styled-components
script being loaded via CDN.