用webpack的模块联盟构建微前端

611 阅读6分钟

现在有很好的框架可以帮助我们创建微前端,如single-spa和OpenComponents。但如果我们不想在我们的架构中整合不同的框架呢?让我们来看看如何在我们的Vue应用程序中实现微前端。

在这篇文章中,我们将讨论如何通过使用webpack 5的模块联盟来消费和分享微前端组件的示例代码,从而极大地简化我们的应用架构。

什么是模块联盟?

模块联盟是一个由Zack Jackson发明的JavaScript架构。这个架构允许两个不同的应用程序代码库之间共享代码和依赖关系。

代码是动态加载的,如果缺少一个依赖项,该依赖项将由主应用程序下载,这使得应用程序中的代码重复性减少。

什么是微前端?

近来,微前端的概念越来越受到重视。对微服务的推动也给现代网络带来了同样的实现,即微前端的形式。随着单体应用的扩展,它变得难以维护,特别是在同一应用的几个团队之间。

我们可以把微前端看成是基于功能的,这里有不同的团队,每个团队处理一个特定的功能组件,而另一个团队处理其他的东西。最后,所有的团队将他们构建的不同组件合并起来,形成一个应用程序。

开发人员利用single-spaOpenComponents等框架来实现这一目标,但随着webpack 5和模块联盟的新发布,我们可以很容易地实现同样的目标,但更容易。

微前端的优势

采用微前端的方法来构建你的网络应用可能是最好的策略。如果你正在构建一个有许多移动部件的大规模Web应用,或者是那些被分支成子应用的应用,你希望在整体外观上有一些一致性,那么这一点尤其正确。

让我强调一下你可能想改用微前端方法的几个原因。

  • 采用微观前台的方法将使我们能够创建一个端到端的功能架构。这种方法将使我们能够在本地开发和部署功能,而不需要大规模的部署基础设施
  • 通过更小、更优化的捆绑规模,微前端提供了整体上更好的开发者和用户体验,因为共享组件和依赖关系可以在我们想要的时候偷懒加载
  • 对我来说,最大的优势之一是,从事该特定产品的开发者团队能够选择自己喜欢的技术栈,而不用担心与其他团队的代码不兼容。

我们如何分割我们的应用程序?

这些是开发人员分割大型应用程序的一些方法。

  1. 按页面--在我们的前端应用程序中,有时在浏览器中同时运行不同的页面会导致旧设备的崩溃,所以最安全的方法是按页面分割。如果你有很好的路由,你可以为每一个页面运行单独的、特定的微应用程序,这对你团队中的开发者来说也很好,因为他们总是在分配给他们的那个页面上工作。
  2. 按功能--如果你有一个页面有多个事情功能,执行不同的操作,你可以将这些大的功能分成小的应用程序,使其成为一个独立的应用程序,运行该特定功能
  3. 按部分划分 - 你也可以按部分划分你的应用程序,不同的应用程序共享同一个部分或组件

概念证明

我们已经解释了关于微前端和模块联合的一些概念。现在是进行概念验证的时候了。

在这里,我们将演示如何使用模块联盟来创建Vue的微前端。为了测试这一点,我们将启动两个不同的应用程序,因此我们可以在其中一个中创建一个微前端,并与另一个共享。

首先,我们创建一个文件夹来托管这两个Vue应用程序。

mkdir vue-mf 

就是在vue-mf 文件夹中,我们将运行我们的Vue应用程序。我们不会在这里使用Vue CLI。相反,我们将使用webpack的新版本,也就是webpack 5,来设置Vue应用程序。

我们将把我们要共享组件的两个应用程序分别命名为CompanyShop 。我们在vue-mf 文件夹中为它们各创建一个文件夹,然后从GitHub中抓取Jherr创建的Vue的webpack启动文件到每个文件夹中。

git clone https://github.com/jherr/wp5-starter-vue-3.git

现在我们来看看文件结构,我们已经设置了应用程序。

+-- vue-mf/
|   +-- Company/
|   +-- Shops/

当我们打开其中一个应用文件夹时,结构是这样的。

+-- vue-mf/
|   +-- Company/
|       +-- src/
|           +-- App.vue
|           +-- bootloader.js
|           +-- index.css
|           +-- index.html
|           +-- index.js
|       +-- package.json
|       +-- webpack.config.js
|   +-- Shops/

设置webpack配置

因此,我们有两个应用程序,CompanyShop ,它们现在是完全一样的。当我们调查文件结构时,我们看一下package.json 。我们有我们的webpack加载器,CSS加载器,以及所有我们需要的基本加载器和webpack东西。

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const { VueLoaderPlugin } = require("vue-loader");
module.exports = {
  output: {
    publicPath: "http://localhost:8080/",
  },
  resolve: {
    extensions: [".vue", ".jsx", ".js", ".json"],
  },
  devServer: {
    port: 8080,
  },
  module: {
    rules: [
      {
        test: /.vue$/,
        loader: "vue-loader",
      },
      {
        test: /.css$/i,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new ModuleFederationPlugin({
      name: "starter",
      filename: "remoteEntry.js",
      remotes: {},
      exposes: {},
      shared: require("./package.json").dependencies,
    }),
    new HtmlWebPackPlugin({
      template: "./src/index.html",
    }),
  ],
};

如果我们看一下webpack.config.js 文件,我们可以看到,我们的公共路径被设置为端口8080 。我们还可以看到webpack在检查我们的文件扩展名,并使用适当的加载器。

这里需要注意的是我们在解析文件时使用的Vue加载器插件,以及我们从webpack 5中导入并使用的Module federation插件,这将使我们能够执行共享功能。我们将在本教程的后面回到ModuleFederationPlugin 的配置问题。

注意,确保将另一个应用程序(商店)的公共路径和开发服务器端口设置为8081 ,这样我们就能同时运行两个应用程序。

创建要共享的组件

在我们的应用程序中,App.vue 文件将作为主页,所以让我们添加一些标记。

<template>
 <div>
     <h1>Our Application Homepage</h1>
 </div>
</template>

头部组件是我们想在应用程序之间共享的一个部分。假设其中一个团队的开发人员决定建立页眉,那么我们就创建一个header 组件,在两个应用程序中共享。

在我们公司应用程序的src 文件夹中,我们将创建一个header 组件。要做到这一点,我们创建一个Header.vue 文件,并在其中创建header 组件。

<template>
  <div>
      <header>
          App Header
      </header>
  </div>
</template>

创建头后,导航到App.vue ,导入header 组件。

<template>
 <div>
     <Header />
     <h1>Our Application Homepage</h1>
 </div>
</template>
<script>
import Header from './Header.vue';
export default {
 components: {
     Header,
 },   
}
</script>

现在我们可以通过导航到每个文件夹并运行来启动我们的开发服务器。

yarn && yarn start

现在,我们的应用程序在公司应用程序中看起来是这样的。

Sample App for a Vue Micro-Frontend

通过模块联盟插件暴露header 组件

我们现在在公司应用程序中拥有我们的标题,我们想在商店应用程序中使用它。因此,我们前往公司应用程序中的webpack配置。

 plugins: [
    new VueLoaderPlugin(),
    new ModuleFederationPlugin({
      name: "Company",
      filename: "remoteEntry.js",
      remotes: {},
      exposes: {
        "./Header": "./src/Header",
      },
      shared: require("./package.json").dependencies,
    }),
    new HtmlWebPackPlugin({
      template: "./src/index.html",
    }),
  ],

在webpack模块联盟配置中,我们将name 设置为应用程序的名称,也就是Company ,并将remoteEntry.js 设置为我们的文件名。当我们导航到remoteEntry.js 文件名时,我们看到了与我们想要共享的组件和依赖关系相关的代码。我们还暴露了header 组件的位置。

现在,如果我们重新启动我们的服务器并导航到 [http://localhost:8080/remoteEntry.js](http://localhost:8080/remoteEntry.js),我们会看到这个。

remoteentry.js Example for Vue Micro-Frontend

现在抓取远程入口URL,并切换到我们Shop应用中的webpack配置文件。

plugins: [
    new VueLoaderPlugin(),
    new ModuleFederationPlugin({
      name: "Shop",
      filename: "remoteEntry.js",
      remotes: {
        Company: "Company@http://localhost:8080/remoteEntry.js"
      },
      exposes: {},
      shared: require("./package.json").dependencies,
    }),
    new HtmlWebPackPlugin({
      template: "./src/index.html",
    }),
  ],

在这里,我们给这个插件取名为Shop ,并将远程设置为remoteEntry URL。然后在我们商店应用程序的App.vue 文件中,我们从我们的公司应用程序中导入并使用header 组件。

<template>
<div>
    <Header />
    <h2>Our Shop Page</h2>
</div>
</template>

<script>
import Header from 'Company/Header';
export default {
 components: {
     Header,
 },   
}
</script>

如果我们重新启动我们的服务器,我们可以看到商店页面现在有标题组件,这意味着我们已经成功地在两个应用程序之间共享组件。耶!

注意,如果处理头的团队决定为header 组件推送一个新的更新,一旦商店应用被刷新,商店应用团队将立即看到该更新。

在联盟模块之间共享应用状态

假设你在你的Vue应用中使用一个状态管理器,比如Vuex。你可能会问自己,你怎么可能有状态,在两个组件之间分享它,还能让它更新。所以,让我们为两个应用程序安装Vuex。

yarn install vuex@next

一旦我们安装了Vuex,导航到我们公司应用中的bootloader.js 文件,在那里我们初始化我们的Vue应用。
在这里,我们导入我们的商店并创建一个状态。

import { createApp } from "vue";
import { createStore } from 'vuex'
import "./index.css";
import App from "./App.vue";
const app = createApp(App)
const store = createStore({
    state () {
      return {
        cartItems: 0
      }
    }
  })
app.use(store)
app.mount("#app");

例如,如果这是一个电子商务商店,我们想显示我们在购物车中的商品数量,我们创建一个cartItems 状态,并在我们的公司标题中显示。然后进入我们的header 组件,访问该状态,并显示它。

<template>
  <div>
      <header>
          <h2> App Header</h2>
          <p>items: {{cartCount}}</p>
      </header>
  </div>
</template>
<script>
export default {
    computed: {
        cartCount() {
            return this.$store.state.cartItems
        }
    },
}
</script>f

我们已经成功地设置了我们的状态,但问题是如果我们启动两个应用程序的服务器,并检查公司应用程序,我们可以看到标题显示了状态。

Header with State for Vue Micro-Frontend

但如果我们导航到商店应用程序,我们不再能看到共享的header 组件,更不用说我们添加的状态。相反,我们得到一个错误信息,说我们不能读取未定义的状态,因为在我们的商店应用程序中,我们还没有设置任何商店。

为了纠正这个问题,我们复制我们在公司应用的引导程序中的所有代码,并将其粘贴到商店应用的bootloader.js 文件中。这一次,我们把cartCount 的状态改为12 。如果我们重新启动服务器,我们现在在商店应用程序中拥有我们的头,购物车项目为12

假设我们想模仿在购物车中增加更多的商店项目,所以在商店应用程序中,我们添加一个按钮,增加cartCount 状态。

<template>
<div>
    <Header />
    <h2>Our Shop Page</h2>
    <div>
        <button @click="addItem">Add item</button>
    </div>
</div>
</template>

<script>
import Header from 'Company/Header';
export default {
 components: {
     Header,
 }, 
 methods: {
         addItem() {
             this.$store.state.cartItems += 1
         }
     },  
}
</script>

如果我们重新启动商店应用程序,我们可以看到,标题中的项目现在已经更新。耶!

资源

总结

我们已经来到了本教程的结尾。

在这里,我们讨论了如何通过使用webpack 5的模块联盟来消费和分享微前端组件的示例代码,从而极大地简化我们的应用架构。

你是否应该采用微前端取决于你正在构建的项目的种类,因为这种方法对小型应用或企业来说不会是最好的。当你在一个有分布式团队的大型项目上工作时,微前端架构方法是你最好的选择。

The postBuilding micro-frontends with webpack's Module Federationappeared first onLogRocket Blog.