本文将从0搭建开发环境,实现一个最简单的模块联邦功能,理解模块联邦原理。
模块联邦工作原理
模块联邦的工作原理其实就是我们有A、B两个项目,我们在A项目里导出一个模块,然后B模块可以引入这个模块进行使用,就像引入一个npm包一样。这个模块可以是一个组件也可以是一个功能函数,这样就实现了项目间模块的共享。
如果我们有更多的项目,比如C、D,那么C、D项目都可以引入A项目导出的模块,同时C、D也可以导出自己的模块给其它项目使用。
大致原理就是这样,接下来我们来实现它。
搭建项目开发环境
首先我们用Webpack搭建一个Vue项目的开发环境,我们创建一个文件夹webpack-federation,然后在下面再创建两个文件夹,分别是project1和project2作为我们的两个项目。然后我们进入这两个目录分别执行pnpm init来初始化我们的package.json文件。
进入project1目录,安装我们需要的包:
pnpm install webpack webpack-cli webpack-dev-server html-webpack-plugin vue-loader -D
pnpm install vue
创建webpack.config.js
然后创建webpack.config.js文件
const path = require("path");
const { VueLoaderPlugin } = require("vue-loader");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/main.js",
mode: "development",
output: {
path: path.resolve(__dirname, "./dist"),
filename: "name[hash:6].js",
clean: true, // 在生成文件之前清空 output 目录
},
devServer: {
port: 8080,
},
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader",
},
],
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "./public/template.html"),
}),
],
};
src目录
创建src目录,然后创建main.js和App.vue
main.js
// main.js
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.mount("#app");
App.vue
// App.vue
<template>
<div>This is project1</div>
</template>
<script setup></script>
template
然后创建public/template.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>Project1</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
package.json scripts
在package.json添加scripts
"scripts": {
"start": "webpack serve",
"build": "webpack"
},
我们执行pnpm run start,可以看到项目正常启动
搭建project2项目
我们用同样的方式搭建项目project2,可以将project1下的src、public目录以及webpack.config.js拷贝到project2,然后修改webpack.config.js里的服务端口为8081。
更改project2/src/App.vue
// project2/src/App.vue
<template>
<div>This is project2</div>
</template>
<script setup></script>
将project1/package.json下的scripts和dependencies拷贝到project2/package.json下
// 拷贝的内容
"scripts": {
"start": "webpack serve",
"build": "webpack"
},
"devDependencies": {
"html-webpack-plugin": "^5.5.0",
"vue-loader": "^17.0.1",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
},
"dependencies": {
"vue": "^3.2.45"
}
然后在preject2下执行
pnpm install
pnpm run start
可以看到在8081端口启动了project2项目
两个项目搭建完毕,到目前为止还没有涉及模块联邦的功能,接下来我们实现模块联邦功能。
实现模块联邦
我们要实现的功能是在project1里使用project2里共享出来的模块。
比如我们在project2里有一个List组件,这个组件我们想要和其它项目共享,其它项目也可以使用这个组件,那么我们可以在prject2里将这个组件暴露出去,然后其它项目再引入这个组件就可以了。
新建List.vue
我们首先新建project2/src/List.vue文件 内容如下:
<template>
<div>This is List Component</div>
</template>
<script setup></script>
然后我们在project2/src/App.vue里可以使用它
// project2/src/App.vue
<template>
<div>This is project2</div>
<List />
</template>
<script setup>
import List from "./List.vue";
</script>
这时页面显示如下
List组件在project2里使用正常
导出共享模块
我们想要这个List.vue不仅在project2里可以使用,在其它项目里也可以使用,那么我们可以将这个模块暴露,我们需要使用一个模块联邦的插件,在project2/webpack.config.js里我们增加如下配置:
// webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
// ...其它配置
plugins: [
new ModuleFederationPlugin({
name: "remoteV2",
library: { type: "var", name: "remoteV2" },
filename: "remoteEntry.js",
exposes: [
{
"./List": "./src/List.vue",
},
],
}),
],
}
相关配置项说明如下
ModuleFederationPlugin
ModuleFederationPlugin是webpack提供的内置插件
name、library
我们的项目导出的模块的整体名字,这里我们设置为'remoteV2',这个名字可以任意设置,一般和library配合使用,当我们进行如下配置:
name: "remoteV2",
library: { type: "var", name: "remoteV2" },
那么我们导出的模块会挂载在window.remoteV2下面,其它项目使用该模块的时候会从window.remoteV2取模块导出的内容。
filename
我们导出的模块的文件名
比如我们设置
filename: "remoteEntry.js",
那么就会在dist目录下打包生成一个remoteEntry.js文件,可以理解为将我们需要导出的模块作为入口文件,然后打包后放到remoteEntry.js文件里面,其它项目需要使用的时候通过http://localhost:8080/remoteEntry.js可以访问我们共享的模块。
exposes
exposes就是我们要导出的模块,比如我们设置
exposes: [
{
"./List": "./src/List.vue",
},
],
即我们会导出./src/List.vue模块,然后给这个导出的模块取一个名字./List,这个./List名字在其它项目导入模块的时候需要使用。
使用共享的模块
我们在project2里导出了需要共享的模块,然后我们在project1里使用这个模块。
在project1/webpack.config.js里新增如下配置
// webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
// ...其它配置
plugins: [
new ModuleFederationPlugin({
name: "remoteV1",
remotes: {
"app_project1": "remoteV2@http://localhost:8081/remoteEntry.js",
},
}),
],
}
然后再project1/App.vue里引入这个远程的共享模块
// project1/App.vue
<template>
<div>This is project1</div>
<div>下面是从project2 远程加载的组件</div>
<List />
</template>
<script setup>
import { defineAsyncComponent } from "vue";
const List = defineAsyncComponent(() => import("app_project1/List"));
</script>
其中defineAsyncComponent是Vue提供的用来定义远程加载组件的Api
然后我们再启动project2和project1项目,发现List组件都能正确加载。
下面我们对使用共享模块的配置做一个说明
其中name: "remoteV1"在导出模块的时候才需要使用,是导出模块的时候需要挂在windows下的变量,我们这里随便取个名字。
然后是关键配置
remotes: {
"app_project1": "remoteV2@http://localhost:8081/remoteEntry.js",
},
remotes配置我们需要使用的远程模块,我们配置了一个远程模块名字是app_project1,这个名字是可以自己随便定义的,使用的时候用自己定义的这个名字就好了,这个模块的加载地址是http://localhost:8081/remoteEntry.js。
同时在地址前有一个remoteV2@,这个名字需要和project1导出时候设置的名字保持一致。当导入模块的时候,project1会先去加载http://localhost:8081/remoteEntry.js文件,然后在文件加载成功之后,会去window.remoteV2下去取文件导出的内容。
然后我们在App.vue里使用import("app_project1/List")的方式引入我们需要的组件,我们可以认为app_project1是project1整个导出的模块,但是我们只需要导出的整个模块里的List组件。
回顾一下project1导出的配置
exposes: [
{
"./List": "./src/List.vue",
},
],
我们需要使用./List,app_project1 + ./List就是app_project1/List这里可以看做是一个路径拼接的方式。
实际项目中我们导出的可能有多个组件,类似
exposes: [
{
"./List": "./src/List.vue",
"./Item": "./src/Item.vue",
"./Menu": "./src/Menu.vue",
},
],
使用的时候就是
import { defineAsyncComponent } from "vue";
const List = defineAsyncComponent(() => import("app_project1/List"));
const Item = defineAsyncComponent(() => import("app_project1/Item"));
const Menu = defineAsyncComponent(() => import("app_project1/Menu"));
以上就是模块联邦功能的简单实现
本文代码git 仓库