携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情
初始化项目
项目名称我这边就叫MF,然后对着课程中提供的项目结构建好对应的目录和文件,之前记录过很多次初始化过程了,这里是最后一次了。
- 新建
MF的目录作为项目根目录。 - 使用终端
cd进入到MF npm init -y初始化出package.jsonnpm i -D webpack webpack-cli webpack-dev-server安装webpack的依赖配置。
课程中提到了一个
lerna的玩意,让你自己去看,自己去学习,自己啥也不讲,很郁闷的自己去找资料,过程不提了,感兴趣的自己去试试。
npx lerna init初始化lerna
这一步会初始
lerna,自动创建lerna.json,还有其他的东西,初始化完了也就完了,不用管其他的了,我不讲是因为我还没学会。
app1的配置
- 新建
app1目录 npm init -y初始一个package.jsonapp1下新建src目录。src下新建main.js、utils.js、foo.js
app1在这里是作为子应用的存在创建的,里面有3个js文件,都没有讲里面的内容是什么,这里我试过了,其他的不用管,主要是utils.js里面需要导出一个名为sayHello的函数,如下
// utils.js
export function sayHello() {
console.log('sayHello')
}
- 新建
webpack.config.js并配置,如下
const path = require("path");
const {ModuleFederationPlugin} = require("webpack").container;
module.exports = {
mode: "development",
devtool: false,
entry: path.resolve(__dirname, "./src/main.js"),
output: {
path: path.resolve(__dirname, "./dist"),
// 必须指定产物的完整路径,否则使用方无法正确加载产物资源
publicPath: `http://localhost:8081/dist/`,
},
plugins: [
new ModuleFederationPlugin({
// MF 应用名称
name: "app1",
// MF 模块入口,可以理解为该应用的资源清单
filename: `remoteEntry.js`,
// 定义应用导出哪些模块
exposes: {
"./utils": "./src/utils",
"./foo": "./src/foo",
},
// 可被共享的依赖模块
shared: ['lodash']
}),
],
// MF 应用资源提供方必须以 http(s) 形式提供服务
// 所以这里需要使用 devServer 提供 http(s) server 能力
devServer: {
port: 8081,
hot: true,
},
};
app2的配置
- 新建
app2目录 npm init -y初始一个package.jsonapp2下新建src目录。src下新建main.js,内容如下
(async () => {
const { sayHello } = await import("RemoteApp/utils");
sayHello();
})();
这里的
app2作为主应用,简单的说明一下import("RemoteApp/utils")。
RemoteApp:是在webpack.config.js中配置的,代码在下面,他指向的是app1中生成的remoteEntry.js:这个文件包含utils.js和foo.js。
utils是app1中webpack.config.js使用ModuleFederationPlugin导出的utils,可以对比一下和修改一下代码尝试一下。
- 新建
webpack.config.js并配置,如下
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
mode: "development",
devtool: false,
entry: path.resolve(__dirname, "./src/main.js"),
output: {
path: path.resolve(__dirname, "./dist"),
},
plugins: [
// 模块使用方也依然使用 ModuleFederationPlugin 插件搭建 MF 环境
new ModuleFederationPlugin({
// 使用 remotes 属性声明远程模块列表
remotes: {
// 地址需要指向导出方生成的应用入口文件
RemoteApp: "app1@http://localhost:8081/dist/remoteEntry.js",
},
shared: ['lodash']
}),
new HtmlWebpackPlugin(),
],
devServer: {
port: 8082,
hot: true,
open: true,
},
};
上面可以看到
ModuleFederationPlugin.shared中有个lodash,这里需要在外层安装lodash的依赖,同时上面提到的lerna初始化完成之后,会自动在package.json中添加依赖的,但是没有安装,也是需要安装的。
全局配置
- 首先安装一个
lodash再说,npm i -D lodash - 然后把
lerna安装一下npm i - 接着在
app1和app2的package.json中添加lodash的依赖,手动添加,不用npm安装。
上面都完成之后就可以在终端中,进入app1下执行npx webpack server,然后再打开一个终端进入app2下执行npx webpack server,app2会自动打开页面,可以打开控制台查看是否输出sayHello。
这里都有热更新,可以尝试修改代码查看效果。
课程里面讲到了依赖共享,我这里略过了,上面的配置是一步到位的,依赖共享我这里没有做记录,个人觉得有一个大概的了解就好了。
微前端
课程中直接新开了一个项目,我这里就继续沿用刚才的,课程中使用的
React,我这里就用vue,目的就是为了说明微前端是跨平台的。
- 根目录下新建一个
vue-app的目录。 npm init -y初始化package.json文件。- 根目录下安装依赖,不是在
vue-app下面安装。 npm i vue(这个是可以共享的)/npm i -D vue-loader babel-loader- 这个vue项目其实可以使用之前创建好的,我这里就拿出之前的配置,简化一点,这里没有
css。 vue-app下新建一个webpack.config.js的配置文件,如下
const path = require('path');
const { VueLoaderPlugin } = require("vue-loader");
const {ModuleFederationPlugin} = require("webpack").container;
module.exports = {
mode: "development",
entry: {
index: './main.js'
},
output: {
path: path.resolve(__dirname, "./dist"),
publicPath: `http://localhost:8083/dist/`,
},
module: {
rules: [
{
test: /.js$/,
use: {
loader: 'babel-loader'
}
},
{
test: /.vue$/,
use: {
loader: 'vue-loader'
}
}
]
},
optimization: {
splitChunks: false,
},
plugins: [
new VueLoaderPlugin(),
new ModuleFederationPlugin({
// MF 应用名称
name: "vueApp",
// MF 模块入口,可以理解为该应用的资源清单
filename: `remoteEntry.js`,
// 定义应用导出哪些模块
exposes: {
"./app": "./main",
},
// 可被共享的依赖模块
shared: ['vue']
}),
],
devServer: {
port: 8083,
hot: true,
},
};
- 根目录下新建
main.js,内容如下
import {createApp} from 'vue'
import App from './src/App.vue'
// 这里的创建函数需要放到一个导出函数里面使用
export const createVueApp = () => {
const app = createApp(App);
app.mount('#app');
return app;
}
- 新建
src目录 src目录下新建App.vue文件,随便写点什么都可以,我的内容如下
<template>
<h1>这里是Vue项目</h1>
</template>
- 修改
app2的webpack.config.js配置
module.exports = {
plugins: [
new ModuleFederationPlugin({
remotes: {
// 地址需要指向导出方生成的应用入口文件
RemoteApp: "app1@http://localhost:8081/dist/remoteEntry.js",
// 这里是新增的vue配置
vueApp: 'vueApp@http://localhost:8083/dist/remoteEntry.js'
},
shared: ['lodash']
}),
]
};
- 修改
app2的src/main.js文件,如下
(async () => {
const {sayHello} = await import("RemoteApp/utils");
const btn1 = document.createElement('button');
btn1.innerText = 'sayHello';
btn1.addEventListener('click', sayHello);
document.body.append(btn1);
const {createVueApp} = await import("vueApp/app");
const btn2 = document.createElement('button');
btn2.innerText = 'vueApp';
btn2.addEventListener('click', createVueApp);
document.body.append(btn2);
const app = document.createElement('div');
app.id = 'app';
document.body.append(app);
})();
这里为啥不是用
html-webpack-plugin去写页面的按钮呢?因为报错了,我找了两个小时没找到原因,只用html-webpack-plugin中指定了template的指向就会报错,感兴趣的可以拿创建微前端的之前的示例去做实验,如果找到问题麻烦在评论区留言,感激不尽。
经过上面的一番操作,这里我们同步启动这三个服务,执行命令npx webpack server
点击sayHello控制台就会有sayHello的输出
点击vueApp页面展现的就是vue-app中写的内容啦。
总结
上面的示例中其实一直都有一个问题,就是css的样式隔离并不能行,而且js的模块加载也只是远程模块加载,会不会带来一些属性的污染?这里我只是学习阶段,了解并不深,需要继续探索。
其次就是课程中内容,写到这里我想说,如果只是想入门看我的笔记,跟着写就够了,其他的就不多说了。
demo地址:MF