背景
公司的项目陈旧,采用的是传统的jsp形式开发页面。短期内没有重构的计划,新功能的新增,是继续采用jsp的形式开发还是寻求其他方式,是一个令人纠结的问题。
恰好此时,网上微前端的声音越来越响,与之相关的webpack5 module federation也逐渐被推广出来。能否利用webpack5的这个新特点,来解决我的困境呢?
module federation
令我兴奋的特点:
- 支持项目导出组件供其他项目使用(众所周知)
- 导出的组件,是挂在window下的,可以通过
window[container].get([module])
获取相应的组件
想法
只在jsp项目,引入组件,且执行组件的渲染,具体的业务逻辑代码放在一个单独的,类似“基站”的项目。
基站跟项目的关系大概如下图:
界面构成如下图:
动手
材料
- (提供组件)一个vue项目
- (使用组件)一个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');
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。