Hi there ❗️

Let’s build a world with code ~ ✂️

如何打造一套Vue组件库

组件库能帮我们节省开发精力,无需所有东西都从头开始去做,通过一个个小组件拼接起来,就得到了我们想要的最终页面。在日常开发中如果没有特定的一些业务需求,使用组件库进行开发无疑是更便捷高效,而且质量也相对更高的方案。 目前的开源组件库有很多,不管是react还是vue的体系里都有很多非常优秀的组件库,比如我经常使用的就有elementui和iview。当然也还有其他的一些组件库,他们的本质其实都是为了节省重复造基础组件这一轮子的过程。也有的公司可能会对自己公司的产品有特别的需求,不太愿意使用开源的组件库的样式,或者自己有一些公司内部的业务项目需要用到,但开源项目无法满足的组件需要沉淀下来的时候,自建一套组件库就成为了一个作为业务驱动所需要的项目。 本文会从 ”准备“ 和 ”实践“ 两个阶段来阐述,一步步完成一个组件库的打造。大致内容如下: 准备:主要讲了搭建组件库之前我们需要先提及一下一些基础知识,为实践阶段做铺垫。 实践:有了一些基本概念,咱们就直接通过一个实践案例来动手搭建一套基础的组件库。从做的过程中去感受组件库的设计。 希望通过本文的分享以及包含的一个简单的 实际操作案例,能让你从组件库使用者的角色向组件库创造者的角色迈进那么一小步,在日常使用组件库的时候心里有个底,那我的目的也就达到了。 我们的案例地址是:https://arronkler.github.io/lime-ui/ 对应的 repo也就是: 准备 :打造组件库之前你应该知道些什么? 这一个章节主要是想先解析清楚一些在组件库的建立中会用到的一些平时在业务概念中很少去关注的概念。我会分为工程和组件两个方面来阐述,把我所知道的一些其中的技巧和坑点都交付出来,以帮助我们在实际去做的过程中可以有所准备。 项目:做一个组件库项目有哪些额外需要考虑的事? 做组件库项目和常规业务项目肯定还是有一些事情是我们业务项目不怎么需要,但是类库项目一般都会考虑的事,这一小节就是介绍说明一下,那些我们在做组件库的过程中需要额外考虑的事。 组件测试 很多开发者平时业务项目都比较赶,然后就是一般业务项目中都不怎么写测试脚本。但在做一个组件库项目的过程中,最好还是有对应的组件测试的脚本。至少有两点好处: 自动化测试你写的组件的功能特性 改动代码不用担心会影响之前的使用者。(测试脚本会告诉你有没有出现未预料到的影响) 对于类库型项目,我觉得第二点好处还是很重要的,这才能保证你在不断推进项目升级迭代的过程中,确保不会出现影响已经在用你所创造的类库的那些人,毕竟你要是升级一次让他的项目出现大问题,那可真保不准别人饭碗都能丢。(就像之前的antd的圣诞节雪花事件一样) 由于我们是要写vue的组件库,这里推荐的测试工具集是 vue-test-utils 这套工具,https://vue-test-utils.vuejs.org/zh/ 。其中提供的各种测试函数和方法都能很好的满足我们的测试需要。具体的安装使用可以参见它的文档。 我们这里主要想提的是 组件测试到底要测什么? 我们这里给到一张很直观的图,看到这张图其实你应该也清楚了这个问题的答案 这张图来自视频 https://www.youtube.com/watch?v=OIpfWTThrK8 ,也是vue-test-util推荐的一个非常棒的演讲,想要具体了解可以进去看一下。 所以回过头来,组件测试,实际需要我们不仅仅作为创造者的角度对组件的功能特性进行测试。更要从使用者的角度来看,把组件当做一个“黑盒子”,我们能给到它的是用户的交互行为、props数据等,这个“黑盒子”也会对应的反馈出一定的事件和渲染的视图可以被使用者所捕获和观察。通过对这些位置的检查,我们就能获知一个组件的行为是否如我们所愿的去进行着,确保它的行为一定是一致不出幺蛾子的。 另外还想提的一点偏的话题就是 契约精神。作为组件的使用者,我使用你的组件,等于咱们签订一个契约,这个组件的所有行为应该是和你描述的是一致的,不会出现第三种意料之外的可能。毕竟对于企业项目来说,我们不喜欢surprise。antd的彩蛋事件也是给各位都提个醒,咱们搞技术可以这么玩也挺有创意,但是这种公用类库,特别是企业使用的也比较多的,还是把创意收一收,讲究契约,不讲surprise。就算是自家企业内部使用的组件库,除非是业务上的人都是认可的,否则也不要做这种危险试探。 好的组件测试也是能够帮助我们识别出那些我们有意或无意创造的surprise,有意的咱就不说了,就怕是那种无意中出现的surprise那就比较要命了,所以写好组件测试还是挺有必要的。 文档生成 一般来说,我们做一个类库项目都会有对应的说明文档的,有的项目一个README.md 的文档就够了,有的可能需要在来几个 Markdown的文档。对于组件库这一类的项目来说,我们可以用文档工具来辅助直接生成文档。这里推荐 vuepress ,可以快速帮我们完成组件库文档的建设。(https://vuepress.vuejs.org/zh/guide/) vuepress是一个文档生成工具,默认的样式和vue官方文档几乎是一致的,因为创造它的初衷就是想为vue和相关的子项目提供文档支持。它内置了 Markdown的扩展,写文档的时候就是用 markdown来写,最让人省心的是你可以直接在 Markdown 文件中使用Vue组件,意味着我们的组件库中写的一个个组件,可以直接放到文档里去用,展示组件的实际运行效果。 我们的案例网站也就是通过vuepress来写的,生成静态网站后,用 gh-pages 直接部署到github上。 vuepress更好的一点在于你可以自定义其webpack配置和主题,意味着你可以让你自己的文档站点在开发阶段有更多的功能特性的支持,同时可以把站点风格改成自己的一套主题风格。这就无需我们重头开始去做一套了,对于咱们想要快速完成组件库文档建设这一需求来说,还是挺有效的。 不过这只是咱们要做的事情的一个辅助性的东西,所以具体的使用咱们在实践阶段再说明,这里就不赘述了。 自定义主题 自定义主题的功能对于一个开源类库来说肯定还是挺有好处的,这样使用者就可以自己使用组件库的功能而在界面设计上使用自己的设计风格。其实大部分组件库的功能设计都是挺好挺完善的,所以一般来说中小型公司即使想要实现自己的一套组件风格的东西,直接使用开源类库如 element、iview或者基于react的Antd 所提供的功能和交互逻辑,然后在其上进行主题定制基本就满足需求了(除非你家设计师很有想法。。。)。 自定义主题的功能一般的使用方式是这样的 通过主题生成工具。(制作者需要单独做一个工具) 引入关键主题文件,覆盖主题变量。(这种方式一般都需要适配制作者所使用的css预处理器) 对于第一种方式往往都是组件库的制作者通过把生成组件样式的那一套东西做成一个工具,然后提供给使用者去根据自己的需要来调整,最后生成一套特定的样式文件,引入使用。 第二种方式,作为使用者来说,你主要做的其实是覆盖了组件库中的一些主题变量,因为具体的组件的样式文件不是写死的固定样式值,而是使用了定义好的变量,所以你的自定义主题就生效了。但是这也会引入一个小问题就是你必须适配组件库的创造者所使用的样式预处理器,比如你用iview,那你的项目就要能解析Less文件,你用ElementUI,你的项目就必须可以解析SCSS。 其实对于第一种方式也主要是以调整主题变量为主。所以当咱们自己要做一套组件库的时候,不难看出,一个核心点就是需要把主题变量文件和样式文件拆开来,后面的就简单了。 webpack打包 类库项目的构建这里提两点: 暴露入口 外部化依赖 先谈第一点 “暴露接口”。业务项目中,我们的整个项目通过webpack或其他打包工具打包成一个或多个bundle文件,这些文件被浏览器载入后就会直接运行。但是一个类库项目往往都不是单独运行的,而是通过暴露一个 “入口”,然我在业务项目中去调用它。 在webpack配置文件里,可以通过定义 output 中的 library 和 libraryTarget 来控制我们要暴露的一个 “入口变量” ,以及我们要构建的目标代码。...

January 29, 2021

网络资源请求流程及优化

浏览器在获取网络资源的时候,会经过一系列的网络连接建立、请求数据、接收数据等过程。整个过程中的每个阶段都会存在时间消耗。有的时间消耗是不可避免的,但有的消耗是可以被进一步优化的。 本文想传递的内容就是:网络请求的流程是怎样的,以及如何通过 devtool 查看时间消耗,和如何进行优化。 一个完整的资源请求流程 我们先看一下完整的资源请求的流程是如何进行的 步骤 描述 1️⃣ 准备请求 构建请求行信息 2️⃣ 查找缓存 这个比较好理解,要请求之前先查看要请求的资源是否已经缓存过了,并且还处在有效期内。如果存在有效的缓存,那就不需要再去发送网络请求了,直接就可以返回给浏览器处理。 3️⃣ 准备IP 一般来说,我们给到的资源都是一个URL,其域名需要先转换为IP地址才能拿来使用。将域名转换为IP地址的过程,首先是会看DNS缓存是否存在,存在就返回IP,不存在就去 DNS 服务器查这个域名对应的IP。IP准备好之后就可以进行下一步了。 4️⃣ TCP排队 TCP连接不是随意建立的,对于 Chrome 浏览器来说,针对每一个域名,同时最多建立 6 个TCP连接。 所以如果当前已经存在了6个对同一个域名的TCP连接,那当前的请求只能先到TCP请求队列里面去排队,等待有连接被释放了,再从TCP请求队列里拿出来建立新的连接。 5️⃣ 建立TCP连接 三次握手🤝,两台计算机之间建立连接。TCP是可靠的面向连接的传输层的协议,建立了TCP连接,上层数据就能完整的送达对方计算机了。 6️⃣ 发送HTTP请求 给服务端发送基于HTTP协议的请求,然后会有一小段时间等待服务端的响应 7️⃣ 接收HTTP响应 服务端处理完请求,给出响应数据(🔆 分两次, 先给响应头的数据,再给响应体的数据) 8️⃣ 断开TCP连接 数据交互结束,四次挥手👋断开TCP连接 用 Devtool 查看时间消耗 操作步骤: 打开Chrome开发者工具 在Network面板下选择某个资源请求 切换到 Timing 子面板 关键时间消耗和优化策略 Timing面板中展示了整个资源请求所消耗的时间, 其中的时间消耗和我们上面所提及的一些资源请求的流程是对应的。关于Timing面板下的数据,完整的解释可以看下图(取自Chrome Devtools官方文档 https://developers.google.com/web/tools/chrome-devtools/network/reference) 其中有三个阶段我们需要特别关注一下: Queueing : 排队时间,也即 TCP 建立连接的请求发起前的排队时间 Waiting(TTFB) : Time To First Byte ,发送请求到接收到第一个字节的时间 Content Download : 资源下载时间,服务端从返回第一个字节到返回完所有数据所花费的时间 Queueing 排队阶段,这个阶段是指请求已经做好准备了,但是因为一些原因需要先排队等待。排队的原因主要有下面三种...

January 29, 2021

浏览器进程架构的演化

前言 曾经你用 IE6或IE7 或者 firefox 的时候有遇到一个插件崩溃,而你打开的一系列页面全部崩溃的场景么?😰 曾经你遇到过打开浏览器或某些页面总是弹出很多你不想打开的恶意窗,要一个一个手动叉掉的情况吗?😤 曾经在你打开某个页面由于某种原因整个浏览器就卡住,连关闭按钮都点不动的时候,你是只能强制关掉整个浏览器么?🤔 如果上述情况你都遇见过,那我们今天就有得聊了。如果没遇到也没关系,今天的内容也会让你拓展一下视野,当你真正遇到的时候,不至于一头雾水。 今天的主题是《浏览器进程架构的演化》,可能你会问了,什么是浏览器进程架构。其实很简单,架构指的是一个软件的各个方面的设计,那浏览器进程架构你就可以理解为 浏览器是怎么设计其进程的操作和管理方式的 我们会将各种浏览器放到一起来聊一聊这些 浏览器在历史发展过程中,其进程架构做了哪些调整?为什么这样调整?解决了哪些问题? 相信看完这篇文章,能让你明白开头提出的三个问题是怎么回事儿,对你以后遇到的其它问题也给出一个思考方向。 好了,既然要说进程架构的演化,那你真的了解了进程是怎么回事儿么?与线程之间有啥关系?我们来简单的过一遍 了解进程和线程 我们一般使用电脑的时候都会打开多个程序同时运行,比如同时打开了音乐程序放着歌,又打开了文档程序写记录,还开了一个下载程序下载电影。但 CPU其实在某一个特定时刻是只能执行一个程序的(先不考虑多核),那么我们的电脑又是如何同时运行多个程序的呢? 答案就是 进程。进程是操作系统中的概念,操作系统在面对同时处理多个程序的时候,将应用程序抽象为进程来运行。这样一来,操作系统就可以根据一定的规则快速的将CPU执行时间这一宝贵的资源分配给不同的进程去使用,因为切换分配的速度很快,所以看起来就像是多个应用程序同时在运行一样。 但是只有进程还不够,往往一个应用程序整体在执行的时候会存在多个子任务的情况,就像一个在线音乐程序在运行的时候,需要同时运行网络加载的子程序加载音乐流,还要运行音乐数据流的编解码子程序,还会运行音乐界面的UI程序等。因而操作系统又在进程里面划分出了 线程 的概念。有了线程,一个应用程序就可以同时管理自己的多个子任务了。从这里我们还要更新一个概念,CPU在某个特定时刻其实只能执行某个线程 。 我们再来整体过一遍一个程序的运行。当你运行一个应用程序的时候,操作系统会把你的应用程序封装为一个进程来运行。因为程序运行是需要内存的,所以在分配线程的时候也会同时给你分配一块儿对应的内存,用来存储程序代码、数据和文件资源等。当你的进程需要执行子任务的时候,就可以创建新的线程来执行。 这里我们需要了解几个特性: 线程共享进程资源。进程是操作系统分配资源的基本单位,所以进程所需要的系统资源都是操作系统给的,而里面的线程要用资源的时候只能共享进程所拥有的资源。这种资源包括内存空间,也包括操作系统的权限。 一个线程崩溃,整个进程跟着崩。 其实很好理解,一个程序运行过程中,如果某个线程出错了,因为内存是共享的,那如果产生了错误的数据,整个进程最终执行结果也很可能是错的。所以操作系统就直接全部干掉了。 进程之间相互隔离,通过IPC进行通信。 虽然我们一直说一个进程就是一个应用程序,但有的时候一个应用程序也有可能会启动子进程,进程之间的数据呢又是隔离开的,各自为战。要同步某些数据内容,就可以通过某种进程间通信(IPC)的手段来进行。(IPC是一个统称,指可用于不同进程之间通信的手段,这里无需细究) 早期的单进程浏览器 早期的浏览器包括IE7及之前的IE浏览器,Firefox浏览器都是使用的单进程的浏览器架构,也就是说整个浏览器程序都是在一个进程中运行的。不同类型的浏览器的实际进程架构肯定是有一定的差异的,为了描述方便,我们都简化为下面的这张图来说明一下单进程浏览器存在的问题。 在这样的进程架构里,整个进程除了要运行浏览器窗口,下载资源,其中的页面线程还要同时承担了页面渲染、JS执行以及各种插件的运行。这样的一个进程运行起来在我们现在看来其实是有点刀尖上跳舞的感觉。因为这样来运行包含很多页面甚至很多插件的和功能的浏览器,会及其的不稳定,不流畅和不安全 先来看看为啥不稳定。我们回到最开始提的问题,曾经你有用IE6、7或者Firefox的时候,一个插件或页面崩的时候,整个页面崩的情况吗? 现在听起来是不是有点熟悉,可能你心里已经有答案了吧。我们刚才谈到,线程崩溃,整个进程也会全部崩掉。在单进程架构的浏览器里,我们的页面渲染引擎、插件程序,还有像IE里有很多动态链接库的程序都是可能出错的❌,那么不管这些程序在进程中的哪个线程里面运行,其实只要有一个出了个错,那么整个浏览器就挂掉了。这就是我们说的不稳定。 再来看看不流畅。我们已经知道CPU其实在某个时间点只能执行某个进程中的某一条线程。那么当我们一个页面线程中既包含了页面的渲染又包含了JS的执行,还有各种插件执行的时候。假设我们JS代码中写了一个死循环的任务,那可想而知,整个浏览器中的其他任务就都没法执行下去了——也就卡住了。那么即使我们不那么暴躁,只是我们的JS或者插件程序需要一直运行一些东西,当你页面正在用JS执行动画的时候,CPU突然被插件进程抢过去执行其他任务了,那你的动画效果怕不是卡得你心慌 😫 最后说一下不安全的问题,这就要用到另一条我们刚提过的特性了——线程共享进程资源。在windows上用过杀毒软件和各种安全卫士软件的应该对 “恶意插件” 这个词不陌生吧。插件这种东西本来是用来便捷的扩展浏览器功能特性的,比如我们最常见的Adobe Flash Player这个插件,以前几乎人手必备。当浏览器和插件的程序在一个页面里面运行的时候,因为进程的内存是被共享的,因而插件就能获取到浏览器运行过程中的数据,以及拥有和浏览器同等的系统权限。那么当你的系统感染了恶意插件,在浏览器运行的过程中,这个插件就可以记录你输入到网页的密码,给你弹出各种窗口,打开多个网页等等。现在你知道曾经你手动一个一个叉掉的那些无故打开的页面是怎么来的吧,同时你也应该了解到当初QQ号被盗的一种可能的原因了吧。 这就是早期的单进程浏览器存在的问题,不稳定、不流畅和不安全。而这一切都是由于把所有的东西像揉面团一样的放到一起所产生的问题,职责是混淆不清的、权限是一给全给的、还有 “一人放火全家遭殃” 的风险。 向多进程架构演化 可能是由于早期的网页功能特性都比较简单,不像现在这样需要非常丰富的功能特性(比如:Canvas、WebGL、Webworker 这些东西),那个时候常规来说单进程就足够了。曾经前端的开发工作还没那么复杂的时候,页面很多都是后端直接就写了。现在有了前端开发工程师这样的一个职位,而且有了更加系统和全面的前端开发工作流,前端的重要性和复杂性都在不断提升。除了单进程浏览器自身存在的一些问题,面对时代的变革,前端的崛起,浏览器若还停留在原地肯定是行不通的,必然无法支撑新的技术的发展,何况单进程本身带来的问题也是需要解决的。 所谓多进程浏览器当然就是将浏览器的各种不同类别的任务拆分出来,放到多个不同的进程中去执行。这里会用到一个关键的安全技术,就是Sandox。 Sandbox(沙盒)可以看做就是一个被限制了权限的进程,一个稍微严格的定义是这样的 沙箱技术按照安全策略来限制程序对系统资源的使用,进而防止其对系统进行破坏,其有效性依赖于所使用的安全策略的有效性 也就是说沙盒限制了多少权限是根据你的安全策略来的,它能防止系统被恶意破坏,提供了一定的安全性,这也是我们上面提到的 “不安全” 这一个问题的解决方案的一环。具体的沙盒的实现方法呢在不同的操作系统上都是有差异的,毕竟进程是操作系统提供的,这不是我们的重点,就此打住。 接着我们一起来看一下Chrome、IE 和 Firefox这三款浏览器是如何演化自身到多进程的一个架构方案的? Chrome的多进程架构 Google Chrome浏览器最早是在2008年发布的,比起IE这种老牌浏览器,现在看来算是后起之秀。不过这个后起之秀并非只是突兀的来抢占浏览器市场份额的,这一点从 Chrome一开始就是基于多进程架构 可见端倪。 在Chrome的多进程架构中,包含这样几种进程 浏览器主进程。整个浏览器的主要进程,其他几个进程都是这个进程的子进程,由它来管理和调配;同时你所看到的浏览器的整个窗口,包含地址输入栏,书签栏这些东西也都是它来展示的; 渲染进程。一般来说一个Tab标签页面一个渲染进程(对于不一般的情况可以了解Site Isolation策略);每个渲染进程中会运行Blink布局引擎,V8 JavaScript执行引擎等,单独服务于一个Tab标签页;运行在沙盒中无法访问系统资源。 插件进程。一个插件单独存在于一个进程当中,同时为了安全性,运行在沙盒中限制其权限。 网络进程。发起网络请求访问。 GPU进程。处理GPU渲染方面的任务。 可以看得出,chrome这样的多进程设计,将原本揉到一起的各种任务根据职责的不同分拆了出去,极大的减轻了单进程的执行负担。(其实,最早的chrome进程设计其实没有单独的网络进程和GPU进程的,都是放到浏览器主进程中的)...

January 29, 2021