小斑瘦身记:4M 到 300K 的华丽变身

console.info

Hello ~ 我是小斑,一个富文本编辑器。今天,咱来聊聊体重,对,没错!就是那使人头疼的体重!任何事物都烦这个体重,固然也包括我,当个人创造者阿飞发布小斑的第一个版本时,小斑足足有 4M 之重!看着控制台茫茫多的流量消耗,他愣住了:你。。。特么的这么怎么胖,足足 4M(包括 CSS)!但小斑我也很无奈呀!代码是你写的,怪我咯!javascript

软件发展总归避免不了两个过程:野蛮生长 & 精心优化,小斑长算是长出来了,但这一身肥膘得好好减减!css

image

哝!上图,就是我刚诞生时,所包含的 JS 模块,足足 3M 之多!但小斑的核心代码仅仅存在于右下角蓝色的方块内,其余都属于加强体验的代码,包括 UI 框架,代码高亮,表情的便捷输入等等。html

接下来,小斑瘦身记,正式开始。java


数据压缩:从 4M 到 1.5M

提到数据压缩,就不得不提一个鼎鼎大名的数据压缩算法:Gzipreact

Gzip 全名: GNU zip 开源的数据压缩算法,普遍应用于网络传输。你们可能会有疑惑,数据压缩算法这么多,凭啥 Gzip 如此出名?由于压缩比高?webpack

no!no!no!仅仅由于它是 GNU 开头,互联网诞生于一个专利横飞的年代,各类好用的压缩算法受限于专利,并不能使用在开放的互联网上,所以你们须要一个高效且开源的压缩算法,Gzip 所以诞生!git

使用 Gzip 很简单,过程大概也就 3 步:github

  1. 服务端使用 Gzip 将须要发送的数据进行压缩;
  2. 客户端将接收的数据并进行 Gzip 解压;
  3. 客户端使用解压后的数据;

所以,Gzip 是服务端对输出内容的优化,且须要客户端支持(客户端须要有解压的能力)。web

等等!须要客户端支持?会不会很麻烦?算法

固然不!别忘了 Gzip 的全名是 GNU zip ,它但是开源的!只要客户端内置 Gzip 模块,就能够彻底使用 Gzip 压缩后的数据!所以能够简单的认为:只要服务端进行了 Gzip 压缩,文件的体积能瞬间减小一半!

至于如何开启 Gzip ,各个服务平台都有与之相对应的方法。小斑由 Nginx 提供服务,开启 Gzip 模块后,小斑体积骤减至 1.5M ,看起来像个瘦子了!但也仅仅是看起来!我依然是个胖子,只不过神奇的压缩算法把我给变瘦了,滤镜下的胖子,再怎么好看依旧是个胖子!

ps:并非全部的浏览器都内置了 Gzip 模块,IE 系列就没有(你们赶忙抛弃 IE 吧),所以真实环境下客户端须要一些特殊的请求头,来控制服务器返回的具体内容。


移除冗余代码:锐减 1.4M

冗余代码:就是那些打包到项目中,却没被使用的代码。看到这个标题,有些小伙伴可能会心生不屑,哼!这人连 Webpack 支持 TreeShaking 了都不知道,也不知道是用那个版本的 Webpack,还在说这些老掉牙的问题,真没意思!

稍等!小斑诞生环境中的 Webpack 早已 4.42.0,彻底支持 TreeShaking,在这里,小斑只想问一个简单的问题:TreeShaking 能解决样式冗余吗?

不能,直到如今为止,依然没有一种较好的办法,在代码打包时,解决样式冗余的问题。但在代码打包前,也就是编写代码的时候,却能够!

一行代码,700K 的体积!

相信使用过 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 核心的其余浏览器)FireFoxSafari ,一些已经成为标准的 CSS3 样式前缀就不必兼容了,所以小斑的兼容性以下:

"browserslist": [
     "last 1 chrome version",
     "last 1 firefox version",
     "last 1 safari version"
 ]

不要问小斑为何不兼容 IE,连它亲爸都不要它了,兼容它干吗!

一番下来,小斑的样式获得了最大程度的精简(由于 Antd 组件内的样式包含了组件的全部样式,会有部分冗余),样式体积骤减,由本来 900K 减至不到 200K!通过 Gzip 发生到客户端的体积,不到 100K

想高亮吗?收下这 1M 代码!

在小斑诞生之初,便已肯定,编辑器必须支持代码高亮,那天然就须要引入 highlight.js,但这句代码比 Antd 的样式代码更加夸张,好嘛!整整 1MJS!看到上图中那个大大的 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 种语言增长了一点点,但换来的倒是 1M300K 打包体积的变化,值吗?很是的值!

image

Δ 移除很是用语言后的代码高亮后的总体打包体积。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 的解析器。但该种方式有个弊端,因为异步函数是有传染性的!会致使一层层的函数都变成异步函数。

React 异步组件

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 的加载时间,那么它以一种后台加载的形式,默默的在用户使用的时候加载好,对用户来讲,也是很是棒的体验了!

所以,对于异步模块,只要知足这两点中的一点,就可使用了:

  1. 首屏用不到的模块;
  2. 须要在必定状况下才显示或使用的模块;

异步模块,虽然不能减小小斑的代码总量,但却能让小斑早一点与大家相见。你说值吗?固然很是的值呀!


缓存:并非全部的代码,都须要再次加载!

熟悉 Webpack 的小伙伴都应该知道,Webpack 拥有代码分类打包的能力,具体到 Webpack4 其实就是 optimization.splitChunks 这一块你们讨论的不少了,复制粘贴官网的文档也没有意义,就很少说了。在这小斑推荐你们结合官网和网上总结一块儿看,由于网络上文章的并不必定是最新的,记录下为何要代码分块(知其因此然比知其然更加剧要!):

  • 浏览器有本身的缓存策略,分为强缓存和协商缓存,缓存生效的前提在于内容不变;
  • 因为项目代码并不是一层不变,所以为了不缓存致使的问题,须要生成版本号;
  • 因为版本号的不一样,浏览器的缓存策略失效了;
  • 但项目的公共代码(好比说 UI 库)实际上是能够缓存的;
  • 若是这些公共代码分包成一个单独的 bundle 不添加版本号,就能被浏览器缓存了!
  • 但公共代码也会变呀!好比说修复漏洞,增长新功能等!
  • 那仍是得生成版本号!若是根据打包出来的内容生成版本号,问题迎刃而解!

分包,说到底,是让浏览器缓存生效的一种策略,若是说全部的代码都在一个 bundle 中,为了不缓存致使的影响,不得已须要添加版本号,但分包以后就不一样了,即便版本须要更新,一些包的内容其实没有发生变化,打包出来的文件名也就不会变化,这极大的利用了缓存!

image

Δ 经异步加载,内容分包后的最终成果

首屏须要加载的内容块以下:

  • willChange: 一些可能会发生变化的项目依赖,如 antd,由于可能会使用新的组件;
  • library: 固定不会的项目依赖,如 react
  • main: 小斑的核心代码;

未经 Gzip 前,一共:554K + 344K + 338K ≈ 1MGzip 后仅仅 340K 小斑瘦身大获成功!可是,一切并无结束!


终极利器 Service Work:0K!

Service Work 能够简单理解为一个介于客户端和服务器之间的一个代理,客户端的请求会经过 Service Work 对外发送,并获取内容,所以 Service Work 具备了控制返回内容的权利,它能够缓存返回的内容,并在第二次请求的时候直接返回数据,这点和缓存很像,但区别就在于,它是可控的,甚至能够提早获取资源,并缓存!

想像一个具体场景,你的网页发版了,用户焦急的等待着浏览器下载新版网页的资源,但一旁使用 Service Work 的网站依旧瞬间打开了老版页面,开始浏览了起来,看起来好像你隔壁的网页没更新。但次日,你却发现隔壁的网页,不只更新到了最新版,并且依旧瞬间打开!做为一个积极向上的开发者,我想,你应该理解到了这在用户体验上的差距!

Service Work 能够检查更新,输出原先内容的同时,下载最新内容,并在合适的时候更新缓存内容,对于用户来讲,除了第一次访问,以后的每一次都是瞬间打开,彻底不须要下载新内容!

这时候有些小伙伴就会问了:这么牛逼的技术,必定很难咯!不!它不只不难,还很简单,简单到若是你使用脚手架,都不须要考虑这个问题!

因此,你要作的仅仅是打开脚手架里的 Service Work 选项 ~

最后

啰啰嗦嗦写了将近 6000 字,固然,还有一些好比 JS 的代码压缩插件 TerserPluginCSS 的压缩插件 MiniCssExtractPlugin 之类的不提也罢,都是形式化的东西,这些脚手架已经帮开发者弄好了,不须要考虑,固然了解仍是须要了解的,那就先把 Webpack 文档看一遍吧 ~

虽然文章仅用了几天写成,但具体到小斑的优化过程倒是是一个较长的过程,总结不易,给个 Star 呗。

本篇文章由斑马编辑器编辑并生成,我是小斑,我为本身带盐 ~

相关文章
相关标签/搜索