微前端 和 webapck5 Federation,从0到1搭建微前端环境

引言

image.png

大家好~

周末时间,探究下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,
                }
            },
        }),
    ]
}
复制代码

解析:

  1. library: 主要是定义app1, 将app1以全局变量的方式挂载
  2. name: 这个分享库的名称
  3. remotes: 远端的子项目
  4. 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
复制代码

同时启动子项目的打包,避免一个项目一个项目的打包,太繁琐。
image.png

打包完成后,就可以启动静态服务器,测试项目: app1.启动在8888端口,app2启动在8889端口。

打开app1的路径:
image.png

可以看到项目已经正常的运行,并且通过了Federation加载了其他项目的内容,包括其他项目的组件(当然前提是:其他项目对外进行了暴露)

此时打开控制台,可以很直观的看到webpack打出来的包,最主要的文件是:remote.js 、src_app_js.js、src_button_js.js

最主要的包是remote.js,应用加载这个包后,然后加载对应的组件比如app, button等。

image.png

remote.js中的代码,可以看到app2以var 的形式挂载到了全局
image.png

main.js,利用webpack独有的自定义模块机制,将app2挂载到了模块里,这样子当主应用访问时,就可以访问到这个子应用的一些东西。
image.png

总结

目前微前端的实现方案有很多,比如iframe、qiankun、Web component等。但是共同要解决的可能是下面几个问题:

  1. 考虑一个沙箱环境
  2. 子应用嵌套
  3. 父子应用通讯问题
  4. 公共依赖

webpack 提供了federation这个能力,也可能是考虑到时代的变化,工具的演进要随着时代的脚步,但是webpack的配置对于很对新手来说还是有点挑战的。

个人觉得,微前端这个概念,将应用变得复杂起来了,本质上是为了整合不同的技术。但是如果转念一想,当今比较热门的框架:Vue、React等,理念是一样的都是组件,编译,响应式,只是对应的处理可能不太一样。

有没有这么一天,当浏览器的原生组件(Web Component)支持的很好了,Vue、React会不会也会像Jq一样,慢慢的淡出圈子。

END

以上是周末对Webpack新特性的一些实践和想法,欢迎关注: coding

我是Gavin,让我们一起加油 !!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享