Hello ~ 我是小斑,一个富文本编辑器。今天,咱来聊聊体重,对,没错!就是那使人头疼的体重!任何事物都烦这个体重,固然也包括我,当个人创造者阿飞发布小斑的第一个版本时,小斑足足有 4M
之重!看着控制台茫茫多的流量消耗,他愣住了:你。。。特么的这么怎么胖,足足 4M
(包括 CSS
)!但小斑我也很无奈呀!代码是你写的,怪我咯!javascript
软件发展总归避免不了两个过程:野蛮生长 & 精心优化,小斑长算是长出来了,但这一身肥膘得好好减减!css
哝!上图,就是我刚诞生时,所包含的 JS
模块,足足 3M
之多!但小斑的核心代码仅仅存在于右下角蓝色的方块内,其余都属于加强体验的代码,包括 UI
框架,代码高亮,表情的便捷输入等等。html
接下来,小斑瘦身记,正式开始。java
提到数据压缩,就不得不提一个鼎鼎大名的数据压缩算法:Gzip
。react
Gzip
全名: GNU zip
开源的数据压缩算法,普遍应用于网络传输。你们可能会有疑惑,数据压缩算法这么多,凭啥 Gzip
如此出名?由于压缩比高?webpack
no!no!no!仅仅由于它是 GNU
开头,互联网诞生于一个专利横飞的年代,各类好用的压缩算法受限于专利,并不能使用在开放的互联网上,所以你们须要一个高效且开源的压缩算法,Gzip
所以诞生!git
使用 Gzip
很简单,过程大概也就 3 步:github
Gzip
将须要发送的数据进行压缩;Gzip
解压;所以,Gzip
是服务端对输出内容的优化,且须要客户端支持(客户端须要有解压的能力)。web
等等!须要客户端支持?会不会很麻烦?算法
固然不!别忘了 Gzip
的全名是 GNU zip
,它但是开源的!只要客户端内置 Gzip
模块,就能够彻底使用 Gzip
压缩后的数据!所以能够简单的认为:只要服务端进行了 Gzip
压缩,文件的体积能瞬间减小一半!
至于如何开启 Gzip
,各个服务平台都有与之相对应的方法。小斑由 Nginx
提供服务,开启 Gzip
模块后,小斑体积骤减至 1.5M
,看起来像个瘦子了!但也仅仅是看起来!我依然是个胖子,只不过神奇的压缩算法把我给变瘦了,滤镜下的胖子,再怎么好看依旧是个胖子!
ps:并非全部的浏览器都内置了 Gzip
模块,IE
系列就没有(你们赶忙抛弃 IE
吧),所以真实环境下客户端须要一些特殊的请求头,来控制服务器返回的具体内容。
冗余代码:就是那些打包到项目中,却没被使用的代码。看到这个标题,有些小伙伴可能会心生不屑,哼!这人连 Webpack
支持 TreeShaking
了都不知道,也不知道是用那个版本的 Webpack
,还在说这些老掉牙的问题,真没意思!
稍等!小斑诞生环境中的 Webpack
早已 4.42.0
,彻底支持 TreeShaking
,在这里,小斑只想问一个简单的问题:TreeShaking
能解决样式冗余吗?
不能,直到如今为止,依然没有一种较好的办法,在代码打包时,解决样式冗余的问题。但在代码打包前,也就是编写代码的时候,却能够!
相信使用过 Antd
组件库的小伙伴,使用 Antd
时第一行代码都长这样:
import "antd/dist/antd.css";
但我悄悄告诉你,这个文件,整整 700K
!请问:有何感想?
小斑为此深刻查看了 Antd
代码,了解到其实每一个组件下都有单独的样式文件,单独引入样式,就能能够移除多余 CSS
代码。示例以下:
// antd 的公共样式,必须引入 import "antd/lib/style/core/index.less"; // 组件样式,单独引入便可 import "antd/lib/button/style/index.less"; import "antd/lib/xxx/style/index.less";
考虑到,编辑器内,用到了一些高级特性,须要较高版本浏览器的支持,故只考虑了最近几个版本的 Chrome(包括使用 Chromium 核心的其余浏览器)
、FireFox
、Safari
,一些已经成为标准的 CSS3
样式前缀就不必兼容了,所以小斑的兼容性以下:
"browserslist": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ]
不要问小斑为何不兼容 IE
,连它亲爸都不要它了,兼容它干吗!
一番下来,小斑的样式获得了最大程度的精简(由于 Antd
组件内的样式包含了组件的全部样式,会有部分冗余),样式体积骤减,由本来 900K
减至不到 200K
!通过 Gzip
发生到客户端的体积,不到 100K
!
在小斑诞生之初,便已肯定,编辑器必须支持代码高亮,那天然就须要引入 highlight.js
,但这句代码比 Antd
的样式代码更加夸张,好嘛!整整 1M
的 JS
!看到上图中那个大大的 highlight.js
代码块了吗?臃肿不堪,经仔细研究后发现,核心的高亮代码其实不多,绝大部分被 languages
占用!
怎么办?去官网瞧瞧呗,得嘞,不看不知道,一看吓一跳,经过如下形式引入的 highlight.js
包含了整整 189
种的语言解析器!
import hljs from 'highlight.js';
什么?为啥 TreeShaking
没起做用?这和 TreeShaking
的关系可真心不大,高亮是在打包后才用到的模块!那该如何精简?其实官方已经给了答案:按需加载,仅加载须要引入的语言解析器,甚至官方已经为咱们推荐好了经常使用的 38
种语言,一一注入便可!
import hljs from 'highlight.js/lib/core'; import javascript from 'highlight.js/lib/languages/javascript'; hljs.registerLanguage('javascript', javascript);
虽然总体的代码量因为须要注册 38
种语言增长了一点点,但换来的倒是 1M
到 300K
打包体积的变化,值吗?很是的值!
Δ 移除很是用语言后的代码高亮后的总体打包体积。highlight.js
仅占了其中的一小块。
有用过小斑编辑器应该知道,小斑支持选择代码的高亮风格以及文章的主题。一开始,小斑包含这部份内容,但后来发现,每次发版,这部份内容永远不变!高亮风格和文章主题不过是一段样式信息而已,为何不静态化?把这部份内容独立出去!这样不只小斑可使用,你们也均可以使用了!
最后,阿飞又折腾出一个新项目:zebra-editor-theme
专一于文章样式的管理,但愿你们能多多关注!
固然,开源意味着共享,站点内全部的代码高亮风格,以及文章主题,你们均可以获取,使用,一下是文章主题以及代码风格的样式文件获取方式,固然来目前的文章主题还不多,欢迎你们多多 PR
~
冗余,静态代码已优化完毕,看着这依然有 2.5M
的小斑,阿飞叹了口气:哎,你个胖子啊,看来不能一口气把全部的代码都给你。异步加载模块得用上了!
异步加载是什么?
一种延迟加载的技术,可让资源在须要加载的时候,才进行加载。
如何实现?
很简单!一句话的事:
const loadMdAst = () => import( /* webpackChunkName: "mdast", webpackPrefetch: true */ "@textlint/markdown-to-ast" );
如上,就动态的引入了 Markdown
语法解析器。
如何使用?
import
函数返回 Promise
,该 Promise
会在资源加载结束后进入 resolve
状态,就能得到模块的默认导出了!
const mdAstParse = await loadMdAst();
如上就能在 async
函数中获取 Markdwon
的解析器。但该种方式有个弊端,因为异步函数是有传染性的!会致使一层层的函数都变成异步函数。
import {lazy, Suspense} from "react"; const Panel = lazy(() => import(/* webpackChunkName: "color", webpackPrefetch: true */ "./color-panel"), ); const ColorBtn = () => { return ( <Suspense fallback={<Loading size={80} />}> <Panel /> </Suspense> ) }
一个简单例子:颜色选择框经过异步组件的形式,加载到项目中,相关内容可翻阅文档:React-Suspense
。
异步的模块或是组件能够延缓加载时间,但却会致使两个问题:
对于第一点,若是说在 async
函数出来以前,大部分小伙伴心里估计是抗拒的,本来流程明确的代码,被套用在一个 Promise
链里,想一想都不舒服,不过如今 async/await
函数规范以及制定,异步的获取结果也就一个 await
的事,一点也不麻烦!因此目前,第一点能够忽略不计!
关于第二点,这么说,若是项目中的模块首屏就会被用到,虽然使用了异步的形式,但却依然要等到模块加载完毕才能展现首屏,这类异步的模块其实没有必要。
但若是说项目中有个功能,使用者始终不会用到,不加载应该是最好的选择。还有,若是该功能,不会出如今首屏,但却须要长达 1s
的加载时间,那么它以一种后台加载的形式,默默的在用户使用的时候加载好,对用户来讲,也是很是棒的体验了!
所以,对于异步模块,只要知足这两点中的一点,就可使用了:
异步模块,虽然不能减小小斑的代码总量,但却能让小斑早一点与大家相见。你说值吗?固然很是的值呀!
熟悉 Webpack
的小伙伴都应该知道,Webpack
拥有代码分类打包的能力,具体到 Webpack4
其实就是 optimization.splitChunks
这一块你们讨论的不少了,复制粘贴官网的文档也没有意义,就很少说了。在这小斑推荐你们结合官网和网上总结一块儿看,由于网络上文章的并不必定是最新的,记录下为何要代码分块(知其因此然比知其然更加剧要!):
UI
库)实际上是能够缓存的;bundle
不添加版本号,就能被浏览器缓存了!分包,说到底,是让浏览器缓存生效的一种策略,若是说全部的代码都在一个 bundle
中,为了不缓存致使的影响,不得已须要添加版本号,但分包以后就不一样了,即便版本须要更新,一些包的内容其实没有发生变化,打包出来的文件名也就不会变化,这极大的利用了缓存!
Δ 经异步加载,内容分包后的最终成果
首屏须要加载的内容块以下:
willChange
: 一些可能会发生变化的项目依赖,如 antd
,由于可能会使用新的组件;library
: 固定不会的项目依赖,如 react
;main
: 小斑的核心代码;未经 Gzip
前,一共:554K + 344K + 338K ≈ 1M
,Gzip
后仅仅 340K
小斑瘦身大获成功!可是,一切并无结束!
Service Work
能够简单理解为一个介于客户端和服务器之间的一个代理,客户端的请求会经过 Service Work
对外发送,并获取内容,所以 Service Work
具备了控制返回内容的权利,它能够缓存返回的内容,并在第二次请求的时候直接返回数据,这点和缓存很像,但区别就在于,它是可控的,甚至能够提早获取资源,并缓存!
想像一个具体场景,你的网页发版了,用户焦急的等待着浏览器下载新版网页的资源,但一旁使用 Service Work
的网站依旧瞬间打开了老版页面,开始浏览了起来,看起来好像你隔壁的网页没更新。但次日,你却发现隔壁的网页,不只更新到了最新版,并且依旧瞬间打开!做为一个积极向上的开发者,我想,你应该理解到了这在用户体验上的差距!
Service Work
能够检查更新,输出原先内容的同时,下载最新内容,并在合适的时候更新缓存内容,对于用户来讲,除了第一次访问,以后的每一次都是瞬间打开,彻底不须要下载新内容!
这时候有些小伙伴就会问了:这么牛逼的技术,必定很难咯!不!它不只不难,还很简单,简单到若是你使用脚手架,都不须要考虑这个问题!
因此,你要作的仅仅是打开脚手架里的 Service Work
选项 ~
啰啰嗦嗦写了将近 6000
字,固然,还有一些好比 JS
的代码压缩插件 TerserPlugin
,CSS
的压缩插件 MiniCssExtractPlugin
之类的不提也罢,都是形式化的东西,这些脚手架已经帮开发者弄好了,不须要考虑,固然了解仍是须要了解的,那就先把 Webpack
文档看一遍吧 ~
虽然文章仅用了几天写成,但具体到小斑的优化过程倒是是一个较长的过程,总结不易,给个 Star
呗。