English | 简体中文
Micro Frontends solution for large application. Website Chinese docs.
NPM version build status Test coverage NPM downloads David deps
- No framework constraint for main&sub applications, support React/Vue/Angular/...
- Sub-application support multiple types of entry: js&css, html entry, html content
- Compatible with single-spa sub-application and lifecycles
- JavaScript sandbox by
Proxy
API
https://icestark-vue.surge.sh/
Main-application based on Vue, And sub-applications based on React, Vue respectively.
https://icestark-react.surge.sh/
Main-application based on React, And sub-applications based on React, Vue, Angular respectively.
Concepts:
- Main-application: also named framework application, responsible for sub-applications registration&load&render, layout display (Header, Sidebar, Footer, etc.)
- Sub-application: responsible for content display related to its own business
Main-application:
# Based on React $ npm init ice icestark-layout @icedesign/stark-layout-scaffold # Based on Vue $ npm init ice icestark-layout @vue-materials/icestark-layout-app $ cd icestark-layout $ npm install $ npm start
Sub-application:
# Based on React $ npm init ice icestark-child @icedesign/stark-child-scaffold # Based on Vue $ npm init ice icestark-child @vue-materials/icestark-child-app $ cd icestark-child $ npm install $ npm run start
// src/App.jsx import React from 'react'; import ReactDOM from 'react-dom'; import { AppRouter, AppRoute } from '@ice/stark'; class App extends React.Component { onRouteChange = (pathname, query) => { console.log(pathname, query); }; render() { return ( <div> <div>this is common header</div> <AppRouter onRouteChange={this.onRouteChange} ErrorComponent={<div>js bundle loaded error</div>} NotFoundComponent={<div>NotFound</div>} > <AppRoute path={['/', '/message', '/about']} exact title="通用页面" url={['//unpkg.com/icestark-child-common/build/js/index.js']} /> <AppRoute path="/seller" url={[ '//unpkg.com/icestark-child-seller/build/js/index.js', '//unpkg.com/icestark-child-seller/build/css/index.css', ]} /> </AppRouter> <div>this is common footer</div> </div> ); } } ReactDOM.render(<App />, document.getElementById('ice-container'));
AppRouter
locates the sub-application rendering nodeAppRoute
corresponds to the configuration of a sub-application,path
configures all route information,basename
configures a uniform route prefix,url
configures assets urlicestark
will follow the route parsing rules like to determine the currentpath
, load the static resources of the corresponding sub-application, and render
supported by @ice/stark@2.0.0
import { registerMicroApps } from '@ice/stark'; regsiterMicroApps([ { name: 'app1', activePath: ['/', '/message', '/about'], exact: true, title: '通用页面', container: document.getElementById('icestarkNode'), url: ['//unpkg.com/icestark-child-common/build/js/index.js'], }, { name: 'app2', activePath: '/seller', title: '商家平台', container: document.getElementById('icestarkNode'), url: [ '//unpkg.com/icestark-child-seller/build/js/index.js', '//unpkg.com/icestark-child-seller/build/css/index.css', ], }, ]); start();
after sub-application is registered, icestark will load app according to the activePath
.
sub-application can expose lifecycles in both register lifecycles and export lifecycles(umd) ways.
// src/index.js import ReactDOM from 'react-dom'; import { isInIcestark, getMountNode, registerAppEnter, registerAppLeave } from '@ice/stark-app'; import router from './router'; if (isInIcestark()) { const mountNode = getMountNode(); registerAppEnter(() => { ReactDOM.render(router(), mountNode); }); // make sure the unmount event is triggered registerAppLeave(() => { ReactDOM.unmountComponentAtNode(mountNode); }); } else { ReactDOM.render(router(), document.getElementById('ice-container')); }
- Get the render
DOM Node
viagetMountNode
- Trigger app mount manually via
registerAppEnter
- Trigger app unmount manually via
registerAppLeave
// src/router.js import React from 'react'; import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; import { renderNotFound, getBasename } from '@ice/stark-app'; function List() { return <div>List</div>; } function Detail() { return <div>Detail</div>; } export default class App extends React.Component { render() { return ( <Router basename={getBasename()}> <Switch> <Route path="/list" component={List} /> <Route path="/detail" component={Detail} /> <Redirect exact from="/" to="list" /> <Route component={() => { return renderNotFound(); }} /> </Switch> </Router> ); } }
- Get the
basename
configuration in the framework application viagetBasename
renderNotFound
triggers the framework application rendering global NotFound
exports lifecycles in sub-application:
import ReactDOM from 'react-dom'; import App from './app'; export function mount(props) { ReactDOM.render(<App />, document.getElementById('icestarkNode')); } export function unmount() { ReactDOM.unmountComponentAtNode(document.getElementById('icestarkNode')); }
sub-application should be bundled as an UMD module, add the following configuration of webpack:
module.exports = { output: { library: 'sub-app-name', libraryTarget: 'umd', }, };
https://micro-frontends.ice.work/
Feel free to report any questions as an issue, we'd love to have your helping hand on icestark
.