共计 7928 个字符,预计需要花费 20 分钟才能阅读完成。
我的项目背景
偏中后盾管理系统或者集成平台的利用入口,常常须要在利用入口进行 个性化的展现。从用户角度来看,反对个性化的定制,能够无效晋升用户的幸福感,晋升应用或工作效率;从产品提供商来看,提供个性化定制服务的能力,也是晋升产品力的一种形式。
如何提供个性化定制能力
-
定制开发(也就是始终被开发者诟病的二次开发)
- 优:疾速响应共性需要
-
劣:
- 开发成本高,阐明本身产品力无限
- 源码管控上的危险
-
凋谢配置项
- 优:无需二次开发,节约老本
-
劣:
- 配置项难形象,粒度难把握,常常有配置项无奈笼罩需要的中央
- Do everything then do nothing.
解决方案
有没有一种形式,既能疾速响应共性需要,也能管制开发成本?这里抛出两个词儿:可视化搭建,Low Code 低代码。
- 可视化搭建:通过在指定配置平台,进行利落拽页面模块,过程可视化,输入个性化页面或模块。
- Low Code 低代码:开发人员通过 大量编码 或 No Code,即可疾速满足共性需要,并将 coding 成绩物利用于配置中。
其实这两个概念比不是陈腐词,远在 Dreamwaver 时代就有影子(返祖现象?),且 Low Code 计划很难来到可视化搭建,相辅相成。在门户“千人千面”的需要中,就采纳了以上的计划。
指标受众
这套计划给谁用?职责怎么划分?
可视化搭建 | Coding 开发 | |
---|---|---|
受众 | 技术支持 / 项目经理 /… | 基线开发人员 / 我的项目开发人员 |
职责 | 利落拽页面 / 个性化配置 | 疾速模块开发 / 定制化开发 |
门户实际计划
在实际落地中,门户采纳的是:将整个页面拆分成各个模块(部件),就像积木搭建一样,可通过不同的排列组合、布局形式,搭建个性化页面。
部件 widget – 可视化搭建最小单元
部件 (widget) 其实就是在这套计划里的“积木”,可灵便摆放,也可共性开发。
当咱们去思考如何拆分整个页面的时候,咱们天然会发现,与前端 组件化 的思维不约而同。任意一个拆分后的模块单元,都有本人的 视图 HTML,交互逻辑 JavaScript,款式 CSS。
一个最根本的部件,就是由 HTML/Javascript/CSS 组成,再加上一些动态资源(如图片 / 语言文件 /…)。
当然,咱们心愿这个部件有一下特点:
- 独立开发:开发者能够在宿主环境(门户)以外进行 Coding 开发
- 独立部署:即能够在任意中央部署,只有能通过地址拜访到该部件资源,即可加载
- 动静加载:用到的时候加载,不必不加载。
如何实现
古代的前端框架(React/Vue/Angular)基本上能做到组件化思维开发,但现实的计划是 framework free,即不受框架束缚。
出于开发成本思考,联合外部前端对立技术栈,门户的部件采纳的是 Vue.js 动静组件 的计划。即一个部件,就是一个 SFC 单文件组件(Single File Component),通过 Webpack 模块化打包工具,将部件源码打包,输入一个资源包。
在宿主环境,通过接口获取部件资源地址,动静解析和执行字符串,再通过 Vue 动静组件的形式进行动静加载(<component>
的 is
attribute)。
<component v-bind:is="widgetActiveComponent"></component>
可视化搭建布局设计
目前门户采纳 xy 坐标系,作为前端页面布局的规定。
如图所示,部件容器左上角的顶点坐标作为该部件定位点。
PS:这里的布局设计其实咱们思考过 栅格布局 ,但各有优劣。坐标布局颗粒度更细,实用于个性化更强的场景。但栅格布局搭建老本较小,效率较高。最终咱们通过加上“ 网格吸附 ”的性能来 优化坐标布局的体验 。 这价值一个亿的代码有须要我能够无偿提供。
计划整体流程
如下图所示,从页面模块形象,通过打包构建部署,最终加载渲染,造成闭环。:)
小结
通过部件利落拽配置(可视化搭建 )和Low Code 低代码开发(利用提供的脚手架疾速开发部件,可独立开发,独立部署,动静加载),实现门户的个性化需要。
技术积淀 – 可视化搭建
这块在门户中的实现不在难,在繁琐。列几个可能会踩坑的点。
1. 画布内利落拽 drag
这里的难点,除了要思考到原生 onmousedown
/onmousemove
/onmouseup
等鼠标事件,阻止事件冒泡,以及一些边界问题,特地是鼠标在不同画布 缩放比例 的状况下的 漂移景象。上面的伪代码中,都会提到。
// 伪代码
<div class="dragItemBox">
<div class="dragCover" @mousemove="drag($event)" @click.stop></div>
</div>
<script>
drag(e) {
e.onmousedown = e => {
// 记录鼠标按下时的坐标
document.onmousemove = e => {
// 记录鼠标挪动时的 xy 坐标差
// 重点来了,这里差值要思考画布的缩放比例,不然在不同的缩放比例下,会造成鼠标漂移哦
// 边界问题,这里能够束缚拖拽的范畴
document.onmouseup = e => {// 将坐标差赋给指标元素,扭转其布局款式}
}
document.onmouseup = e => {// 按下不挪动鼠标,对事件状态进行重置}
}
}
</script>
2. 画布内利落拽 resize
以及对部件进行 resize 的时候,每一个锚点(共 8 个锚点)的逻辑都须要严格形象。举个栗子就懂了。
部件有 width/height/top/left 这四个布局属性,先看锚点 1 和锚点 8,当拖拽 锚点 8 的时候,扭转的只是 width/height 两个属性,而当拖拽 锚点 1 的时候,扭转的却是 width/height/top/left 四个属性,所以每个锚点的逻辑是不同的,怎么形象,怎么封装,看本人施展,这里要特地留心,不然会呈现各种莫名其妙的 漂移问题。
3. 纯 CSS 实现虚点线网格
不是虚线,也不是点线,而是 虚点线 。这名字我本人想的,看下图,在纵横线的 交叉点 会比其余中央要大一点!
看到这个视觉稿,差点掏出了桌子下 40 米的砍刀。这里不是作死,为啥不必图片?次要是思考到这里的方格大小和色彩可调节,所以应该只能用纯 CSS 实现,吧。
感兴趣能够先本人尝试实现下。这里的代码,真的,没 500 块我不卖 :(
波及到的 CSS API:
- linear-gradient()):线性突变
- radial-gradient()):径向突变
- background-size 属性规定背景图像的尺寸。
- background-position 属性设置背景图像的起始地位。
实现合成
- Step.1 条纹
- Step.2 实线网格
- Step.3 虚线网格
- Step.4 虚点线网格
如下为下文图例的 DOM 构造及前置款式。
<style>
.bg {
width: 100px;
height: 100px;
background-color: black;
}
.basegrid {
width: 100%;
height: 100%;
}
</style>
<div class="bg">
<div class="grid basegrid"></div>
</div>
- Step.1 先利用
linear-gradient()
画出条纹。重点:边界显著(失常状况下 linear-gradient 是会产生突变成果的,但这里的条纹须要边界显著,那么就须要前一个渐变色在后一个渐变色地位之后,具体能够参考这篇文章)
- Step.2 网格能够了解为横竖条纹组合而成。重点:background-image 能够设置多个突变叠加,横竖条纹能够各自实现后叠加。
网格线的粗细问题:能够管制 background-size
和linear-gradient
渐变色的差值,来管制网格线的粗细。
- Step.3 虚线的实现能够持续叠加与背景色统一的条纹来叠加。能够用两个突变叠加(横竖条纹)。
这里我想到的是能够用斜条纹来实现虚线,能够节约一个突变叠加。
- Step.4 虚线网格曾经实现了,那么 虚点线 网格怎么搞?拆分!拆分为 点阵 和虚线网格 ,使其两者重合。点阵就用到了radial-gradient()) 径向突变。
两者叠加,Finally !
Low Code 实际
上面会介绍 low code 及微前端的相干内容,过程中会插入门户部件的落地计划(题目前带 *)。从后果来看,low-code 低代码 是后果,条条小道通罗马,但总离不开一些关键点,如上述提到的可视化搭建、模块隔离等;而微前端在模块隔离方面,提供了一些很多值得借鉴的计划。相辅相成,技术的诞生就是为了解决 具体问题。
Low-Code 介绍
A low-code development platform (LCDP) is software that provides a development environment used to create application software through graphical user interfaces and configuration instead of traditional hand-coded computer programming. A low-code model enables developers of varied experience levels to create applications using a visual user interface in combination with model-driven logic.
一句话概括就是:实现低代码平台的要害因素是 模型驱动设计 、 代码主动生成 和可视化编程。
场景
从最早的通过模块化搭建解决营销流动畛域的问题,倒退到当初,能够通过 low-code 来解决外部简单的中后盾业务需要,既实用于面向 C 侧用户的产品经营,也贴合 B 侧用户的数据管理需要。
外围能力
-
根底物料的搭建和接入
- 定制化组件接入
- 自定义组件
-
编排能力:页面排版 / 逻辑编排
- 实时可视化
- 多端适配
- 多场景适配
-
Pro-code 和 low-code 的代码转换能力
- 对输入的编程产物进行二次开发的能力
- 合作能力
- 数据分析能力
二八准则
- 通过 low-code 平台实现对 80% 业务场景的笼罩
- 20% 的能力通过 pro-code 自定义实现
与微前端的交加
为什么会提到微前端?因为在微前端架构中,前端利用能够 独立运行、独立开发、独立部署 。与门户的部件计划 不约而同,当然,咱们在模块隔离的计划中,也参考了 Single-SPA 和 qiankun 等微前端框架。
微前端计划
微前端架构是一种相似于微服务的架构,它将微服务的理念利用于浏览器端,行将 Web 利用由繁多的单体利用转变为多个小型前端利用聚合为一的利用。
形式 | 开发成本 | 保护老本 | 可行性 | 同一框架要求 | 实现难度 |
---|---|---|---|---|---|
路由散发 | 低 | 低 | 高 | 否 | ★ |
iFrame | 低 | 低 | 高 | 否 | ★ |
利用微服务化 | 高 | 低 | 中 | 否 | ★★★★ |
微件化 | 高 | 中 | 低 | 是 | ★★★★★ |
微利用化 | 中 | 中 | 高 | 是 | ★★★ |
纯 Web Components | 高 | 低 | 高 | 否 | ★★ |
联合 Web Components | 高 | 低 | 高 | 否 | ★★ |
微件化 – 组合式集成
在前端组件(或利用)集成计划中,有以下几种形式:
- 独立构建组件和利用,生成 chunk 文件,在构建后应用脚本合并。
- 开发时独立开发组件或利用,集成时合并组件和利用,最初生成单体的利用。
- 在运行时,加载利用的 Runtime,随后加载对应的利用代码和模板。
* 门户部件组合集成计划
门户的部件集成计划相似形式二和三,基于 脚手架工具 独立开发部件,打包构建后输入动态成绩物,在宿主环境能够依据动态资源门路,动静加载 Vue 组件。如果把首页了解成一个利用,那么通过首页主题包整个导出,将部件成绩物集成在整个主题中,就和上述提到的形式二很靠近了。
模块隔离
多个模块(部件)在同一个页面集成,就必须要思考到模块隔离,包含子模块与宿主环境的隔离,以及子模块之间的隔离。
可能导致的问题
- 子模块可能会批改宿主环境的逻辑,从而影响整个零碎的运行,xss 平安问题;
- 子模块之间能够互相拜访且批改,难以定位问题;
- 模块间的款式净化;
利用集成
在微前端的利用集成中,如果要做到利用的独立部署及与技术栈解耦,就不得不思考子利用的 运行时加载。
App Entry | 阐明 | 长处 | 毛病 |
---|---|---|---|
JS Entry | 子利用将资源打成一个 entry script,子利用的所有资源打包到一个 js bundler 里。 | 主利用和子利用独特构建,不便做 bundler 优化。 | 1. 子利用更新会造成整个主利用更新,更新老本较高 2. 子利用的动态资源须要打成 bundler,加载效率较低 |
HTML Entry | 将子利用打进去 HTML 作为入口,主框架能够通过 fetch html 的形式获取子利用的动态资源,同时将 HTML document 作为子节点塞到主框架的容器中。 | 子利用独立开发,独立部署公布; | 1. 网络申请开销较大 2. bundler 构建优化难度较大,如公共依赖抽取 |
js 隔离
JavaScript 沙箱
沙箱机制的外围是让部分的 JavaScript 运行时,对外部对象的拜访和批改处在可控的范畴内,即无论外部怎么运行,都不会影响内部的对象。通常在 Node.js 端能够采纳 `vm` 模块,而对于浏览器,则须要联合 `with` 关键字和 `window.Proxy` 对象来实现浏览器端的沙箱。以下为繁难的实现:
function sandbox(code) {code = 'with (sandbox) {' + code + '}';
const fn = new Function('sandbox', code);
return (sandbox) => {
const proxy = new Proxy(sandbox, {has(target, key) {return true;},
get(target, key, receiver) {if (key === Symbol.unscopables) {return undefined;}
}
});
return fn(proxy);
}
}
let str = 'let a = 10;console.log(a)'
sandbox(str)({})
Web Worker
具体内容可参考文章 浅探 Web Worker 与 JavaScript 沙箱
Web Worker 子线程的模式也是一种人造的沙箱隔离,借鉴了 Browser-VM 思路,在编译阶段通过 Webpack 插件为每个子利用包裹一层创立 Worker 对象的代码,让子利用运行在其对应的单个 Worker 实例中,比方:
__WRAP_WORKER__(`/* 打包代码 */}`);
function __WRAP_WORKER__(appCode) {var blob = new Blob([appCode]);
var appWorker = new Worker(window.URL.createObjectURL(blob));
}
但有几个缺点:
- 不反对
DOM
操作,必须通过postMessage
告诉 UI 主线程来实现 - 无法访问
window
、document
之类的浏览器全局对象 - 无法访问页面全局变量和函数、无奈调用
alert
、confirm
等BOM API
Module Federation – Webpack 5
webpack5 – Module Federation 中文文档 中是这么定义的:多个独立的构建能够造成一个应用程序。这些独立的构建不会相互依赖,因而能够独自开发和部署它们。这通常被称为微前端,但并不仅限于此。
事实上,MF 基于模块,实质上就是 JS 代码片段,也就是 chunk。为了解决依赖问题,webpack5 的实现形式是重写了加载 chunk 的 webpack_require.e,从而前置加载依赖;为了解决 modules 的共享问题,应用了全局变量来 hook。但有利有弊:
- 长处:做到运行时加载,shared 代码无需本人手动打包构建
- 毛病:可能会对页面运行时的性能造成影响;须要思考模块的版本控制;旧我的项目革新老本较大;
css 隔离
当主利用和子利用同屏渲染时,就可能会有一些款式会互相净化,如果要彻底隔离 CSS 净化,能够采纳 **CSS Module** 或者 ** 命名空间 ** 的形式,给每个子利用模块以特定前缀,即可保障不会相互烦扰,能够采纳 webpack 的 postcss 插件,在打包时 ** 增加特定的前缀 **。而对于子利用与子利用之间的 CSS 隔离,采纳 ** 动静加载 **,即在每次子利用加载时,将该利用所有的 `link` 和 `style` 内容进行标记,在利用卸载后,同步卸载页面上对应的 `link` 和 `style` 即可。另一种举荐的形式是 **shadow DOM**,是能够彻底隔离 css 的一个思路。可将子利用包裹在 Shadow DOM 节点中,只须要将 [shadowRoot](https://developer.mozilla.org/zh-CN/docs/Web/API/ShadowRoot)的 API 设置为 true。但 ** 存在一些问题 **,比方 Shadow DOM 的 css 隔离、dom 事件冒泡终止在 Shadow DOM 父节点的问题,会导致子利用外部在调用一些 UI 组件库的时候显示异样。
* 门户部件模块隔离计划
简略形容就是利用 vue 组件的隔离,包含款式 scoped、组件实例隔离,算不上特地齐备的思考。
因为门户将 UI 组件库、vue.js 等公共依赖从部件脚手架中排除(webpack externals),因而所有的部件都用同一套 UI 组件库,一方面为了 管制开发成本和标准 ,另一方面也是尽可能的将部件的 逻辑简单化 ,每个部件的 逻辑性能尽可能繁多。
但子模块领有宿主环境依赖的援用权限,并不合乎隔离要求。除此之外,子模块和宿主环境共享一个 window 对象,能施展的空间就更大了,但之所以凋谢,是因为真实情况下的确有这样的需要,比方子模块须要拜访宿主环境的变量(门户登录的 userName?等)。咱们尽可能将数据通过动静组件的 prop 来传递通信 <component :prop="widget.option" ... />
,尽可能通过标准、lint 等伎俩去限度部件外部对宿主的拜访。
在 CSS 款式方面就绝对简略,利用 vue 组件 <style scoped></style>
,给该组件生成一个惟一 data 值。如果想要扭转宿主 UI 组件库的款式,能够利用::v-deep
款式穿透 来设置,这样不会影响其余模块的 UI 组件款式。
模块通信
对于拆散的模块来说,音讯订阅(pub/sub)模式的通信机制是十分实用的,在基座利用中定义事件核心 Event,每个微利用别离来注册事件,当被触发事件时再由事件核心对立散发,这就形成了根本的通信机制。
能够引入基于 Flux 的状态治理机,Redux/Vuex,使每个子利用保护本人的 store。
qiankun 利用间通信计划
qiankun 官网提供的利用间通信形式 – Actions 通信,如下图所示,先注册观察者到 观察者池 中,而后通过批改 globalState 能够触发所有的观察者函数,从而达到组件间通信的成果。
* 门户部件通信
门户的部件基于 Vue.js 组件,因而通信机制也依赖于 Vue 组件本人的通信机制,即利用宿主环境 this.$root.$widgetEventBus
空 Vue 实例作为音讯总线,部件之间通过 $emit/$on
进行事件发送及监听。
总结
上述介绍了门户 portal 利用在可视化搭建及 Low Code 方面的实际,也介绍了社区中微前端的相干内容,站在伟人的肩膀上,会看到目前门户基于 Vue 组件实现的部件化仍然存在许多可改善的中央,如部件隔离、集成能力、运行时加载性能等。当然,所有计划的抉择须要结合实际场景和理论需要,综合思考,均衡利弊,没有银弹!
【参考】
- Low-code_development_platform
- 「可视化搭建零碎」——从设计到架构,摸索前端畛域技术和业务价值
- 2020 年大前端技术趋势解读
- low-code 与 20 年前的 Dreamweaver 有什么区别?
- 施行前端微服务化的六七种形式 – phodal
- 浅探 Web Worker 与 JavaScript 沙箱
- 可能是你见过最欠缺的微前端解决方案