传统模块化在利于开发的同时,存在诸多不便,例如:
- 当前端多页面工程场景中使用的组件库更新时,需要编译并上线所有使用到的页面,提高了上线成本,存在部分页面漏发或回滚时漏发带来的风险;
- 当使用CDN资源管理组件库时,CDN资源更新可以实现浏览器端更新,但是服务器端渲染场景node端加载资源并执行无法保证与浏览器端渲染中加载的CDN资源版本一致,会带来渲染差异导致重新渲染问题;
- 当采用打包工具提取公共资源时,打包工具的chunk提取局限于当前构建批次,若一个工程单次只构建部分页面,无法实现工程内所有的公共资源被全部提取,由于各批次构建的公共资源hash值不同,会导致客户端渲染时加载CDN资源的效率大打折扣。
致力于解决以上问题,我们开发了web模块的概念,从架构的层面解决困扰业务的问题。
愿景
基于已知问题,我们的关注点有:
- 实现web模块单独编译上线,亦可单独回滚;
- web模块上线后所有使用模块的工程页面立即生效;
- 支持服务端渲染的同构工程。
在实际业务场景中,我们希望通过web模块扩展解决多页面项目内部UI组件库、工具库(lodash,moment)、请求库(axios)等的跨页面资源共享,全量上线一次,后续更新共享资源只需要单独构建发布web模块扩展即可,发布后通过我们提供的能力达到多页面实时更新共享资源的效果。
另外我们希望业务方使用时引用web模块扩展与引用其它资源语法一致,采用原生的import或require,不引入特殊语法,避免增加业务线的学习成本。
实现思路
此方案基于智联大前端Ada架构之上,可以到 《揭秘智联招聘的大前端架构Ada》了解,下文会直接使用Ada进行描述。
Ada从开发到构建使用的是清单服务进行解析渲染的,构建工具打包会生成一份清单文件,此文件包含本次打包的url及打包资源等信息。
利用清单的约定,我们实现了微前端落地方案Widget,本次设计一样是针对清单进行一些约定实现运行时的动态加载。
首先我们约定了一种新的工件类型,叫做“web模块扩展”,开发时需要写到固定的文件夹内,才会被脚手架识别并编译。
同正常的入口文件一样,写到规定文件夹内的文件会被编译成单独的bundle文件,方便引用这里选择只打包成一个bundle的方式,即js与css最多各只有一个文件。
到这里我们就拿到了web公共模块,并写入到清单文件当中,拿axios举例如图:
接下来就是项目中的axios提取,既然是公共的web资源,所以需要将 import 稍微修改一下,即:
import axios from 'axios' // 原引用
import axios from 'extensions/web-modules/axios' // 新引用
此时需要做的就是把 'extensions/web-modules/axios' 这个路径变成一个动态加载的代码,各打包工具都提供了 Externals,利用这一特性可以自定义排除构建资源的加载方式。
拿webpack举例,简单实现如下:
{
externals: [
({ request }, callback) => {
// ...
if (!matched) return callback() // 未match到web模块扩展路径直接返回
const url = path.posix.join(projectKey, WEB_MODULES_SCOPE, matched.name) // 真实URL路径
const name = target === 'node'
? `ada.webModules.require('${url}')` // 由Ada server内部实现此方法
: url
return callback(null, name, scope)
}
]
}
将静态路径引用变成动态获取,浏览器端使用window全局变量的方式,node端使用自实现require的方式。
如图,编译后的web模块URL,分别对应清单内的web与node属性内的资源,下一步只需要将资源挂载到服务当中就实现闭环了。
浏览器端我们实现了template的模版占位符,我们将web相关的资源挂载到当前请求上下文的ctx当中,使用时:
async function GET (ctx) {
const html = `
...
<script src="${ctx.template.placeholders.webModules.axios.js}"></script>
...
`
ctx.response.set(html, 200)
}
服务端有发布模块提供监听器,当监听到web模块扩展上线时,会拉取最新的清单并将资源文件更新至本地缓存中,这样下次请求拿到的 ctx.template.placeholders.webModules.axios.js 就是替换后的最新资源。
同理,ada.webModules.require 方法是一个node端暴漏到全局的工具方法,里面对服务端的web扩展模块进行了资源加载代理,当资源更新的时候会清除require缓存,重新require最新的js bundle。
这里不深入介绍发布注册及发现的机制,因为更偏向于服务端同步分发的内容,与本文中心想表达的设计思想无关。
到这里web模块扩展机制就完成了,达成了我们设计之初的愿景。
总结
模块化和动态化带来开发便利的同时,也增加了上线带来的影响和风险,毕竟上线会影响所有使用的页面,对开发者的风险把控能力也有一定的门槛。
智联大前端团队发布web模块扩展已有一年多的时间,内部反响比较正面,说明相对比保守大家更愿意节约时间成本和尝试新能力。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。