微前端

  1. 在大型项目中,往往会把项目中的某个区域或功能模块作为单独的项目开发,最终形成 微前端 架构,在微前端架构中,不同的工程可能出现下面的场景:
  2. 这涉及到很多非常棘手的问题:
    1. 如何避免公共模块重复打包;
    2. 如何将某个项目中一部分模块分享出去,同时还要避免重复打包;
    3. 如何管理依赖的不同版本;
    4. 如何更新模块;
  3. webpack5 尝试着通过 模块联邦 来解决此类问题;

初始化工程

现有两个微前端工程,它们各自独立开发、测试、部署,但它们有一些相同的公共模块,并有一些自己的模块需要分享给其他工程使用,同时又要引入其他工程的模块;

home 项目

shell
json
JavaScript
JavaScript
JavaScript
JavaScript
# 初始化 package.json
npm init -y 

# 安装依赖
npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin
npm i jquery
// 修改 package.json
"scripts": {
  "build": "webpack",
  "dev": "webpack serve"
}
// 配置 webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: './src/index.js',
  mode: 'development',
  devtool: 'source-map',
  devServer: {
    port: 8080,
  },
  output: {
    clean: true,
  },
  plugins: [ new HtmlWebpackPlugin() ]
};
// src/now.js
import $ from 'jquery';

export default function (container) {
  const p = $('<p>').appendTo(container).text(new Date().toLocaleString());
  setInterval(function () {
    p.text(new Date().toLocaleString());
  }, 1000);
}
// src/bootstrap.js
import $ from 'jquery';
import now from './now';

// 生成首页标题
$('<h1>').text('首页').appendTo(document.body);

// 首页中有一个显示当前时间的区域
now($('<div>').appendTo(document.body));
// src/index.js
import('./bootstrap');

active 项目

shell
json
JavaScript
JavaScript
JavaScript
JavaScript
# 初始化 package.json
npm init -y 

# 安装依赖
npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin
npm i jquery
// 修改 package.json
"scripts": {
  "build": "webpack",
  "dev": "webpack serve"
}
// 配置 webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: './src/index.js',
  mode: 'development',
  devtool: 'source-map',
  devServer: {
    port: 3000,
  },
  output: {
    clean: true,
  },
  plugins: [ new HtmlWebpackPlugin() ]
};
// src/news.js
import $ from 'jquery';

export default function (container) {
  const ul = $('<ul>').appendTo(container);
  let html = '';
  for (var i = 1; i <= 20; i++) {
    html += `<li>新闻${i}</li>`;
  }
  ul.html(html);
}
// src/bootstrap.js
import $ from 'jquery';
import news from './news';

// 生成活动页标题
$('<h1>').text('活动页').appendTo(document.body);

// 活动页中有一个新闻列表
news($('<div>').appendTo(document.body));
// src/index.js
import('./bootstrap');

暴露和引用模块

active 项目需要使用 home 项目的 now 模块

  1. home 项目暴露 now 模块

    // webpack.config.js
    const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
    module.exports = {
      plugins: [
        new ModuleFederationPlugin({
          // 模块联邦的名称
          // 该名称将成为一个全部变量,通过该变量将可获取当前联邦的所有暴露模块
          name: 'home',
          // 模块联邦生成的文件名,全部变量将置入到该文件中
          filename: 'home-entry.js',
          // 模块联邦暴露的所有模块
          exposes: {
            // key:相对于模块联邦的路径
            // 这里的 ./now 将决定该模块的访问路径为 home/now
            // value: 模块的具体路径
            './now': './src/now.js',
          },
        }),
      ],
    };
    
  2. active 项目引入 now 模块

    // webpack.config.js
    const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
    module.exports = {
      plugins: [
        new ModuleFederationPlugin({
          // 远程使用其他项目暴露的模块
          remotes: {
            // key: 自定义远程暴露的联邦名
            // 比如为 abc, 则之后引用该联邦的模块则使用 import "abc/模块名"
            // value: 模块联邦名@模块联邦访问地址
            // 远程访问时,将从下面的地址加载
            home: 'home@http://localhost:8080/home-entry.js',
          },
        }),
      ],
    };
    
    // src/bootstrap.js
    // 远程引入时间模块
    import now from 'home/now'
    now($('<div>').appendTo(document.body));
    

home 项目需要使用 active 项目的 news 模块

  1. active 项目暴露 news 模块

    // webpack.config.js
    const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
    module.exports = {
      plugins: [
        new ModuleFederationPlugin({
          // 模块联邦的名称
          // 该名称将成为一个全部变量,通过该变量将可获取当前联邦的所有暴露模块
          name: 'active',
          // 模块联邦生成的文件名,全部变量将置入到该文件中
          filename: 'active-entry.js',
          // 模块联邦暴露的所有模块
          exposes: {
            // key:相对于模块联邦的路径
            // 这里的 ./news 将决定该模块的访问路径为 active/news
            // value: 模块的具体路径
            './news': './src/news.js',
          },
        }),
      ],
    };
    
  2. home 项目引入 news 模块

    // webpack.config.js
    const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
    module.exports = {
      plugins: [
        new ModuleFederationPlugin({
          // 远程使用其他项目暴露的模块
          remotes: {
            // key: 自定义远程暴露的联邦名
            // 比如为 abc, 则之后引用该联邦的模块则使用 import "abc/模块名"
            // value: 模块联邦名@模块联邦访问地址
            // 远程访问时,将从下面的地址加载
            active: 'active@http://localhost:3000/active-entry.js',
          }
        }),
      ],
    };
    
    // src/bootstrap.js
    // 远程引入新闻模块
    import news from 'active/news'
    news($('<div>').appendTo(document.body));
    

处理共享模块

两个项目均使用了 jquery ,为了避免重复,可以同时为双方使用 shared 配置共享模块

const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      // 配置共享模块
      shared: {
        // jquery为共享模块
        jquery: {
          singleton: true, // 全局唯一
        },
      },
    }),
  ],
};
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 微前端
  2. 2. 初始化工程
    1. 2.1. home 项目
    2. 2.2. active 项目
  3. 3. 暴露和引用模块
    1. 3.1. active 项目需要使用 home 项目的 now 模块
    2. 3.2. home 项目需要使用 active 项目的 news 模块
  4. 4. 处理共享模块