探索组件在线预览和调试
这是第159篇没有水的原创文章。想要获取更多原创文章,请搜索公众号【正彩云前端团队】关注我们~
背景
开发过程中如何快速培养前端人员 洞察力 到组件功能和属性?现状是阅读组件的相关文档。好在基础组件库的文档比较完整清晰,示例都是手动完成的。与业务组件相关的文档只能在内部 NPM 私有库上查看,静态 API 文档,没有组件的演示。对于非前端的人,如何预览和调试组件?例如:某天,产品要提前调查其他业务线的业务组件的功能是否能满足业务需求;业务组件开发完成,可参与组件相关功能验证的测试和设计;运营商可以低代码搭建平台,预览和调试相关组件等。
基于以上痛点,我们从需求点出发,逐步探索实施方案。
需要
情景分析
功能
- 组件预览
- 组件调试面向不同的用户群。组件功能调试的交互分为两种。一个是 代码调试 ,即通过代码编辑器修改示例代码,另一个是组件 架构调试 ,通过schema JSON数据描述组件的属性,然后通过schema渲染器渲染到组件属性面板中,让非开发人员也能轻松调试组件功能。
分类
- 基本组件
- 业务组件
- 低代码组件大致组织如下:
这里 低代码组件 它是指提供给低代码构建平台的自定义组件。目前,公司低代码搭建平台主要包括“鲁班”。对此感兴趣的可以参考之前的问题。 “我保证” 的文章。
用于组件 架构调试 ,低代码组件本身自带一个schema文件,比如:“鲁班”自定义组件会有一个schema.json文件,需要开发者编写和维护这个文件。
喜欢:
{
“道具” : {
“链接列表”:{
“组”:“链接配置”,
"title" : "链接列表" ,
“类型”:“数组”,
“领域”:[
{
“名称”:“图像地址”,
"title" : "图片链接图片地址" ,
“类型”:“字符串”
} ,
{
“名称”:“图像链接”,
"title" : "链接跳转地址" ,
“类型”:“字符串”
}
]
}
} ,
“楷模” : {
“链接列表”:[
{
“图像地址”:“”,
“图像链接”:“”
} ,
{
“图像地址”:“”,
“图像链接”:“”
}
]
}
}
复制代码
同理,业务组件也需要相同模式协议的 JSON 文件,以便动态调试组件的属性。但是,开发组件的学生将不允许手动编写它们。
自动生成schema文件的大致思路:
应用
- 基本组件的示例在线预览和调试
- 演示在线预览和调试业务组件
对于人群
- 研究与开发
- 非研发:产品、测试、运营研发主要使用组件的调试功能,而运营、产品等非研发人员有一个简单快速的诉求,就是直接预览组件,看到实时效果通过修改组件的props,那么问题来了,如何修改组件当前的props属性呢?玩过低码的同学应该很清楚有个组件 属性面板 .基于以上,我们可能需要代码编辑面板、组件属性面板、组件功能模块。
粗略画出如下页面的结构图:
研究
市场上成熟的产品
- Stackblitz 是一个优秀的在线 IDE,它移植了 VS Code 的许多功能和特性。目前支持很多框架模板,例如:React、Angular、Vue3、Next.js、Nuxt3 和自定义模板。其中,StackBlitz 提供的 WebContainers 可以在浏览器端运行 Node.js 环境。
- CodeSandbox 是一款为 Web 应用而构建的在线编辑器,同时也提供了多种模板供开发者使用。大部分核心代码也是开源的,网上有相关原理分析和搭建在线IDE解决方案的资料。有兴趣的同学可以去看看。
概括
需求和应用场景已经明确。考虑到不同的用户群体,交互方式也不同。重点是组件调试功能的差异。对于开发者来说,可以通过代码编辑器修改代码,达到调试效果。修改属性面板的组件属性值。市面上成熟的产品会提供一些设计思路,具体实施方案将在下文详细讨论。
程序
从页面结构图中,我们来说说代码编辑器、组件属性面板、工具栏、预览区的设计。
代码编辑器
目前主要有两种:
- 摩纳哥编辑
- Codemirror MonacoEditor比较强大,集成度高,但是比较重,而Codemirror轻巧紧凑,核心文件压缩后只有70+ KB左右,根据需要支持的语言打包.
两种代码编辑器都可以满足我们的需求,在线修改一些组件Demo的部分代码,其实Codemirror就够了。
组件属性面板
了解低代码搭建平台的朋友应该不陌生。其实就是通过表单来动态修改组件的属性参数。因此,需要一个通用的模式协议来描述组件的自定义属性。可以通过 我保证 架构数据由大数据搭建平台提供,我们负责渲染。
组件属性类型与操作表单类型的对应关系大致列出:
工具栏
工具栏中包含的主要功能有:
- 帐号登录
- 当需要调试接口代理业务组件和低代码组件时,例如测试人员需要干预测试组件功能时,需要使用 帐号登录 和 接口代理功能 .组件中业务接口相关的请求头需要携带当前登录用户的token信息,首先通过请求oauth接口获取对应的token,然后填充到请求头的Authorization字段中。
上述实现的前提是需要代理服务。在本地开发环境中,我们可以使用 http代理 插件创建了本地代理服务,那么问题来了,浏览器端的代理服务怎么做呢?
目前主流的解决方案都是Chrome插件的形式,需要用户手动填写代理界面等信息。在我们的场景中,这个解决方案显然不够人性化。还有一种可以利用浏览器黑科技的解决方案—— 服务工作者 ,它可以拦截来自网页的请求并自定义返回的内容,相当于在浏览器内部实现了一个反向代理。
预览区
核心将涉及两点:
- 容器
- 通信容器是指页面容器。业界常见的做法是通过iframe将编译好的组件代码挂载到iframe中的一个根节点,主要用于环境隔离和预览页面访问链接的动态生成。编辑器、核心包和预览区之间的通信可以使用 postMessage。
通讯时序图:
核心包
设计思路主要参考CodeSandbox的核心源码,主要涉及代码翻译和代码执行。核心模块是 Manger、Transpiler、Preset、Transpiled-module、Runtime。
架构图:
粗加工:
马槽模块
顾名思义,管理其他核心模块的“manager”主要负责代码翻译和执行的一系列流程。
核心方法是:
addTranspiledModule
resolveTranspiledModuleSync
resolveTranspiledModuleAsync
评估转译模块
先缓存翻译后的模块,放入转译模块
目的。如果需要,可以同步或异步地从缓存中加载翻译后的模块。如果需要执行翻译后的模块,可以调用评估转译模块
方法。
转译模块
类型定义:
类型 IModule = {
路径:字符串;
网址?:任何;
代码:字符串;
需要?:数组<string>;
父母?:模块;
} ;
接口 ITranspiledModules {
[路径:字符串]:{
模块:IModule;
t模块:{
[查询:字符串]:ITranspiledModule; // ITranspiledModule 类型定义放在 Transpiled-module 模块中
} ;
} ;
}
复制代码
转译器模块
类似于 Webpack 的 loader,它编译指定类型的文件,例如 Babel、Typescript、vue、tsx、jsx 等。
引入以下内置 Transpiler 模块:
babelTranspiler
样式转换器
原始转译器
noopTranspiler
VueTranspiler`` 原始转译器
与 Webpack 的 raw-loader 一样,它将模块的内容作为字符串导入到内联静态资源中。
实现原理也很简单:
module.exports = JSON.stringify(sourceCode)
复制代码
babelTranspiler
这里实现一个简化版,在bable-standalone.js中引入script标签,获取全局对象babel。
部分核心代码:
从 './plugins/babel-plugin-rename-imports' 导入 babelPluginRenameImports;
const transpiledCode = 窗口。通天塔。转换(代码,{
插件:[babelPluginRenameImports],
预设:['es2015','es2016','es2017'],
})。代码;
复制代码
VueTranspiler
,这里默认是vue2.0版本,核心依赖 vue 模板编译器
, vue-template-es2015-compiler
.
将vue单文件组件转换为SFC对象:
从 'vue-template-compiler' 导入 * 作为编译器;
从'vue-template-compiler'导入类型{SFCDescriptor};
const sfc: SFCDescriptor = 编译器。 parseComponent(content, { pad: 'line' });
复制代码
解析Vue模板部分的核心代码:
从 'vue-template-compiler' 导入 * 作为编译器;
从'vue-template-es2015-compiler'导入转译;
函数 vueTemplateCompiler(html,选项){
const bubleOptions = 选项。气泡;
const vueOptions = 选项。 vue 选项 || {};
const userModules = vueOptions.编译器模块 ||选项。编译器模块;
const stripWith = bubleOptions.变换。带!==假;
常量 { stripWithFunctional } = bubleOptions。变换;
const staticRenderFns = 已编译。静态渲染Fns。地图((fn)=>
toFunction(fn, stripWithFunctional)
); // 静态渲染函数放在数组中
const compilerOptions:编译器。 CompilerOptionsWithSourceRange = {
保留空白:选项。 preserveWhitespace, // 是否保留 HTML 标签之间的所有空白字符
模块:默认模块。 concat(userModules || []), // 自定义构建模板
指令:vueOptions。编译器指令 ||选项。编译器指令 || {}, // 自定义命令
评论:选项。 hasComment, // 是否保留评论
scopeId:选项。有作用域?选项。标识:空,/
};
常量编译 = 编译器。编译(html,编译器选项);
// 生成渲染函数和静态子树
让代码 = 转译(
'var 渲染 = ' +
toFunction(compiled.render, stripWithFunctional) +
'\n' +
'var staticRenderFns = [' +
静态渲染Fns。加入(',')+
']') + '\n';
// 用剥离标记(这使 Vue 能够使用正确的运行时代理检测)
如果(带){
代码 += `render._withStripped = true\n`;
}
常量导出=`{渲染:渲染,静态渲染Fns:静态渲染Fns}`;
代码 += `module.exports= ${ 出口 } `;
返回码;
}
函数 toFunction(代码,stripWithFunctional){
return 'function (' + (stripWithFunctional ? '_h,_vm' : '') + ') {' + code + '}';
}
复制代码
Vue 在渲染阶段将模板编译成 AST,然后根据 AST 生成渲染函数。底层通过调用render函数生成VNode来创建一个虚拟DOM。
预设模块
组件预设构建模板,针对不同的组件框架类型,如:Vue2、React等,预设该类型组件所需的默认组件 转译
模块。类似于 vue-cli,create-react-app。
核心方法:
注册表转译器
获取转译器`` 注册表转译器
作用是注册Transpiler模块。
部分伪代码:
vue 预设。注册转译器(
( 模块 ) => /.(m|c)?jsx?$/.测试(模块。路径),
[{ 转译器:babelTranspiler }]
);
vue 预设。注册转译器(
(模块)=> /.vue$/。测试(模块。路径),
[{ 转译器:vueTranspiler }]
);
复制代码
转译模块模块
即翻译后的模块维护翻译结果、代码执行结果和依赖模块信息,并负责驱动特定模块的翻译(调用Transpiler)和执行。
运行时模块
执行翻译后的模块入口,使用eval执行入口文件,如果遇到require函数,加载翻译后的依赖模块,然后使用eval执行。
核心代码:
导出默认函数(代码:字符串,要求:函数,模块:{导出:任何},环境:对象= {},全局:对象= {},{ asUMD = false }:{ asUMD?:布尔} = {}) {
常量 { 出口 } = 模块;
const g = typeof window === 'undefined' ?自我:窗口;
常量全局 = g;
G。全球=全球;
// 兼容Node.js环境,部分列出
常量进程 = {
env: { NODE_ENV: '发展', ...env },
cwd: () => { return '/' },
umask: () => { 返回 0 }
};
// 全局变量
const allGlobals: { [ key: string]: any } = {
require, // 需要函数
模块,
出口,
过程,
全球的,
...全局变量,
};
//是否UMD模块
如果(asUMD){
删除所有全局变量。模块;
删除所有全局变量。出口;
删除所有全局变量。全球的;
}
常量 allGlobalKeys = 对象。键(所有全局);
const globalsCode = allGlobalKeys。长度 ?所有全局键。加入( ', ') : '';
const globalsValues = allGlobalKeys。地图( ( k ) => allGlobals[k]);
const newCode = `(function $csb$eval(` + globalsCode + `) {` + code + `\n})`;
(0,评估)(新代码)。应用(allGlobals.global, globalsValues);
}
复制代码
概括
从页面功能模块到组件构建的核心封装设计,相信大家已经初步了解了。有两点没有提到,这里简单补充一下。
第一点是依赖包的数据源。简单粗暴的一点就是创建一个manifest文件,并预存一份底层通用依赖包数据的副本,比如:babel插件相关等,如果需要动态添加依赖包,可以使用import -地图功能。
第二点是Transpiler模块没有提到react组件的构建方案,只是添加了相关的Babel插件,比如: 转换运行时
, @babel/plugin-transform-react-jsx-source
等待。
最后
有背景、需求、研究和计划四个层次。其中,背景和需求更多是从产品的角度考虑和设计。只有这样,事物才能更好地满足用户需求,提升用户体验。我们的技术人员不仅关心技术层面的设计,更经常从产品的角度思考。
组件是项目开发的一个组成部分。从基础组件到业务组件,我们的前端开发人员每天都在与组件打交道。我们可以有很多关于组件的话题,如何创建高质量的组件?如何提高组件的复用率?如何提高对组件的感知?等等,贯穿组件的整个生命周期,如何把组件管理好,需要我们的共同努力和思考。
参考
CodeSandbox 的浏览器端 webpack 是如何工作的?
推荐阅读
开源作品
- Zhengcaiyun front-end tabloid
开源地址 www.zoo.team/openweekly/ (小报官网首页有微信交流群)
- 产品选择sku插件
职业生涯
ZooTeam是一支年轻、充满激情、富有创造力的前端团队,隶属于正彩云产品研发部,基地位于风景如画的杭州。团队目前有90多名前端合伙人,平均年龄27岁,其中近40%是全栈工程师,妥妥的青年风暴群。成员由来自阿里、网易的“老兵”,以及浙江大学、中国科技大学、航电大学等学校的新生组成。除了日常业务对接,团队还在材料系统、工程平台、搭建平台、性能体验、云应用、数据分析与可视化等领域进行技术探索和实战,推动和实施了一系列内部技术产品。探索前端技术系统的新前沿。
如果你想改变,你已经被东西折腾了,希望开始折腾东西;如果你想改变,你被告知你需要更多的想法,但你不能打破游戏;如果你想改变,你有能力做到那个结果,但你不需要你;如果你想改变你想要完成的事情,你需要一个团队来支持,但没有地方让你带领人;如果要改变既定的节奏,那就是“5年工作时间,3年工作经验”;如果你想改变原来的感悟是好的,但总有那层窗纸的模糊……如果你相信信仰的力量,相信平凡的人可以成就不平凡的事情,相信他们可以遇到一个更好的自己。如果你想参与到业务腾飞的过程中,亲自推动一个业务理解深入、技术体系完善、技术创造价值、外溢影响力的前端团队的成长,我觉得应该讲话。随时,等你写东西,发给[ [电子邮件保护]](/cdn-cgi/l/email-protection)
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议。转载请附上原文出处链接和本声明。
这篇文章的链接: https://homecpp.art/2621/9723/0756
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明