背景

公司的我的项目古老,采纳的是传统的jsp模式开发页面。短期内没有重构的打算,新性能的新增,是持续采纳jsp的模式开发还是寻求其余形式,是一个令人纠结的问题。
恰好此时,网上微前端的声音越来越响,与之相干的webpack5 module federation也逐步被推广进去。是否利用webpack5的这个新特点,来解决我的窘境呢?

module federation

令我兴奋的特点:

  1. 反对我的项目导出组件供其余我的项目应用(家喻户晓)
  2. 导出的组件,是挂在window下的,能够通过window[container].get([module])获取相应的组件

想法

只在jsp我的项目,引入组件,且执行组件的渲染,具体的业务逻辑代码放在一个独自的,相似“基站”的我的项目。

基站跟我的项目的关系大略如下图:

界面形成如下图:

入手

资料

  1. (提供组件)一个vue我的项目
  2. (应用组件)一个jsp我的项目/一个简略html我的项目

vue我的项目

采纳@efox/emp脚手架搭建,导出了两个组件:
table
button
须要留神的是,因为应用组件者是没有webpack的我的项目,vue我的项目没法通过shared与之共享组件。于是通过cdn的形式引入vue和element-plus,使组件能胜利渲染进去。

vue我的项目的emp-config.js

const withVue3 = require('@efox/emp-vue3')const path = require('path')const ProjectRootPath = path.resolve('./')const { getConfig } = require(path.join(ProjectRootPath, './src/config'))module.exports = withVue3(({ config, env, empEnv }) => {  const confEnv = env === 'production' ? 'prod' : 'dev'  const conf = getConfig(empEnv || confEnv)  const port = conf.port  const publicPath = conf.publicPath  // 设置我的项目URL  config.output.publicPath(publicPath)  config.externals({    'vue': 'Vue',    'element-plus':'ElementPlus'  })  // 设置我的项目端口  config.devServer.port(port)    config.plugin('mf').tap(args => {    console.info(args)    args[0] = {      ...args[0],        name: "wComponent",        // 被近程引入的文件名        filename: 'table.js',        exposes:{          './table':'./src/components/Layout.vue',           './button':'./src/components/Button.vue'        }    }    return args  })  // 配置 index.html  config.plugin('html').tap(args => {    args[0] = {      ...args[0],      ...{        // head 的 title        title: 'jsp-provider',        // 近程调用我的项目的文件链接        files: {          js: ['https://unpkg.com/vue@3.0.7/dist/vue.global.js','https://unpkg.com/element-plus@1.0.2-beta.35/lib/index.full.js'],          css: ['https://unpkg.com/element-plus/lib/theme-chalk/index.css']        },      },    }    return args  })})

应用组件者index.html

在这里,只需引入依赖的vue、element-plus和下面我的项目提供的组件入口http://localhost:8066/table.js,便可应用组件了。

<!DOCTYPE html><html lang="en"><head>  <meta charset="utf-8" />  <meta name="viewport"    content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0" />  <!-- EMP inject title -->  <title>jsp-provider</title>  <!-- EMP inject css -->    <link rel="stylesheet" href="https://unpkg.com/element-plus/lib/theme-chalk/index.css" />  <!-- EMP inject js -->    <script src="https://unpkg.com/vue@3.0.7/dist/vue.global.js"></script>  <script src="https://unpkg.com/element-plus@1.0.2-beta.35/lib/index.full.js"></script>  <link rel="icon" href="http://localhost:8066/favicon.ico">  <script src="http://localhost:8066/table.js"></script></head><body>  <h3>这里是表格</h3>  <div id="emp-root"></div>  <h3>这里是按钮</h3>  <div id="button"></div></body><script defer>  window.wComponent.get('./table').then((factory)=>{    var module = factory();    var app = Vue.createApp(module.default);    app.use(ElementPlus);    app.mount('#emp-root');  })  window.wComponent.get('./button').then((factory)=>{    var module = factory();    var app = Vue.createApp(module.default);    app.use(ElementPlus);    app.mount('#button');  })</script></html>

剖析

module federation (模块联邦)

官网有一个Example给了我启发:

源码中上面这个代码,是间接在window取被共享的组件容器的。那么,非webpack的我的项目,也能够通过同样的形式拿到。

const container = window[scope]; // or get the container somewhere else

查看导出的文件,容器wComponent同过var定义的。

容器会暴露出两个办法

其中get办法能够通过传入模块的名字,获取到一个promise对象

在promise对象中,返回了一个factory,通过执行,失去相应的组件。
上面这个就是咱们在Vue我的项目中导出的table组件

如愿拿到导出的组件,那接下来,就依照Vue组件如何渲染到DOM上实现就能够了。

window.wComponent.get('./table').then((factory)=>{    var module = factory();    var app = Vue.createApp(module.default);    app.use(ElementPlus);    app.mount('#emp-root');  }