前言
微前端 —— 理论篇
微前端 —— portal 项目
上一篇中,我们完成了 portal 项目的搭建,算是完成了整个微前端架构的一半工程了。现在开始新建我们的业务小应用。
Menu 项目
menu 项目是作为页面的菜单显示的,主要用于路由的控制。
项目的结构如下:
接下来就开始实现它吧。
- 新建项目文件夹 menu,在根目录执行
npm init -y
- 安装相关依赖,由于 react 相关的几个依赖已经在
portal
项目中抽离出来,因此我们这不需要安装了。
package.js
文件内容如下:
{
"name": "menu",
"version": "1.0.0",
"description": "","main":"index.js","scripts": {"test":"echo \"Error: no test specified\" && exit 1","start":"webpack-dev-server --config ./webpack.dev.js --port 8235","build":"webpack --config ./webpack.config.js -p"},"keywords": [],"author":"",
"license": "ISC",
"dependencies": {
"antd": "^3.20.7",
"babel-plugin-import": "^1.12.0",
"copy-webpack-plugin": "^5.0.4",
"react-router": "4.3.1",
"react-router-dom": "4.3.1",
"single-spa-react": "2.8.1",
"@reach/router": "1.2.1"
},
"devDependencies": {
"@babel/core": "7.0.0",
"@babel/plugin-proposal-class-properties": "7.0.0",
"@babel/plugin-proposal-decorators": "7.1.0",
"@babel/plugin-proposal-object-rest-spread": "7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-react": "7.0.0",
"autoprefixer": "9.1.5",
"babel-core": "6.26.3",
"babel-loader": "8.0.0",
"clean-webpack-plugin": "0.1.19",
"css-loader": "1.0.0",
"postcss-loader": "3.0.0",
"style-loader": "0.23.0",
"webpack": "4.17.1",
"webpack-cli": "3.1.0",
"webpack-dev-server": "^3.1.14"
}
}
新建 src 文件夹,在 src 文件夹下创建 components 文件夹,在 components 文件夹中新建文件Menu.js
, 内容如下:
import React from 'react'
import {Menu, Icon} from 'antd'
import {Link} from '@reach/router'
const MenuItem = Menu.Item
export default class Menu_ extends React.Component {render () {
return (<div style={menuStyle}>
<h1>menu!</h1>
<Menu>
<MenuItem key="1">
<Icon type="pie-chart"/>
<Link to='/' style={{display: 'inline'}}> 主页 </Link>
</MenuItem>
<MenuItem key="2">
<Icon type="desktop"/>
<Link to='/page1' style={{display: 'inline'}}> 页面 1 </Link>
</MenuItem>
<MenuItem key="32">
<Icon type="desktop"/>
<Link to='/page2' style={{display: 'inline'}}> 页面 2 </Link>
</MenuItem>
<MenuItem key="4">
<Icon type="desktop"/>
<Link to='/page3' style={{display: 'inline'}}> 页面 3 </Link>
</MenuItem>
<MenuItem key="5">
<Icon type="desktop"/>
<Link to='/page4' style={{display: 'inline'}}> 页面 4 </Link>
</MenuItem>
</Menu>
</div>
)
}
}
const menuStyle = {
display: 'inline-block',
position: 'fixed',
width: '300px',
height: '100%'
}
现在我们已经实现了菜单的页面代码。在 src 目录下新建文件root.component.js
,引入导出 Menu 组件
import React from 'react'
import {BrowserRouter, Route, Switch, Redirect} from 'react-router-dom'
import Menu from './components/Menu'
export default class Root extends React.Component {
state = {hasError: false}
componentDidCatch (error, info) {this.setState({hasError: true})
}
render () {
return (<Menu />)
}
}
在 src 目录下新建文件set-public-path.js
// For lazy loading within an application to work you need to set webpack's public path
// basically webpack's internal module system always looks for code-splits (modules) at the root
export default function setPublicPath() {return Promise.all([getUrl()]).then(values => {const [url] = values
const webpackPublicPath = url.slice(0, url.lastIndexOf('/') + 1)
__webpack_public_path__ = webpackPublicPath
return true
})
}
function getUrl () {return window.System.resolve('@portal/menu')
}
在 src 目录下新建文件menu.js
,用于组件的注册
import React from 'react'
import ReactDOM from 'react-dom'
import singleSpaReact from 'single-spa-react'
import {property} from 'lodash'
import setPublicPath from './set-public-path.js'
const reactLifecycles = singleSpaReact({
React,
ReactDOM,
loadRootComponent: () => import(/* webpackChunkName: "people-app" */'./root.component.js').then(property('default')),
domElementGetter,
})
export const bootstrap = [() => {return setPublicPath()
},
reactLifecycles.bootstrap,
]
export const mount = [reactLifecycles.mount,]
export const unmount = [reactLifecycles.unmount,]
export const unload = [reactLifecycles.unload,]
function domElementGetter() {let el = document.getElementById("menu");
if (!el) {el = document.createElement('div');
el.id = 'menu';
document.body.appendChild(el);
}
return el;
}
最后编写 webpack 配置文件,在项目根目录下创建文件webpack.config.js
、webpack.dev.js
webpack.config.js
/* eslint-env node */
const webpack = require('webpack')
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {entry: path.resolve(__dirname, 'src/menu.js'),
output: {
filename: 'menu.js',
library: 'menu',
libraryTarget: 'amd',
path: path.resolve(__dirname, 'build/menu'),
},
mode: 'production',
module: {
rules: [{parser: {System: false}},
{
test: /\.js?$/,
exclude: [path.resolve(__dirname, 'node_modules')],
loader: 'babel-loader',
},
{
test: /\.css$/,
exclude: [path.resolve(__dirname, 'node_modules')],
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path][name]__[local]',
},
},
{
loader: 'postcss-loader',
options: {plugins() {
return [require('autoprefixer')
];
},
},
},
],
},
{
test: /\.css$/,
include: [path.resolve(__dirname, 'node_modules')],
exclude: [/\.krem.css$/],
use: ['style-loader', 'css-loader'],
},
],
},
resolve: {
modules: [
__dirname,
'node_modules',
],
},
plugins: [new CleanWebpackPlugin(['build/menu']),
new CopyWebpackPlugin([{from: path.resolve(__dirname, 'src/Menu.js')}
]),
],
devtool: 'source-map',
externals: [
/^@portal\/*/,
/^lodash$/,
/^single-spa$/,
/^rxjs\/?.*$/,
/^react$/,
/^react\/lib.*/,
/^react-dom$/,
/.*react-dom.*/,
],
};
公共依赖就别单独打包了
webpack.dev.js
/* eslint-env node */
const config = require('./webpack.config.js');
const webpack = require('webpack');
config.plugins.push(new webpack.NamedModulesPlugin());
config.plugins.push(new webpack.HotModuleReplacementPlugin());
config.devServer = {
headers: {"Access-Control-Allow-Origin": "*",},
}
config.mode = 'development'
module.exports = config;
menu 项目的源码大概就这些
3.project1
project1 项目也是通过 react 框架实现的,主要是实现了不同路由匹配不同页面,最后运行打包的时候,暴露出一个 js 文件,共 portal 项目调用。详细的就不阐述了,跟 menu 项目大同小异,也就只是页面代码不一样。
menu 源码地址
project1 源码地址
项目源码地址
微前端 —— 理论篇
微前端 —— portal 项目
微前端 —— project2 项目(VUE)