今年4月,我们发布了一系列致力于负责任的JavaScript开发方法的
第一本材料的译文。 作者在那里回顾了现代网络技术及其合理使用。 现在,我们为您提供本系列第二篇文章的翻译。 它致力于有关Web项目设计的一些技术细节。

有个主意
您和您的团队热情地提出了对公司老化的网站进行彻底检查的想法。 您的要求已到达领导层,甚至在最高层也已出现。 您被授予绿灯。 您的团队热情工作,吸引了设计师,撰稿人和其他专家。 不久,您推出了新代码。
它的工作天真地开始了。
npm install
命令在这里,
npm install
命令在这里。 当您环顾四周时,就已经建立了生产依赖关系,就好像项目开发是一场疯狂的狂欢一样,而您却根本不在乎明天早晨的情况。
然后,您开始了。
但是,与最疯狂的酒会的后果不同,可怕的事情并没有在第二天早晨开始。 不幸的是-不是第二天早上。 数月来就算了。 对于公司所有者和中层管理人员,她采取了一种令人不愉快的轻度恶心和头痛的方式,他们想知道为什么在新站点启动后,转换和收入下降了。 然后,灾难开始了。 发生这种情况的原因是技术总监从周末返回时,他在城外的某个地方度过。 他想知道为什么公司网站在手机上的加载速度如此之慢(如果有的话)。
过去对所有人都有好处。 现在其他黑暗的时刻到了。 消耗大量JavaScript后,满足您的第一个宿醉。
这不是你的错
当您尝试应对地狱般的宿醉时,“我告诉过您”之类的词听起来应该是您应受的谴责。 而且如果您当时能够战斗,那么它们可以作为战斗的机会。
当涉及鲁re使用JavaScript的后果时,您可以责怪一切。 但是,寻找有罪感是浪费时间。 现代Web设备本身要求公司比竞争对手更快地解决问题。 这种压力意味着我们努力尽可能提高生产率,很可能会抓住一切。 这意味着我们将以很高的概率(尽管不能将其称为不可避免)来创建应用程序,其中存在许多多余的应用程序,并且很可能会使用损害应用程序性能和可用性的模式。
Web开发并非易事。 这是一项长期的工作。 第一次尝试时执行得很少。 但是,这项工作的最好之处在于,我们不必在一开始就完美地完成所有工作。 项目启动后,我们可以对其进行改进,实际上,本材料专门针对此,这是有关负责任的JS开发方法的系列文章中的第二篇。 完善是一个非常遥远的目标。 同时,让我们通过改进
脚本来应对JavaScript的困扰。
在不久的将来出现在网站上。
我们处理常见问题
这似乎是解决问题的机械方法,但首先要仔细阅读一系列典型的问题以及解决这些问题的方法。 在大型开发团队中,这些事情通常被遗忘。 对于使用多个存储库或不为他们的项目使用优化模板的团队而言,尤其如此。
▍应用摇树算法
首先,检查您的工具是否配置为实现
树抖动算法。 如果您以前没有遇到过这个概念,请看一下
我去年编写的这份材料。 如果简而言之解释该算法的操作,可以说由于在应用程序的生产程序集中使用了该算法,因此不包括那些虽然已导入到项目中但未在其中使用的包。
摇树算法的实现是
webpack ,
Rollup或
Parcel等现代
捆绑器的标准功能。
咕unt 咕的是任务管理器。 他们不这样做。 与捆绑器不同,任务管理器不会创建
依赖图 。 使用必要的插件,任务管理器将对传输到它的文件执行单独的操作。 任务管理器的功能可以使用插件扩展,使他们能够使用捆绑程序处理JavaScript。 如果在这个方向上扩展任务管理器的功能似乎是一个问题,那么您可能需要手动检查代码库并从中删除未使用的代码。
为了使树抖动算法有效地工作,必须满足以下条件:
- 应用程序代码和已安装的软件包应作为ES6模块提供 。 对CommonJS模块使用摇树算法几乎是不可能的。
- 捆绑程序不应在项目构建期间将ES6模块转换为其他格式的模块。 如果在使用Babel的工具链中发生这种情况,则@ Babel / present-env必须具有以下模块:false设置。 这将导致ES6代码不会转换为使用CommonJS的代码。
如果突然之间在构建项目时未应用树抖动算法,则包含此机制可以改善这种情况。 当然,该算法的有效性因项目而异。 另外,其使用的可能性取决于导入的模块是否有
副作用 。 这可能会影响打包机摆脱在程序集中包含不必要的导入模块的能力。
the将代码分成几部分
您很可能已经在使用某种形式的
代码分离 。 但是,您应该检查如何完成。 无论您如何分离代码,我都想向您提出以下两个非常重要的问题:
- 您是否从输入点中删除重复的代码 ?
- 您是否懒惰地加载可以通过动态导入以这种方式加载的所有内容?
这些问题很重要,因为减少冗余代码的数量是至关重要的性能要素。 延迟加载代码还可以减少页面中加载的JavaScript代码的数量,从而提高性能。 如果我们谈论项目中是否存在冗余代码的分析,那么您可以使用诸如Bundle Buddy之类的工具。 如果您的项目对此有疑问,此工具将让您知道。
Bundle Buddy工具可以检查webpack的编译信息,并找出捆绑中使用了多少相同的代码如果我们谈论的是惰性加载材料,那么找出在哪里寻找应用此优化的机会可能会有些困难。 当我研究现有项目使用延迟加载的可能性时,我会在代码库中查找那些涉及用户与代码交互的地方。 例如,它可以是鼠标或键盘事件处理程序,以及其他类似的东西。 任何需要某些用户操作才能运行的代码都是向其应用dynamic
import()
命令的理想选择。
当然,按需加载脚本会带来将系统移至交互模式时明显延迟的风险。 实际上,在程序可以与用户进行交互之前,您需要下载适当的脚本。 如果传输的数据量不打扰您,请考虑使用
rel = prefetch资源提示加载此类低优先级脚本。 这样的资源将不会与关键资源争夺带宽。 如果用户的浏览器
支持 rel=prefetch
,则使用此工具提示将仅是有益的。 如果没有,则不会发生任何不良情况,因为浏览器只是忽略了他们不理解的标记。
▍使用webpack外部选项标记外部服务器上的资源
理想情况下,您应该在自己的服务器上托管尽可能多的站点依赖项。 如果出于某种原因,您(无选择)不得不从其他人的服务器下载依赖项,则将它们放在webpack设置的
externals块中。 如果不这样做,则可能意味着您网站的访问者将同时下载您托管的代码和从其他人的服务器下载的相同代码。
看一下这种情况可能会损害您的资源的假设情况。 假设您的站点从公共CDN资源下载Lodash库。 您还为本地开发目的在项目中安装了Lodash。 但是,如果您未在Webpack设置中指定Lodash是外部依赖项,那么您的生产代码将从CDN加载该库,但与此同时,它将被包含在服务器上托管的捆绑软件中。
如果您熟悉捆绑器,那么所有这些对您来说似乎都是司空见惯的事实。 但是我看到了这些事情并没有引起人们的注意。 因此,不要花时间仔细检查您的项目是否存在上述问题。
如果您认为不必自己托管由第三方开发人员创建的依赖项,则可以考虑对它们使用
dns-prefetch ,
preconnect或什至
预加载提示。 这样可以减少
TTI (互动时间,网站首次互动时间)得分。 而且,如果需要JavaScript功能来显示网站内容,则
还需要网站的
Speed Index 。
较小的备用库并减少了用户系统的开销
所谓的“
Userland JavaScript ”(用户开发的JS库)似乎是个淫秽的大型糖果店。 所有这些开源的宏伟和多样性激发了我们开发人员的敬畏精神。 框架和库使我们能够扩展应用程序,并迅速为它们配备有助于解决各种问题的功能。 如果我们必须自己实现相同的功能,则将花费大量时间和精力。
尽管我个人提倡在项目中尽量减少使用客户端框架和库,但我不得不承认它们的巨大价值和实用性。 但是,尽管如此,当在项目中安装新的依赖项时,我们应该对每个依赖项都持相当怀疑的态度。 如果我们已经创建并启动了某些东西,其操作取决于许多已安装的依赖项,那么这意味着我们要忍受所有这一切创建的系统上的额外负载。 可能只有程序包开发人员才能通过优化其开发来解决此问题。 是这样吗
也许是这样,但也许不是。 这取决于所使用的依赖项。 例如,React是一个非常流行的库。 但是
Preact是React的
非常小的替代品,它为开发人员提供了几乎相同的API,并且仍然与许多React插件兼容。
Luxon和
date- fns是
moment.js的替代品,它比这个库
小得多 ,它紧凑得多。
在
Lodash之类的
库中,您可以找到许多有用的方法。 但是其中一些很容易用标准的ES6方法替换。 例如,可以将Lodash
紧凑方法替换为标准
过滤器数组方法。
许多其他Lodash方法也可以用标准方法安全地替换。 这种替换的优点是,我们获得了与使用库相同的功能,但摆脱了相当大的依赖性。
无论您使用什么,总的想法都保持不变:询问您的选择是否有更紧凑的选择。 了解您是否可以使用标准语言工具解决相同的问题。 可能需要付出很少的努力来认真减少应用程序的大小以及它施加给用户系统的不必要负载的数量,您可能会感到惊喜。
使用脚本差异加载技术
Babel很可能在您的工具链中。 该工具用于将符合ES6的源代码转换为旧版浏览器可以运行的代码。 这是否意味着我们注定要在不需要所有旧浏览器的浏览器完全消失之前,甚至向不需要它们的浏览器提供巨大的捆绑包?
当然不是 ! 差异资源加载通过基于ES6代码创建两个不同的程序集来帮助解决此问题:
- 第一个程序集包括您的网站在旧版浏览器中运行所需的所有代码转换和polyfill。 可能现在您正在为客户提供此特定的程序集。
- 第二个程序集要么包含最少的代码和polyfill转换,要么根本不包含它们。 它是为现代浏览器设计的。 这是您可能没有的程序集。 至少还没有。
为了使用装配的差异加载技术,您需要做一些工作。 我将不在这里详细介绍-我将为我的资料提供更好的
链接 ,该资料讨论了实现该技术的一种方法。 所有这一切的本质是,您可以修改构建配置,以便在项目的构建过程中创建站点的JS捆绑包的附加版本。 此额外的捆绑包将小于主要的捆绑包。 仅适用于现代浏览器。 最好的部分是,这种方法可让您优化捆绑包的大小,同时又不牺牲任何项目功能。 根据应用程序代码,节省捆绑包大小可能非常重要。
分析旧版浏览器的捆绑包(左)和新浏览器的捆绑包(右)。 使用webpack-bundle-analyzer完成捆绑研究。 这是此图像的完整尺寸版本。使用以下技巧将不同的捆绑包分配给不同的浏览器是最容易的。 它在现代浏览器中运行良好:
<!-- : --> <script type="module" src="/js/app.mjs"></script> <!-- : --> <script defer nomodule src="/js/app.js"></script>
不幸的是,这种方法有缺点。 IE11等过时的浏览器,甚至Edge 15-18等相对较新的浏览器,都将加载这两个捆绑软件。 如果您准备好接受它-那么请使用此技术,不必担心任何事情。
另一方面,如果您担心旧版本的浏览器必须下载两个捆绑软件,这
会对您的应用程序性能
产生影响 ,您需要提出一些建议。 这是使用脚本注入(而不是我们上面使用的
<script>
标记)解决此问题的一种可能的解决方案。 这样可以避免使用适当的浏览器双重加载捆绑软件。 这是我们正在谈论的:
var scriptEl = document.createElement("script"); if ("noModule" in scriptEl) {
该脚本假定,如果浏览器在
script
元素中支持
nomodule属性,则它将理解
type="module"
构造。 这样可以确保旧版浏览器仅接收针对其设计的脚本,而现代浏览器将接收针对其设计的脚本。 但是,请记住,默认情况下动态嵌入的脚本是异步加载的。 因此,如果加载依赖项的顺序对您很重要,请将
async属性设置为
false
。
少传达
我不会在这里攻击巴别塔。 该工具在现代Web开发中是必需的,但它是一个任性的实体。 Babel在其生成的代码中添加了很多东西,开发人员可能不知道。 因此,如果您查看巴别塔尔的大肠并确切了解他在做什么,您将不会后悔。 特别是,对Babel内部机制的了解清楚地表明,某人编写代码的方式上的微小变化会对Babel生成的内容产生积极影响。
少传达即-这就是我们正在谈论的。 例如,
默认选项是ES6的一个非常方便的功能,您可能已经在使用它:
function logger(message, level = "log") { console[level](message); }
这里值得注意
level
参数,其默认值为字符串
log
。 这意味着,如果我们要使用
logger
包装器函数调用
console.log
,则不需要将
level
传递给该函数。 方便吧? 所有这些都很好-除了Babel在转换此函数时获得的代码之外:
function logger(message) { var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "log"; console[level](message); }
这是一个例子,说明尽管事实上我们怀有良好的意愿,但巴别尔给人的舒适感可能导致负面后果。 源代码中只有几个字符,在程序的生产版本中变成了更长的结构。 - , ,
arguments
.
, ,
? , Babel :
, Babel
@babel/preset-env ,
. , , , , , ! — «» (
true
loose ). — , , , , , . «»
, Babel , .
, «» , , Babel :
, — JavaScript, , .
spread ,
,
.
— :
- — @babel/runtime @babel/plugin-transform-runtime , , Babel .
- , . @babel/polyfill . , babel /preset-env useBuiltins
usage
.
, , , , , . ,
JSX , , , . , , . , , . , Babel — . , Babel. , .
: —
, . , JavaScript-, , . , , . - .
. , , , , , , , .
, , , , , , , . - — . , , , . . , , , , . , , , .
亲爱的读者们! JS-?
