引言
大家好~
周末时间,探究下webpack5提供的新能力Federation, 本文主要使用这个功从0-1搭建微前端的环境。在文末会针对微前端进行一定的分析。
目前在社区有很多关于微前端的介绍,包括沙箱环境等,微前端的价值、缺点等等。在文末会列出几个链接,如果感兴趣的可以了解下,这里就不在多赘述。
Webpack Federation
Federation是webpack5提供的新能力,官网介绍: “多个独立的构建可以组成一个应用程序,这些独立的构建之间不应该存在依赖关系,因此可以单独开发和部署它们。”
主要的api
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
shared: {}, // 要共享的依赖库
library: {}, // 当前要导出模块的定义
remotes:{}, // 其他子的定义
filename: '', // 导出的文件名
exposes: {} // 导出的模块
}),
],
};
复制代码
开始
在一个项目里,分了很多子模块和一个公用的基座,本文为了方便将所有的项目都放在一个文件里,所以自然而然就选择了现在比较热门的包管理工具 lerna
项目初始化
mkdir federation-demo
cd federation-demo & yarn init
yarn add lerna
lerna init
复制代码
项目初始化完后,就该写对的运行命令了
"scripts": {
"init": "lerna init",
"build": "lerna run --parallel build",
"serve": "lerna run --parallel serve"
}
复制代码
项目的目录结构
federation-demo/
packages/
app1(基座)
app2 (子应用)
package.json
lerna.json
复制代码
搭建基座(app1)
主要以react为主,搭建简单的应用。
app1目录结构
app1/
public
index.html
src
app.js
index.js
webpack.config.js
package.json
复制代码
为了方便调试,这里用serve来启动一个静态的服务器,package.json相关的命令如下:
"scripts": {
"start": "webpack serve",
"build": "webpack --mode development",
"serve": "serve dist -p 8888"
}
复制代码
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>项目一</title>
<script src="xxxxx"></script><!-- 远端暴露的地址,子应用的路径 -->
</head>
<body>
<h1>项目一</h1>
<div id="app"></div>
</body>
</html>
复制代码
scr/app.js
import React from 'react'
const { lazy, Suspense} = React
const App2 = lazy(() => import('app2/App2')) // 子应用
const App = () => (
<div>
<h1>容器应用</h1>
<Suspense fallback="loading">
<App2 />
</Suspense>
</div>
)
export default App
复制代码
src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'
ReactDOM.render(<App />, document.getElementById('app'))
复制代码
上面主要在搭建一个简单的react页面应用,接下来的重头戏就是webpack.config.js的配置了,主要借助Federation的能力,进行开发。
因为是手动搭建的目录,react的编译就需要借助babel,已经一些必要的依赖库,比如webpack等
yarn add babel-loader @babel/core @babel/preset-env @babel/preset-react webpack webpack-cli -D
复制代码
上面装了一些必要的工具和库,但是为了方便,还需要装一个库,方便来启动服务,进行测试
yarn add serve
复制代码
webapck.config.js配置(这里会忽略一些配置,比如编译react的配置,相信大家都已经烂熟于心了)。下面是主要的配置:
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app1',
library: { type: 'var', name: 'app1' },
remotes: { app2: 'app2' },
shared: {
react: {
singleton: true,
eager: true,
},
'react-dom': {
singleton: true,
eager: true,
}
},
}),
]
}
复制代码
解析:
- library: 主要是定义app1, 将app1以全局变量的方式挂载
- name: 这个分享库的名称
- remotes: 远端的子项目
- shared: 需要共享的依赖(对应的参数可以参考官网,文末链接)
到这里基本的基座环境已经写的差不都了, 接下来搭建子应用。
子应用(app2)
其实子应用和基座差不多,我们直接复制一份,然后稍作修改就行。
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>项目二</title>
</head>
<body>
<h1>项目二</h1>
<div id="app"></div>
</body>
</html>
复制代码
src/app.js
import React from 'react'
import Button from './button'
const App = () => (
<div>
<h2>子应用</h2>
<Button />
</div>
)
export default App
复制代码
src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'
ReactDOM.render(<App />, document.getElementById('app'))
复制代码
为了体现组件的灵活应用,我们这里还将为app2拆除一个按钮组件
src/button.js
import React from 'react'
const Button = () => <button>我是子应用的按钮</button>
export default Button
复制代码
重点来了,就是我们的webpack.config.js的配置,这里的配置和app1的配置有点区别,如下:
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app2',
library: {
type: 'var',
name: 'app2'
},
exposes: {
'./App2': './src/app', // 要暴露的组件
'./Button': './src/button'
},
filename: 'remote.js', //往外暴露的文件名
}),
],
}
复制代码
子应用主要的任务就是:暴露出对应的js,让需要用到的主应用加载到对应的js文件(相当于入口文件)。
原理其实很简单, app2对外分别暴露了:整个应用 和 一个按钮组件,在需要用到这个内容时,加载这个js文件,然后使用对应暴露的组件就可以。
项目运行
利用lerna提供的能力,可以在根目录直接运行打包和启动服务器的相关命令
yarn build
复制代码
同时启动子项目的打包,避免一个项目一个项目的打包,太繁琐。
打包完成后,就可以启动静态服务器,测试项目: app1.启动在8888端口,app2启动在8889端口。
打开app1的路径:
可以看到项目已经正常的运行,并且通过了Federation加载了其他项目的内容,包括其他项目的组件(当然前提是:其他项目对外进行了暴露)
此时打开控制台,可以很直观的看到webpack打出来的包,最主要的文件是:remote.js 、src_app_js.js、src_button_js.js
最主要的包是remote.js,应用加载这个包后,然后加载对应的组件比如app, button等。
remote.js中的代码,可以看到app2以var 的形式挂载到了全局
main.js,利用webpack独有的自定义模块机制,将app2挂载到了模块里,这样子当主应用访问时,就可以访问到这个子应用的一些东西。
总结
目前微前端的实现方案有很多,比如iframe、qiankun、Web component等。但是共同要解决的可能是下面几个问题:
- 考虑一个沙箱环境
- 子应用嵌套
- 父子应用通讯问题
- 公共依赖
webpack 提供了federation这个能力,也可能是考虑到时代的变化,工具的演进要随着时代的脚步,但是webpack的配置对于很对新手来说还是有点挑战的。
个人觉得,微前端这个概念,将应用变得复杂起来了,本质上是为了整合不同的技术。但是如果转念一想,当今比较热门的框架:Vue、React等,理念是一样的都是组件,编译,响应式,只是对应的处理可能不太一样。
有没有这么一天,当浏览器的原生组件(Web Component)支持的很好了,Vue、React会不会也会像Jq一样,慢慢的淡出圈子。
END
以上是周末对Webpack新特性的一些实践和想法,欢迎关注: coding
我是Gavin,让我们一起加油 !!