Application died in status LOADING_SOURCE_CODE: You need to export the functional lifecycles in xxx entry
This error thrown as qiankun could not find the exported lifecycle method from your entry js.
To solve the exception, try the following steps:
check you have exported the specified lifecycles, see the doc
check you have set the specified configuration with your bundler, see the doc
Check the webpack of micro app whether it configured with output.globalObject
or not, be sure its value was window
if it had, or remove it to use default value.
Check your package.json
name field is unique between sub apps.
Check if the entry js in the sub-app's entry HTML is the last script to load. If not, move the order to make it be the last, or manually mark the entry js as entry
in the HTML, such as:
<script src="/antd.js"></script><script src="/appEntry.js" entry></script><script src="https://www.google.com/analytics.js"></script>
If the development environment is OK but the production environment is not, check whether the index.html
and entry js
of the micro app are returned normally, for example, 404.html
is returned.
If you're using webpack5 and not using module federation, please see here
If you are using webpack5 and using module federation, you need to expose the life cycle function in the index file, and then expose the life cycle function externally in the bootstrap file.
const promise = import("index");export const bootstrap = () => promise.then(m => m.boostrap());export const mount = () => promise.then(m => m.mount());export const unmount = () => promise.then(m => m.unmount());
Check whether the main app and micro-app use AMD or CommonJS. Check method: run the main app and the micro-app independently, and enter the following code in the console: (typeof exports === 'object' && typeof module === 'object') || (typeof define === 'function' && define.amd) || typeof exports === 'object'
,If it returns true
,that it is caused by this reason, and there are mainly the following two solutions:
libraryTarget
of the micro-app webpack
to 'window'
.const packageName = require('./package.json').name;module.exports = {output: {library: `${packageName}-[name]`,- libraryTarget: 'umd',+ libraryTarget: 'window',jsonpFunction: `webpackJsonp_${packageName}`,},};
umd
, directly mount the life cycle function to the window
in the entry file, refer toMicro app built without webpack.If it still not works after the steps above, this is usually due to browser compatibility issues. Try to set the webpack output.library
of the broken sub app the same with your main app registration for your app, such as:
Such as here is the main configuration:
// main appregisterMicroApps([{name: 'brokenSubApp',entry: '//localhost:7100',container: '#yourContainer',activeRule: '/react',},]);
Set the output.library
the same with main app registration:
module.exports = {output: {// Keep the same with the registration in main applibrary: 'brokenSubApp',libraryTarget: 'umd',jsonpFunction: `webpackJsonp_${packageName}`,},};
Application died in status NOT_MOUNTED: Target container with #container not existed after xxx mounted!
This error thrown as the container DOM does not exist after the micro app is loaded. The possible reasons are:
The root id of the micro app conflicts with other DOM, and the solution is to modify the search range of the root id.
vue
micro app:
function render(props = {}) {const { container } = props;instance = new Vue({router,store,render: (h) => h(App),}).$mount(container ? container.querySelector('#app') : '#app');}export async function mount(props) {render(props);}
react
micro app:
function render(props) {const { container } = props;ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));}export async function mount(props) {render(props);}export async function unmount(props) {const { container } = props;ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));}
Some js of micro app use document.write
, such as AMAP 1.x version, Tencent Map 2.x version.
If it is caused by the map js, see if the upgrade can be resolved, for example, upgrade the AMAP map to version 2.x.
If the upgrade cannot be resolved, it is recommended to put the map on the main app to load. The micro app also introduces this map js (used in run independently), but add the ignore
attribute to the <script>
tag:
<script src="https://map.qq.com/api/gljs?v=1.exp" ignore></script>
In other cases, please do not use document.write
.
Application died in status NOT_MOUNTED: Target container with #container not existed while xxx mounting!
This error usually occurs when the main app is Vue, and the container is written on a routing page and uses the routing transition effect. Some special transition effect caused the container not to exist in the mounting process of the micro app. The solution is to use other transition effects, or remove the routing transition.
Application died in status NOT_MOUNTED: Target container with #container not existed while xxx loading!
Similar to the above error, This error thrown as the container DOM does not exist when the micro app is loaded. Generally, it is caused by incorrect calling timing of the start
function, just adjust the calling timing of the start
function.
How to determine the completion of the container DOM loading? The vue app can be called during the mounted
life cycle, and the react app can be called during the componentDidMount
life cycle.
If it still reports an error, check whether the container DOM is placed on a routing page of the main app, please refer to [How to load micro apps on a routing page of the main app](#How to load micro apps on a routing page of the main app)
[import-html-entry]: error occurs while excuting xxx script http://xxx.xxx.xxx/x.js
The first line is just an auxiliary information printed by qiankun through console.error, which is used to help users quickly know which js has an error, not a real exception. The real exception information is on the second line.
For example, such an error indicates that when qiankun was executing the http://localhost:9100/index.bundle.js of the sub application, this js itself threw an exception. The specific exception information is Uncaught TypeError: Cannot read property 'call' of undefined.
Sub-application exceptions can be attempted to be resolved through the following steps:
Check the error js for syntax errors according to the specific exception information, such as missing semicolons, dependence on uninitialized variables, etc. Whether it depends on global variables provided by the main application, but the main application is not actually initialized. Compatibility issues. The js itself of the sub-application has syntax compatibility issues in the current runtime environment.
It must be ensured that the routing page of the main app is also loaded when the micro app is loaded.
vue
+ vue-router
main app:
*
to path
, Note: If this route has other sub-routes, you need to register another route, just use this component.const routes = [{path: '/portal/*',name: 'portal',component: () => import('../views/Portal.vue'),},];
activeRule
of the micro app needs to include the route path
of the main app.registerMicroApps([{name: 'app1',entry: 'http://localhost:8080',container: '#container',activeRule: '/portal/app1',},]);
start
function in the mounted
cycle of the Portal.vue
component, be careful not to call it repeatedly.import { start } from 'qiankun';export default {mounted() {if (!window.qiankunStarted) {window.qiankunStarted = true;start();}},};
react
+ react-router
main app:only need to make the activeRule of the sub app include the route of the main app.
angular
+ angular-router
main app,similar to the Vue app:
The main app registers a wildcard sub route for this route, and the content is empty.
const routes: Routes = [{path: 'portal',component: PortalComponent,children: [{ path: '**', component: EmptyComponent }],},];
The activeRule
of the micro app needs to include the route path
of the main app.
registerMicroApps([{name: 'app1',entry: 'http://localhost:8080',container: '#container',activeRule: '/portal/app1',},]);
Call the start
function in the ngAfterViewInit
cycle of this routing component, be careful not to call it repeatedly.
import { start } from 'qiankun';export class PortalComponent implements AfterViewInit {ngAfterViewInit(): void {if (!window.qiankunStarted) {window.qiankunStarted = true;start();}}}
Uncaught TypeError: Cannot redefine property: $router
If you pass { sandbox: true }
to start()
function, qiankun
will use Proxy
to isolate global window
object for sub applications. When you access window.Vue
in sub application's code,it will check whether the Vue
property in the proxyed window
object. If the property does not exist, it will look it up in the global window
object and return it.
There are three lines code in the vue-router
as followed, and it will access window.Vue
once the vue-router
module is loaded. And the window.Vue
in following code is your master application's Vue
.
if (inBrowser && window.Vue) {window.Vue.use(VueRouter);}
To solve the error, choose one of the options listed below:
Vue
library, instead of CDN or external moduleVue
to other name in master application, eg: window.Vue2 = window.Vue; delete window.Vue
Two way to solve that:
qiankun will inject a live public path variable before your sub app bootstrap, what you need is to add this code at the top of your sub app entry js:
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
For more details, check the webpack doc.
You need to set your publicPath configuration to an absolute url, and in development with webpack it might be:
{output: {publicPath: `//localhost:${port}`;}}
The reason is that qiankun
changed the external link style to the inline style, but the loading path of the font file and background image is a relative path.
Once the css
file is packaged, you cannot modify the path of the font file and background image by dynamically modifying the publicPath
.
There are mainly the following solutions:
Upload all static resources such as pictures to cdn
, and directly reference the address of cdn
in css
(recommended)
Use the url-loader
of webpack
to package font files and images as base64
(suitable for projects with small font files and images)(recommended)
module.exports = {module: {rules: [{test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf)$/i,use: [{loader: 'url-loader',options: {},},],},],},};
vue-cli3
project:
module.exports = {chainWebpack: (config) => {config.module.rule('fonts').use('url-loader').loader('url-loader').options({}).end();config.module.rule('images').use('url-loader').loader('url-loader').options({}).end();},};
vue-cli5
project, use the asset/inline
of webpack
replace url-loader
:
module.exports = {chainWebpack: (config) => {config.module.rule('fonts').type('asset/inline').set('generator', {});config.module.rule('images').type('asset/inline').set('generator', {});},};
file-loader
of webpack
to inject the full path when packaging it (suitable for projects with large font files and images)const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;module.exports = {module: {rules: [{test: /\.(png|jpe?g|gif|webp)$/i,use: [{loader: 'file-loader',options: {name: 'img/[name].[hash:8].[ext]',publicPath,},},],},{test: /\.(woff2?|eot|ttf|otf)$/i,use: [{loader: 'file-loader',options: {name: 'fonts/[name].[hash:8].[ext]',publicPath,},},],},],},};
vue-cli3
project:
const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;module.exports = {chainWebpack: (config) => {const fontRule = config.module.rule('fonts');fontRule.uses.clear();fontRule.use('file-loader').loader('file-loader').options({name: 'fonts/[name].[hash:8].[ext]',publicPath,}).end();const imgRule = config.module.rule('images');imgRule.uses.clear();imgRule.use('file-loader').loader('file-loader').options({name: 'img/[name].[hash:8].[ext]',publicPath,}).end();},};
base64
, and inject path prefixes for large filesconst publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;module.exports = {module: {rules: [{test: /\.(png|jpe?g|gif|webp)$/i,use: [{loader: 'url-loader',options: {},fallback: {loader: 'file-loader',options: {name: 'img/[name].[hash:8].[ext]',publicPath,},},},],},{test: /\.(woff2?|eot|ttf|otf)$/i,use: [{loader: 'url-loader',options: {},fallback: {loader: 'file-loader',options: {name: 'fonts/[name].[hash:8].[ext]',publicPath,},},},],},],},};
vue-cli3
project:
const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;module.exports = {chainWebpack: (config) => {config.module.rule('fonts').use('url-loader').loader('url-loader').options({limit: 4096, // Less than 4kb will be packaged as base64fallback: {loader: 'file-loader',options: {name: 'fonts/[name].[hash:8].[ext]',publicPath,},},}).end();config.module.rule('images').use('url-loader').loader('url-loader').options({limit: 4096, // Less than 4kb will be packaged as base64fallback: {loader: 'file-loader',options: {name: 'img/[name].[hash:8].[ext]',publicPath,},},});},};
vue-cli3
project can package css
into js
without generating files separately (not recommended, only suitable for projects with less css
)Configuration reference vue-cli3 official website:
module.exports = {css: {extract: false,},};
Yes it is.
Since qiankun get assets which imported by sub app via fetch, these static resources must be required to support cors.
See Enable Nginx Cors.
Scripts inserted by carriers are usually marked with async
to avoid loading of block micro apps. This is usually no problem, such as:
<script async src="//www.rogue.com/rogue.js"></script>
However, if some inserted scripts are not marked as async
, once such scripts fail to run, the entire application will be blocked and subsequent scripts will no longer be executed. We can solve this problem in the following ways:
getTemplate
methodFilter abnormal scripts in the HTML template of the micro app through the getTemplate
method implemented by yourself.
import { start } from 'qiankun';start({getTemplate(tpl) {return tpl.replace('<script src="/to-be-replaced.js"><script>', '');},});
Intercept abnormal scripts through the fetch
method implemented by yourself.
import { start } from 'qiankun';start({async fetch(url, ...args) {if (url === 'http://to-be-replaced.js') {return {async text() {return '';},};}return window.fetch(url, ...args);},});
text/plain
(ultimate solution)The principle is that the carriers can only recognize the request whose response's content-type
is text/html
and insert the script, and the response of the text/plain
type will not be hijacked.
How to modify the content-type
of the response header of the micro-application HTML request, you can google it yourself, and there is a simpler and more efficient solution:
Copy an index.txt
file from index.html
when the micro-app is published.
Change entry
in the main app to a txt address, for example:
registerMicroApps([- { name: 'app1', entry: '//localhost:8080/index.html', container, activeRule },+ { name: 'app1', entry: '//localhost:8080/index.txt', container, activeRule },],);
Qiankun will isolate stylesheet between your sub apps automatically, you can manually ensure isolation between master and child applications. Such as add a prefix to all classes in the master application, and if you are using ant-design, you can follow this doc to make it works.
Example for antd:
use webpack to modify antd less variable
{loader: 'less-loader',+ options: {+ modifyVars: {+ '@ant-prefix': 'yourPrefix',+ },+ javascriptEnabled: true,+ },}
set antd ConfigProvider
import { ConfigProvider } from 'antd';export const MyApp = () => (<ConfigProvider prefixCls="yourPrefix"><App /></ConfigProvider>);
Detailed documentation pls check antd official guide.
Use the builtin global variable to identify the environment which provided by qiankun master:
if (!window.__POWERED_BY_QIANKUN__) {render();}export const mount = async () => render();
When the subapp should be active depends on your activeRule
config, like the example below, we set activeRule
logic the same between reactApp
and react15App
:
registerMicroApps([// define the activeRule by your self{ name: 'reactApp', entry: '//localhost:7100', container, activeRule: () => window.isReactApp },{ name: 'react15App', entry: '//localhost:7102', container, activeRule: () => window.isReactApp },{ name: 'vue app', entry: '//localhost:7101', container, activeRule: () => window.isVueApp },]);start({ singular: false });
After setting singular: false
in start
method, reactApp
and react15App
should be active at the same time once isReactApp
method returns true
.
Don’t share a runtime, even if all teams use the same framework. - Micro Frontends
Although sharing dependencies isn't a good idea, but if you really need it, you can external the common dependencies from sub apps and then import them in master app.
In the future qiankun will provide a smarter way to make it automatically.
Yes.
However, the IE environment (browsers that do not support Proxy) can only use the single-instance pattern, where the singular
configuration will be set true
automatically by qiankun if IE detected.
You can find the singular usage here.
If you want qiankun (or its dependent libraries, or your own application) to work properly in IE, you need to introduce the following polyfills at the portal at least:
import 'whatwg-fetch';import 'custom-event-polyfill';import 'core-js/stable/promise';import 'core-js/stable/symbol';import 'core-js/stable/string/starts-with';import 'core-js/web/url';
We recommend that you use @babel/preset-env plugin directly to polyfill IE automatically, all the instructions for @babel/preset-env you can found in babel official document.
Here is no "fetch" on the window env, you need to polyfill it
Qiankun use window.fetch
to get resources of the micro applications, but some browsers does not support it, you should get the polyfill in the entry.
qiankun will convert the dynamic script loading of the subapplication (such as JSONP) into a fetch request, so the corresponding back-end service needs to support cross-domain, otherwise it will cause an error.
In singular mode, you can use the excludeAssetFilter
parameter to release this part of the resource request, but note that the resources released by this option will escape the sandbox, and the resulting side effects need to be handled by you.
If you use JSONP in not-singular mode, simply using excludeAssetFilter
does not achieve good results, because each application is isolated by the sandbox; you can provide a unified JSONP tool in the main application, and the subapplication just calls the tool.
It is usually because you are routing in Browser mode, which requires the server to open it. Specific configuration mode reference:
First of all, you cannot use the wildcard *
. You can register the 404 page as a normal routing page, such as /404
, and then judge in the routing hook function of the main project, if it is neither the main application routing nor the micro application , Then jump to the 404 page.
Take vue-router
as an example, the pseudo code is as follows:
const childrenPath = ['/app1', '/app2'];router.beforeEach((to, from, next) => {if (to.name) {// There is a name attribute, indicating that it is the route of the main projectreturn next();}if (childrenPath.some((item) => to.path.includes(item))) {return next();}next({ name: '404' });});
It is not feasible to use the router instance of micro-application directly to jump between micro-applications or micro-applications to main application page, such as the Link
component in react-router or router-link in vue, because the router instance jump of micro-applications is based on the 'base' of routes. There are such ways to jump:
history. PushState ()
: [MDN usage introduction] (https://developer.mozilla.org/zh-CN/docs/Web/API/History/pushState)< a href = "http://localhost:8080/app1" > app1 < / a >
window. The location. The href = 'http://localhost:8080/app1'
The server needs to configure a response header for the index.html
of the micro application: Cache-Control no-cache
, which means to check whether it is updated every time it requests.
Take Nginx
as an example:
location = /index.html {add_header Cache-Control no-cache;}
Some scenarios we had to use config entry to load micro app (** not recommended **):
loadMicroApp({name: 'configEntry',entry: {scripts: ['//t.com/t.js'],styles: ['//t.com/t.css'],},});
Since there is no HTML attached to entry JS for microapp, the mount hook simply says:
export async function mount(props) {ReactDOM.render(<App />, props.container);}
As props.container
is not an empty container and will contain information such as the style sheet that the microapp registers through the styles configuration, when we render directly for the container that the react application is applying with 'props.container', all the original DOM structures in the container will be overwritten, causing the style sheet to be lost.
We need to build an empty render container for micro applications that use Config Entry to mount react applications:
loadMicroApp({name: 'configEntry',entry: {+ html: '<div id="root"></div>',scripts: ['//t.com/t.js'],styles: ['//t.com/t.css']}});
The mount hook is not directly render to props.container
, but to its 'root' node:
export async function mount(props) {- ReactDOM.render(<App/>, props.container);+ ReactDOM.render(<App/>, props.container.querySelector('#root'));}
As the requests to pull micro-app entry are all cross-domain, when your micro-app relies on cookies (such as authentication), you need to customize the fetch method to enable the cors mode:
If you load the microapps through registerMicroApps, you need to configure a custom fetch in the start method, such as:
import { start } from 'qiankun';start({fetch(url, ...args) {// Enable cors mode for the specified microappif (url === 'http://app.alipay.com/entry.html') {return window.fetch(url, {...args,mode: 'cors',credentials: 'include',});}return window.fetch(url, ...args);},});
If you load the microapp via loadMicroApp, you need to configure a custom fetch when invoking, such as:
import { loadMicroApp } from 'qiankun';loadMicroApp(app, {fetch(url, ...args) {// Enable cors mode for the specified microappif (url === 'http://app.alipay.com/entry.html') {return window.fetch(url, {...args,mode: 'cors',credentials: 'include',});}return window.fetch(url, ...args);},});