webpack5模块联邦(Module Federation)使用教程,在vue2中使用
使用vue2组件,演示webpack5中模块联邦(Module Federation)的使用,以及如果优化依赖。
博客示例demo已经上传gitee
Module Federation 可以实现微前端的效果,只是它是模块级的。即一个应用可以引入另外一个应用的模块。例如在a域名下启动的应用里,可以直接引用b域名应用里的资源。也就是说a应用可以异步获取b应用的组件进行使用。
这样就可以将应用分为更小的应用块,头部导航栏、侧边栏、业务逻辑组件都可以分到不同的应用块里开发,同时应用块间间可共享,互相依赖,可实时获取其它应用块构建好的bundle资源。
在入门前,先对一些概念达成共识:
在Module Federation中,每个应用都是一个独立的构建(webpack工程),称为容器。引用远程模块的应用叫作host,被引用模块所在的应用称为remote。
用到的插件:html-webpack-plugin、vue-loader、vue-template-compiler(需要和vue版本一致)、vue-style-loader、css-loader、webpack、webpack-cli、webpack-dev-server
启动webpack服务可使用:npx webpack serve --open
基础使用
一、配置remote应用,模块联邦插件会根据配置打包出对应的资源。
如下配置会构建向外暴露的三个组件对应的bundle,并暴露给其它应用。如果remote应用自身的入口文件里引用了这三个组件,会按本地依赖关系进行打包,不会与模块联邦共享。也就是说这三个组件会被打包两次,一次是根据模块联邦的配置,另一次是根据自身依赖关系。
remote应用只要在注册ModuleFederationPlugin插件时,定义三个参数
- name(向外暴露容器名称)
- filename(连接本容器的入口文件,包含暴露出去组件的及它们的依赖信息)
- exposes:向外暴露的组件
// remote应用的webpack.config.js配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
// webpack5中配置模块联邦的插件
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
clean: true,
// publicPath: 'http://localhost:3001/'
},
devServer: {
port: '3001'
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html'
}),
new VueLoaderPlugin(), // 处理vue文件
new ModuleFederationPlugin({
name: 'remote_app', // 向外暴露容器名称
filename: 'remote_index.js', // 连接本容器的入口文件,包含暴露出去组件的及它们的依赖信息
exposes: {
// 这里暴露了三个vue组件
// 它们依赖vue、lodash、本地其它组件
// 格式:{remote组件访问路径} :{本地文件路径}
'./MyTable': './src/com/myTable.vue',
'./MyTopBar': './src/com/top-bar.vue',
'./MyText': './src/com/text.vue'
},
})
],
experiments: {
// 模块中可在一级作用域直接使用await import('xxx')
topLevelAwait: true
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
}
二、host应用配置远程组件地址
host端同样需要注册ModuleFederationPlugin插件,只要定义remotes参数来接入远程应用即可。
也可像remote应用那样加上name、filename、exposes参数来向外暴露组件
// host应用的webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
// webpack5中配置模块联邦的插件,也可以解构导入
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
clean: true
},
devServer: {
port: 3000
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html'
}),
new VueLoaderPlugin(),
new ModuleFederationPlugin({
remotes: {
// {远程app本地别名}: {远程app配置的name}@远程app根地址/{远程app配置的filename}}
// remote_index.js里包含的组件会用到的所有第三方依赖
// ,这些依赖可能拆分多个文件,具体看远程容器的webpack配置
'remote-app': 'remote_app@http://localhost:3001/remote_index.js',
},
})
],
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
}
}
三、在host里使用远程组件,像异步组件一样使用
远程组件的导入路径由两部分组成:
import('remote-app/MyTopBar')
- 一是host应用中,new模块联邦插件时,定义的remotes参数里,对应的远程应用地址的key,如
remote-app
- 二是remote应用中,new模块联邦插件时,定义的exposes参数里,对应的模块key,如
./MyTable
<!--
// host:app.vue
// 远程组件对应的bundle需要在运行时被加载
// 所以要通过动态引入,地址格式:
// import('{远程应用本地别名}/{远程应用exposes的key}')
-->
<template>
<div>
<reomteTable></reomteTable>
<hostHellow></hostHellow>
</div>
</template>
<script>
// 引入host应用本地的两个组件
import hostLoading from './com/loading.vue';
import hostError from './com/Error.vue';
export default {
components: {
// 引入本地异步组件
hostHellow: () => import('./com/hellow.vue'),
// 引入远程组件,和引入异步组件(动态导入的模块)语法类似
// 路径里remote-app是host里配置的,MyTopBar是remote里配的
remoteTopBar: () => import('remote-app/MyTopBar'),
reomteTable: () => ({
// 远程组件和本地异步组件使用上无差别
component: import('remote-app/MyTable'),
// 异步组件加载时使用的组件
loading: hostLoading,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 加载失败时使用的组件
error: hostError,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
}),
}
}
</script>
到这里已经可以使用远程组件了,但还可以继续优化:
抽离公共依赖
远程组件依赖了vue和lodash,这两个依赖会被打包进remote应用的
remote_index.js
里,当host引入远程组件,会加载加载remote应用生成的remote_index.js
文件,如果host本地也依赖了vue、lodash,它们会被加载两次
remote如果通过缓存组等配置把vue、lodash打包到了独立bundle,
remote_index.js
就不会把它们打包进来,而是remote_index.js
在host被加载后,向remote请求这些依赖的bundle
在host、remote应用里,同时配置插件的shared字段,可避免依赖重复加载。这里配置singleton为true,把vue等依赖打包到独立的bundle里,被host、remote两个应用加载:
一、remote的模块联邦插件配置
只要在ModuleFederationPlugin插件多定义shared参数即可
// webpack.config.js
new ModuleFederationPlugin({
name: 'remote_app',
filename: 'remote_index.js',
exposes: {
'./MyTable': './src/com/myTable.vue',
'./MyTopBar': './src/com/top-bar.vue',
'./MyText': './src/com/text.vue'
},
// 暴露出去的组件依赖了vue、lodash,将vue、lodash独立打包,
// 与引用这些组件的host容器共享这些依赖,避免多次加载
shared: {
vue: {
singleton: true,
},
lodash: {
singleton: true,
}
}
})
二、host的模块联邦插件配置
同样添加shared参数即可
// webpack.config.js
new ModuleFederationPlugin({
remotes: {
'remote-app': 'remote_app@http://localhost:3001/remote_index.js',
},
shared: {
// 远程和本地主机都要设置一样的
vue: {
singleton: true,
},
lodash: {
singleton: true,
}
}
})
三、vue等依赖改为动态引入
无论host还是remote应用,vue等依赖只要配置了shared的 singleton: true
后,这些依赖会被独立打包。
它的弊端在于:
会直接影响本地代码对这些依赖的引入方式,本地代码是无法再静态导入这些依赖的,只能动态导入,如下代码会报错:
// 入口文件 index.js
// 如果这样引入vue会报错:Shared module is not available for eager consumption
// 这里可以是remote的或者host的,因为两个都配置了singleton: true
import Vue form 'vue'
new Vue({
// ...
})
解决:
把应用的入口文件代码抽离到bootstrap.js里,入口文件再动态引入bootstrap.js。
这样,整个应用都变成了异步导入,原本需要动态引入的依赖可以直接静态引入
-
新建bootstrap.js(可叫其它名字)
// 静态导入vue并初始化等 import Vue from 'vue'; import App from './app.vue'; // Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')
-
入口文件动态引入bootstrap.js模块
// index.js import('./bootstrap.js') console.log('index 入口文件');
-
这里vue等依赖可正常加载了,不再报开头的错,同时bootstrap.js及其导入的依赖里,都可以直接使用静态导入的方式,加载异步组件(动态导入)
// 以bootstrap.js引入的app.vue为例 // 例如:remoteTopBar = ()=>import('remote-app/MyTopBar') // 可改成 import remoteTopBar from 'remote-app/MyTopBar' <template> <div> <remoteTopBar></remoteTopBar> <hostHellow></hostHellow> </div> </template> <script> import remoteTopBar from 'remote-app/MyTopBar'; // 动态引入依旧可以使用 // const remoteTopBar = () => import('remote-app/MyTopBar'); export default { components: { remoteTopBar, // 本地动态组件 hostHellow: () => import('./com/hellow.vue') } } </script>
更多推荐
所有评论(0)