作者:王威(地谦)
文章构造
- 我的项目背景
- 演进剖析
- monorepo架构演进
- Webpack与Rollup
- 如何平滑迁徙
- 构建优化
- 组件的可扩大与可插拔
- 演进总结
- 版本动静
我的项目背景
SREWorks是一个面向企业级简单业务的开源云原生数智运维平台,是大数据SRE团队多年工程实际的锻炼及积淀。前端对立托管工程(frontend)作为平台的重要一环,提供了一套serverless体验的配置化前端低代码技术计划:低代码、配置化是前端低代码计划的根底个性。
frontend工程采纳React+antd为主的技术框架,设计了一套组件映射、编排、解析、渲染的工程体系:以antd组件为自在编辑粒度,用户在前端设计器通过可视化交互或者json编辑的形式,根据运维工作的理论应用场景,对组件进行属性配置/组件嵌套拼装;同时依据应用景指标需要对页面组件进行布局的编排、数据源的绑定以及在适合点位插入Dynamic Logic,实现页面节点的设计工作,造成节点模型nodeModel,经模板解析引擎进行解析渲染。因为之前已对架构设计做过一篇具体的介绍,在此不再赘述,详情可移步这一篇 https://mp.weixin.qq.com/s/_k...
咱们开源这套前端工程的愿景是:积淀更多的应用场景,整合更多的用户需要,与社区一起共建一个丰盛的前端运维组件生态。在过来的半年中,为了让前端组件生态更好地演进,frontend 针对 “可扩大、不便插拔”这两个关键点进行架构降级:
- 架构层面进行了monorepo模式重构;
- 前端组件反对近程动静加载;
在文本中,咱们对整个迭代过程中陆陆续续碰到过一些问题,以及技术计划的抉择与思考做一个阶段性的演绎和总结。
演进剖析
关注咱们开源动静的同学应该晓得,咱们初版开源的frontend代码量近10万之多,在没有牢靠详实文档的帮忙之下,想要疾速理分明整套工程的设计理念,构造机理进而能参加奉献还是有肯定难度的。同时工程外部细分来看,设计器,模型层,组件层各自又有相差很大的更新频率,在开发时一个小小的改变都须要整个工程进行构建。
另外一方面,frontend来源于公司外部工程实际的版本,两者尽管同源,然而因为公司外部和开源场景都在疾速性能演进,最后设计的公共框架层和组件层共享机制曾经有些举步维艰,这也为后续这次演进迭代埋下了伏笔。联合咱们打造开源生态“可扩大、不便插拔”两大指标,综合来看须要解决以下问题:
- 基座层面反对runtime近程组件的加载,解决用户的多样化的场景与诉求
- 构造上对大而全的工程进行细粒度拆分
- 提取framework框架层和widget组件层并可能独自构建
- share-tools工具可共享
- 外部业务代码剥离
- 在适配外部业务运行的前提下降级各依赖版本,便于新技术引入与演进
- 构建工具的降级与配置调优,无效晋升构建效率升高构建体积
针对以上待解决的问题,对演进计划进行了技术调研,也参考和驳回了社区同学的倡议,咱们决定采取上面两个计划来解决上文提到的问题:
- 架构层面,采纳monorepo模式进行重构提取出framework框架层、widget组件层、shared-tools等几个子依赖包,以webpack5(主利用包)+rollup(子依赖包)作为构建工具进行过调优构建;
- 针对无奈进入代码库的组件,提供近程组件脚手架,反对将近程组件打包umd格局并以动静script标签模式进行动静引入和移除,做到runtime加载扩大;
上面咱们来具体分享下这两项计划的施行:
Monorepo架构演进
Monorepo即单仓(repository)多包(package),大型前端工程项目采纳这种模式进行开发治理,能带来诸多的开发和治理便当:
- 更加清晰的模块构造和依赖关系
- 更细粒度的独立构建单元便于合作开发和不同更新频率的子包独自发版
- 更加高效的代码复用等
在v1.4版本中采纳lerna + yarn workspace 的技术计划进行了Monorepo的架构实际:将原工程拆分为@sreworks/app主包利用,和@sreworks/components、@sreworks/widgets、@sreworks/framework、@sreworks/shared-utils四个npm子依赖包。目录构造变动如下图所示:
通过lerna+yarnworkspace的计划,将各子包配置进入workspace空间,workspace空间的各子依赖包的更新,会实时同步到主利用包的node_module,无需公布npm,且能抉择针对特定子包独自公布npm版本或者各个包同步公布新的版本,能够更小粒度的更新主利用依赖,便捷高效。
Webpack与Rollup
在设计好子包的拆分之后,就开始着手进行文件构造的革新、组件引入挂载形式的变更、近程组件加载的解决优化,主题式样的迁徙等等问题(因为篇幅无限,本文仅对大的通用环节进行介绍,对解决细节感兴趣或者想探讨沟通的同学能够退出文末的交换群)。
在解决完工程构造代码后,咱们开始着手工程的构建:构建工具的抉择,对于构建工具,webpack,gulp,rollup以及起初的vite,综合咱们理论的状况,最初选定了Webpack和Rollup作为备选计划:Webpack和Rollup实质都是对非ES5代码的本义与打包,一个功能强大的compiler函数,通过配置入口读取指标文件,而后输入本义文件;要实现整个工程的打包,还须要babel-loader,React和Vue等loader的解决和一系列plugin的适时挂载解决能力实现对诸如图片,css文件、JSX及Vue template等类型文件的解决,及js挂载html的工作。
Webpack与Rollup的特点:
依据以上特点比照以及参考业内优良开源我的项目实际,frontend抉择了主包利用应用Webpack5作为构建工具,Rollup作为子包利用构建工具的计划,主包利用HMR对于日常开发而言是刚需,因而抉择Webapck;对子包依赖而言,更便捷的配置和更小的输入才是更佳的抉择。
如何平滑迁徙
整个这么大的工程体量,在没有齐全进行代码层面准确无误的拆解并构建的状况下,是跑不起来的,一个很小的谬误都会造成整个我的项目抛错。且二方包应用了sourcemap也是没有用的,通过了主包构建,很难排查出哪里除了问题,于是就又要推倒重来……在开始的实时过程中,消耗了很多的工夫,叠加每个子包的批改排查,主利用包的构建等验证周期很长,探索性革新的难点就在于此。
那么能更小粒度的验证和迁徙吗?近程组件的加载给了启发思路。尝试性将组件包@SREWorks/widgets打包成esm格局并在原来大而全的工程中间接批改node_modlues引入依赖包打包文件, 和相应加载机制,在能运行起来的工程下来验证各子依赖包,配合sourcemap, 问题排查霎时提速。就这样,又顺次进行@SREWorks/framework等其余包的验证,蒙眼构建排查问题得解。
式样问题比拟头疼,在此采纳的计划是通用式样在主包保留,子包因为式样重置笼罩的场景较少,采纳了css-module的形式进行隔离构建。
构建优化
通过各子依赖包在原有工程上进行平滑验证,来到主利用包的构建环节,构建体积居然达到了惊人的5.5M,还是gzip压缩后的体积:
通过剖析这张图,该版本构建存在以下问题:
- 同名依赖屡次呈现,各子依赖包存在反复的依赖
- 局部依赖包构建体积偏大,如BizCharts
针对以上存在的问题对@sreworks/app整体进行三个维度的优化解决:
第一,通过对立子包依赖排查合并依赖版本,优化至2.8M
const namespace = { appRoot: path.resolve('src'), appAssets: path.resolve('src/assets'), // 缩小子依赖包外部反复依赖 '@ant-design': path.resolve(process.cwd(), 'node_modules', '@ant-design'), 'js-yaml': path.resolve(process.cwd(), 'node_modules', 'js-yaml'), 'ace-builds': path.resolve(process.cwd(), 'node_modules', 'ace-builds'), 'brace': path.resolve(process.cwd(), 'node_modules', 'brace'), 'lodash': path.resolve(process.cwd(), 'node_modules', 'lodash')}... resolve: { alias: paths.namespace, modules: ['node_modules'], extensions: ['.json', '.js', '.jsx', '.less', 'scss'], },
第二,抽离局部大依赖包到cdn,如下在externals配置项进行剥离;将体积优化至1.6M。但思考到某些专有云应用场景,无奈应用内部cdn。于是采纳自定义构建脚本,从node_modules中迁徙指标依赖到输入文件夹并加载至html的计划,升高参加构建的大依赖包数量,同时保障专有云环境对其失常的应用。
externals: { // 剥离局部依赖,不参加打包 'react': 'React', 'react-dom': 'ReactDOM', "antd":"antd", ... },
第三,调整要害组件门路和1.48M, 缩小体积70%,构建工夫由V1.3版本的74秒,优化至23秒,晋升68%。
组件的可扩大与可插拔
尽管frontend已内置运维场景罕用的根底组件,图表组件,landing组件,布局组件等五十余个组件。依据开源之后用户的应用反馈来看,用户依然有着定制化,可扩大的共性诉求:总的来讲大抵分为两大类:
- 前端框架也是React,有本人定制化的应用场景,内置组件不能满足以后需要,须要扩大
- 前端技术栈是Vue,历史组件积淀比拟多,全副进行React重构老本太大
针对问题一,frontend原本就有提供JSXRender,反对用户以JSX进行简略的动态渲染类的组件自定义扩大,但不反对属性配置以及数据源及dynamic业务逻辑解决等高级个性。前端插件化,很容易想到npm包的引入,然而这也只能在工程代码开发的场景下才适配,要runtime应用和移除,就要另寻计划。再进一步深刻追溯,前端开发从jQuery时代倒退到当今的Agular,React,Vue三驾马车以及各种工程化构建工具的参加,但实质其实并没有发变动,仍然是以html中以script标签挂载js代码进行渲染加载的。因而天然想到以script标签的模式加载咱们的近程组件,不过这里要做到动静、批量加载、可移除,即:将近程组件打包umd格局并公布到云端,并获取相应精确门路,以动静script标签的模式引入:
(function (){ let script = document.createElement('script') script.type = 'text/javascript' script.src = url // 指标组件url document.getElementById('targetDomId').appendChild(script)})()script.addEventListener('load',callback,false) // 嵌入逻辑
如果要批量加载多个的话,即:
const loadRemoteComp = async () => { let remoteCompList = ["url_a","url_b","url_c",...]; try { remoteComList.forEach(item => { pros.push(Promise.resolve(loadSingleComp()); }) window['REMOTE_COMP_LIST'] = await Promise.all(pros); } catch (error) { console.log(error); }}loadRemoteComp()
当然这里还波及到浏览器终端适配,容错等细节。在此frontend采纳比拟成熟的systemjs包进行组件的加载,对以上细节都有做妥善处理,正当借力,省时高效。
针对问题二, 技术栈不同,即异构组件的加载。目前来说frontend暂且只针对Vue组件做了异构兼容渲染,在React中应用Vue组件。一开始想到的是应用转换工具,将vue组件手动转换为React组件,之后再粘贴构建,但这种形式有个很大的缺点:不同版本api差别较大,手动转码个别须要对转换过后的代码进行人工二次排查调整,须要开发人员对于两种框架的新老版本属性相熟理解,对于不合乎的代码或已更新的hooks等进行二次确认,无形中进步了应用门槛。
受到Docker容器的启发,思考React和Vue尽管属于不同的技术栈体系,但区别于Java和Golang的差别,Vue和React在实质上都是原生js对象的封装,所以实践上讲是能够在React中进行容器化渲染Vue组件的:即实质是绑定挂载Vue对象的操作:
createVueInstance (targetElement, reactThisBinding) { const { component, on, ...props } = reactThisBinding.props reactThisBinding.vueInstance = new Vue({ el: targetElement, data: props, ...config.vueInstanceOptions, render (createElement) { return createElement( VUE_COMPONENT_NAME, { props: this.$data, on, }, [wrapReactChildren(createElement, this.children)] ) }, components: { [VUE_COMPONENT_NAME]: component, 'vuera-internal-react-wrapper': ReactWrapper, }, })}
通过frontend近程组件脚手架@sreworks/widget-cli,对React组件和Vue组件进行打包并公布到cdn,而后在物料开发处,进行编辑即可便捷进行近程组件runtime加载和移除,解决了问题一和问题二,达成了“扩展性”和“可插拔”的指标。
演进总结
到这里,根本解决了开篇列举的一系列问题,为构建前端运维组件生态铺设好了共建门路,能够做到:
- 从@sreworks/widgets包开发,JSXRender自定义组件,应用@sreworks/widget-cli开发近程组件三个维度扩大组件利用丰盛度
- 更加清晰的构造依赖关系,升高学习和奉献参加这套低代码工程的门槛
- 以更小粒度的更新单元,更短的构建工夫,便捷日常合作开发
- 子包拆分后,为后续小规模分步引入TS提供了条件
版本动静
咱们会依据工作我的项目节奏,继续对性能进行欠缺优化和降级,以后次要是前端低代码性能的输入,后续API低代码编辑编排已纳入版本布局,以笼罩全链路低代码应用,大家有比拟好的倡议欢送多提issue,同时也欢送更多的开发者可能参加到咱们的生态建设中来(@小助手,也能够间接前端@地谦)
SREWorks开源地址:
https://github.com/alibaba/sr...