webpack5 Module Federation “微前端”vue版demo使用示例及实现细节

Module Federation:是webpack5新出的一种“微前端”的概念,此文介绍一下具体的实际操作 vue版

demo内关系介绍:

关系:app1对外暴露“微服务”,app2使用app1的“微服务”

Module-Federation.png

上代码

目录结构

  1. .
  2. ├── README.md
  3. ├── app1 // app1对外暴露“微服务”
  4. ├── package.json
  5. ├── public
  6. └── index.html
  7. ├── src
  8. ├── App.vue
  9. ├── components
  10. └── Header.vue
  11. └── main.js
  12. └── webpack.config.js
  13. └── app2 // app2使用app1的“微服务”
  14. ├── package.json
  15. ├── public
  16. └── index.html
  17. ├── src
  18. ├── App.vue
  19. └── main.js
  20. └── webpack.config.js

app1和app2相同部分代码(环境)

  • package.json相同
  • src/main.js相同
  • public/index.html相同
  1. // package.json
  2. {
  3. "scripts": {
  4. "start": "webpack serve",
  5. "build": "webpack"
  6. },
  7. "devDependencies": {
  8. "vue-loader": "^15.9.3",
  9. "vue-template-compiler": "^2.6.12",
  10. "@babel/core": "^7.14.3",
  11. "babel-loader": "^8.2.2",
  12. "html-webpack-plugin": "^5.3.1",
  13. "webpack": "^5.38.1",
  14. "webpack-cli": "^4.7.2",
  15. "webpack-dev-server": "^3.11.2"
  16. },
  17. "dependencies": {
  18. "vue": "^2.6.12"
  19. }
  20. }
  21. // src/main.js
  22. import Vue from "vue";
  23. import App from "./App.vue";
  24. new Vue({
  25. el: '#app',
  26. render: h => h(App)
  27. })
  28. // public/index.html
  29. <html>
  30. <head></head>
  31. <body>
  32. <div id="app"></div>
  33. </body>
  34. </html>

app1 的组件代码

  • App.vue
  • components/Header.vue
  1. // App.vue
  2. <template>
  3. <div id="app" style="border: 1px solid cornflowerblue">
  4. <h2>我是app.vue</h2>
  5. <Header name="app1"/>
  6. </div>
  7. </template>
  8. <script>
  9. import Header from './components/Header.vue'
  10. export default {
  11. components: {
  12. Header
  13. }
  14. }
  15. </script>
  16. // components/Header.vue
  17. <template>
  18. <div style="border: 1px solid olivedrab">
  19. <h4>我是header.vue 这是传参:{{name}}</h4>
  20. </div>
  21. </template>
  22. <script>
  23. export default {
  24. props: {
  25. name: {
  26. type: String,
  27. default: ''
  28. }
  29. }
  30. }
  31. </script>

app2 的组件代码

  • App.vue 内 使用了app1的“微服务”
  1. // App.vue
  2. <template>
  3. <div id="app">
  4. <Header name="app222222"/> // 此处 app2使用app1的“微服务”
  5. <br>
  6. <qwe></qwe> // 此处 app2使用app1的“微服务”
  7. </div>
  8. </template>
  9. <script>
  10. export default {
  11. components: {
  12. Header: () => import('app1/Header'), // 此处 app2使用app1的“微服务”
  13. qwe: () => import('app1/appIndex') // 此处 app2使用app1的“微服务”
  14. }
  15. }
  16. </script>

实现关键:webpack.config.js

app1的webpack.config.js (对外暴露“微服务”)

  1. const path = require('path');
  2. const VueLoaderPlugin = require('vue-loader/lib/plugin')
  3. const HTMLWebpackPlugin = require('html-webpack-plugin');
  4. const { ModuleFederationPlugin } = require("webpack").container;
  5. module.exports = {
  6. target: 'web',
  7. entry: './src/main.js',
  8. mode: "development",
  9. devServer: {
  10. port: 3000,
  11. hot: true,
  12. open: true,
  13. contentBase: path.join(__dirname, "dist"),
  14. },
  15. module: {
  16. rules: [
  17. {
  18. test: /.js$/,
  19. loader: 'babel-loader',
  20. exclude: /node_modules/
  21. },
  22. {
  23. test: /.vue$/,
  24. loader: 'vue-loader'
  25. }
  26. ]
  27. },
  28. plugins: [
  29. // 请确保引入这个插件!
  30. new VueLoaderPlugin(),
  31. new HTMLWebpackPlugin({
  32. template: path.resolve(__dirname, './public/index.html')
  33. }),
  34. new ModuleFederationPlugin({
  35. // 提供给其他服务加载的文件
  36. filename: "remoteEntry.js",
  37. // 唯一ID,用于标记当前服务
  38. name: "app1",
  39. library: { type: "var", name: "app1" },
  40. // 需要暴露的模块,使用时通过 `${name}/${expose}` 引入
  41. exposes: {
  42. './Header': "./src/components/Header.vue", // app1对外暴露“微服务”Header(组件)
  43. './appIndex': "./src/App.vue", // app1对外暴露“微服务”appIndex(组件)
  44. }
  45. })
  46. ]
  47. }

app2的webpack.config.js (接受app1的“微服务”(组件))

  1. const path = require('path');
  2. const VueLoaderPlugin = require('vue-loader/lib/plugin')
  3. const HTMLWebpackPlugin = require('html-webpack-plugin');
  4. const { ModuleFederationPlugin } = require("webpack").container;
  5. module.exports = {
  6. target: 'web',
  7. entry: './src/main.js',
  8. mode: "development",
  9. devServer: {
  10. port: 3001,
  11. hot: true,
  12. open: true,
  13. contentBase: path.join(__dirname, "dist"),
  14. },
  15. module: {
  16. rules: [
  17. {
  18. test: /.js$/,
  19. loader: 'babel-loader',
  20. exclude: /node_modules/
  21. },
  22. {
  23. test: /.vue$/,
  24. loader: 'vue-loader'
  25. }
  26. ]
  27. },
  28. plugins: [
  29. // 请确保引入这个插件!
  30. new VueLoaderPlugin(),
  31. new HTMLWebpackPlugin({
  32. template: path.resolve(__dirname, './public/index.html')
  33. }),
  34. new ModuleFederationPlugin({
  35. name: "app2",
  36. remotes: {
  37. app1: "app1@http://localhost:3000/remoteEntry.js", // (接受app1的“微服务”),remoteEntry可以理解为中间代理人)
  38. }
  39. })
  40. ]
  41. }

效果展示

app1要先npm run start起来

然后app2 才能 npm run start起来

Module-Federation-display.png

可以看到app2内,可以使用app1的对外暴露的“微服务”(组件)

实现细节

(这里面对比上面的代码,多加了moment.js库依赖)
微服务入口.png

此图中: 端口3001是当前服务,使用了端口3000的微服务

细节解析:

  1. 3001服务引入微服务入口 remoteEntry.js,通过remoteEntry.js去获取对应的依赖
  2. 对应的依赖有:组件和第三方库
    • 每一个组件都会是一个独立的文件,然后第三方依赖也会单独拆出来,防止重复打包
    • 上图中
      • 倒数的两个资源都是用了moment方法的组件,也被3000对外暴露微服务了。此处作为组件引入3001服务内
      • 倒数第三个是 第三方依赖 目前主要是moment.js (此处应当按需加载优化,不应该整个moment.js都加载进来了)

另外还做了一个测试:3000的微服务做了变更后,3001服务不用变动,强制刷新就能生效

  • 因为,这个是通过域名,获取到对应的资源。相当于直接拉服务器的静态资源

码字不易,点赞鼓励