English | 简体中文
Micro Frontends solution for large application. Website Chinese docs.
- 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/
Project | Version | Docs | Description |
---|---|---|---|
icejs | docs | A universal framework based on react.js | |
icestore | docs | Simple and friendly state for React | |
formily | docs | Alibaba Group Unified Form Solution | |
iceworks | docs | Universal Application Development Pack for VS Code |
Feel free to report any questions as an issue, we'd love to have your helping hand on icestark
.
If you're interested in icestark
, see CONTRIBUTING.md for more information to learn how to get started.