Comments (13)
Hi, great. I was busy, but now it should be easier to locate the issue.
from single-spa.
React is being loaded twice as one of the consumers matches the specific scope while the other one falls back to the global scope. Essentially, your import map is configured as: if request for these modules comes from localhost:3000, use this; otherwise, fall back to global scope.
This can create issues as react does not work correctly when there are multiple versions present on the same page. It also does not work correctly when the same version of react is loaded (consumed) multiple times.
If you remove the react and react-dom from imports, I'm getting an error
Unable to resolve bare specifier 'react' from https://cdn.jsdelivr.net/npm/@esm-bundle/[email protected]/system/react-dom.production.min.js
With the import-map without scopes all works perfec
By default react, and react-dom are externalized in webpack configurations (if created with create-single-spa). They will not be bundled and imports will be left as-is. When you remove them from import maps, browser encounters a line in the code such as "import ... from 'react'", and you get the error as it can not locate "react".
from single-spa.
@MilanKovacic, thank you for your responses! Could you please provide further clarification?
I currently have a root app using React 16, which I intend to load from node_modules
. Additionally, there are several single-spa apps using React 17, and I want them to load React from the import map.
If I understand correctly, to ensure proper functionality, both the root app and the single-spa apps should have the same version of React?
I've used a create-react-app and craco(to update webpack) for my single-spa apps.
from single-spa.
This setup could work correctly. In root-config, exclude react, and react-dom from externals so that they are bundled. In microfrontends, set react, and react-dom as externals, and set them in import maps (you do not need to scope them). Best practice is for root-config not to use any framework, and to share dependencies like react accross microfrontends.
from single-spa.
The root app doesn't have any externals, all issues that I described were without externals in the root ap.
you do not need to scope them
We have multiple single-spa apps owned by different teams, and I aimed to use scopes as a means for each team to update their dependencies without affecting other teams' work.
Best practice is for root-config not to use any framework, and to share dependencies like react accross microfrontends.
This is our goal. Root app is a monolith legacy, we're gradually working on separating it step by step. However, this process will take some time.
from single-spa.
The root app doesn't have any externals, all issues that I described were without externals in the root.
After I open the page with single-spa-react app it loads react twice
I am not sure what the issue is. This is an expected behavior as you have one react in global scope of the import one, and one scoped react. All requests originating from https://localhost:3000/ will utilize the scoped one, while all other consumers will utilize the global one, which could lead to multiple versions/instances being loaded.
If you remove the react and react-dom from imports, I'm getting an error
This is happening because you have react/react-dom externalized in one of the applications.
We have multiple single-spa apps owned by different teams, and I aimed to use scopes as a means for each team to update their dependencies without affecting other teams' work.
You can use scopes for this. This is an organizational decision — be careful of microfrontend anarchy.
from single-spa.
I am not sure what the issue is. This is an expected behavior as you have one react in global scope of the import one, and one scoped react. All requests originating from https://localhost:3000/ will utilize the scoped one, while all other consumers will utilize the global one, which could lead to multiple versions/instances being loaded.
But I have only one consumer. In my example, there is one single-spa app. The root app uses react from the node_modules
, so it should not consume a react from import-map. So with one single-spa react, that has external react
it loads scoped and global react.
This is happening because you have react/react-dom externalized in one of the applications.
But I have scoped react.
{
"imports": {
"single-spa": "https://cdn.jsdelivr.net/npm/[email protected]/lib/system/single-spa.min.js",
"@root-config": "{{MFE_JS_URL}}"
},
"scopes": {
"https://localhost:3000/": {
"react": "https://cdn.jsdelivr.net/npm/@esm-bundle/[email protected]/system/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/@esm-bundle/[email protected]/system/react-dom.production.min.js"
}
}
}
With this import-map I'm getting the error
Unable to resolve bare specifier 'react' from https://cdn.jsdelivr.net/npm/@esm-bundle/[email protected]/system/react-dom.production.min.js
Same conditions, one consumer and root that uses node_modules react.
from single-spa.
Please post full root-config index file, and webpack/craco configurations.
from single-spa.
Webpack config for single-spa app(i've removed jest configuration)
const singleSpaApplicationPlugin = require('craco-plugin-single-spa-application');
const orgName = 'org';
const projectName = 'singleSpa';
module.exports = {
webpack: {
plugins: {
remove: ['ManifestPlugin'],
},
configure: {
externals: ['react', 'react-dom']
}
},
plugins: [
{
plugin: {
...singleSpaApplicationPlugin,
overrideCracoConfig: ({ cracoConfig, pluginOptions }) => {
const cracoConfigOverride =
singleSpaApplicationPlugin.overrideCracoConfig({
cracoConfig,
pluginOptions,
});
// Don't remove HtmlWebpackPlugin for ephemerals to work
cracoConfigOverride.webpack.plugins.remove =
cracoConfigOverride.webpack.plugins.remove.filter(
(el) => el !== 'HtmlWebpackPlugin',
);
return cracoConfigOverride;
},
},
options: {
orgName,
projectName,
entry: `src/${orgName}-${projectName}`,
orgPackagesAsExternal: false,
reactPackagesAsExternal: false,
minimize: process.env.NODE_ENV === 'production',
rootDirectoryLevel: 1,
},
},
],
};
Root config file. We use it as a separate entry in the webpack
entry: {
reactapp: [path.join(__dirname, 'index.jsx')],
mfe: [path.join(__dirname, 'root-config.js')],
},
There is moreregisterApplication
, but I'm testing this one so I believe others are irrelevant.
import { registerApplication, start, addErrorHandler } from 'single-spa';
window.addEventListener('single-spa:app-change', event => {
const { appsByNewStatus } = event.detail;
// As core styles were overwriting our styles, this is the solution we came up with:
// Looking for core styles index
const coreStylesIndex = [...document.styleSheets].findIndex(
styleSheet =>
styleSheet.href && styleSheet.href.includes('/css/app.css'),
);
// If it is micro FE that does not need core styles, then set it to true
if (coreStylesIndex >= 0) {
const isDisabled = appsByNewStatus.MOUNTED.some(mountedApp =>
DISABLED_CORE_STYLES_APPS.includes(mountedApp),
);
document.styleSheets[coreStylesIndex].disabled = isDisabled;
}
});
window.addEventListener('single-spa:app-change', event => {
const { newAppStatuses } = event.detail;
console.log('Some applications were mounted/unmounted: ');
console.log(newAppStatuses);
});
window.addEventListener('single-spa:routing-event', event => {
const { newAppStatuses } = event.detail;
console.log(newAppStatuses); // { app1: MOUNTED, app2: NOT_MOUNTED }
});
registerApplication({
name: WEB_APP_NAME,
app: async () => {
if (process.env.NODE_ENV === 'production') {
await System.import(`${WEB_APP_NAME}/config`);
}
return System.import(WEB_APP_NAME);
},
activeWhen: location => {
const { pathname } = location;
return pathname.startsWith(`${prefix}/singleSpa`);
},
customProps: () => {
const context = document.getElementById(
`single-spa-application:${WEB_APP_NAME}`,
)?.dataset;
return {
isEphemeralEnv: formatBool(context?.isEphemeralEnv),
allFeatureFlags: { ...allFeatureFlags },
};
},
});
localStorage.setItem('devtools', 'false');
addErrorHandler(err => {
System.delete(System.resolve(err.appOrParcelName));
});
start();
from single-spa.
Please add the index.html file.
from single-spa.
I've removed some sensitive data
{% load i18n %}
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.5.6/webfont.js"></script>
<script src={{JARIS_CDN_URL}}></script>
<script>
WebFont.load({
google: {
families: ['Montserrat']
},
typekit: {
id: 'pmn7pbb'
}
});
</script>
{% block css %}
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
{% endblock %}
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="application-name" content="HotSpot" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="format-detection" content="telephone=no" />
<meta name="format-detection" content="email=no" />
<meta name="msapplication-config" content="images/favicons/browserconfig.xml">
<meta name="theme-color" content="#f9f9f9">
<meta id="show-tour-intercom" data-value="{{ SHOW_TOUR }}">
<meta id="base-url-intercom" data-value="{{ BASE_URL}}">
<meta id="intercom-tour-id" data-value="{{ INTERCOM_TOUR_ID }}">
{% comment %}
***************************************************
Setup our MicroFE's!!!
***************************************************
{% endcomment %}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/runtime.min.js"></script>
<meta name="importmap-type" content="systemjs-importmap" />
<script type="systemjs-importmap">
{
"imports": {
"single-spa": "https://cdn.jsdelivr.net/npm/[email protected]/lib/system/single-spa.min.js",
"react-router-dom": "https://cdn.jsdelivr.net/npm/[email protected]/umd/react-router-dom.min.js",
"@root-config": "{{MFE_JS_URL}}"
},
"scopes": {
"https://localhost:3000/": {
"react": "https://cdn.jsdelivr.net/npm/@esm-bundle/[email protected]/system/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/@esm-bundle/[email protected]/system/react-dom.production.min.js"
}
}
}
</script>
<script type="systemjs-importmap" src="{{IMPORT_MAP_URL}}"></script>
<link rel="preload" href="https://cdn.jsdelivr.net/npm/[email protected]/lib/system/single-spa.min.js" as="script" crossOrigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/import-map-overrides.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/system.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/extras/amd.min.js"></script>
<script type="application/javascript">
window.apiUrl = '{{ API_URL }}';
</script>
{% if should_mfe_wait_for_dom %}
<script>
window.addEventListener('DOMContentLoaded', function(){
System.import('@root-config');
});
</script>
{% else %}
<script>
System.import('@root-config');
</script>
{% endif %}
</head>
<body>
<import-map-overrides-full show-when-local-storage="devtools" dev-libs></import-map-overrides-full>
<script async src="//www.google-analytics.com/analytics.js"></script>
<script src="{{ APP_JS_URL }}"></script>
<script src="{{ REACTAPP_JS_URL }}" data-name="react-app" type="application/javascript"></script>
</body>
</html>
from single-spa.
@MilanKovacic Hi! I managed to reproduce it with the default create-single-spa
projects.
Here is the repository
https://github.com/ValeraKorovelkov/single-spa-shared-react-bug
from single-spa.
Hi, did you manage to solve the issue?
from single-spa.
Related Issues (20)
- add unload lifecycle to parcel HOT 1
- nodeLoader is undefined - global.nodeLoader.setImportMapPromise(Promise.resolve(nodeImportMap)) HOT 1
- Can not switch App when useing navigateToUrl, can anyone help me?Please.
- Single-spa patches $nuxt
- V6 Update HOT 1
- Change all `new Error` to a specific error like `new SingleSpaError`
- The call to the assets of microfrontend fails because its a different port HOT 2
- If an action tries to unmount a parcel while its already unmounting, the parent application gets set to SKIP_BECAUSE_BROKEN HOT 3
- Drop IE11 support
- Question: Is it possible to consume via single-spa webpack5 based federated micro frontends and vice versa?
- TypeError in Safari: createElementVNode is not a function HOT 5
- Questions about routing HOT 4
- Is this a solution for Vite ESM? HOT 4
- Rework navigation cancelation
- How to merge large application with single spa HOT 1
- Single-spa is always appending index.html to the URL HOT 4
- react query cache stay untouched after switching application HOT 8
- PROPOSAL: single-spa CLI HOT 5
- Adding styleguide to Nuxt app with single-spa
- PROPOSAL: Add the posibility of recycle micro-apps styles, after those are unmounted HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from single-spa.