代码体积的控制对前端来说至关重要,尽管网络条件逐渐变好,但是代码体积的增加不仅仅只影响资源加载速度,还会直接或间接影响浏览器各类性能指标。
例如增加用户内存使用消耗,内存的增加又会更频繁的触发 V8 引擎的 GC 机制,进而影响页面交互性能。
本文从一个典型的 Webpack+Babel 工程出发,找到构建产物体积变大的常见原因和对应的解决思路,减少项目代码构建后的体积
babel 最常见的用途就是代码降级,使构建后的代码能够被低版本浏览器兼容,按照功能可以划分两部分
通过 Babel 构建后的代码为了适配低版本浏览器通常会比源代码大上几倍,这里面除了源代码外还包含 API 垫片和语法辅助函数,分别对应上诉的 API 降级和语法降级,我们看下如何减少这部分的代码体积
按照目前最新版本的 babel@7,@babel/polyfill 已经废弃,我们使用 core-js 完成 API 的语法降级
core-js 可以为浏览器中可能不兼容的 API 提供垫片,例如 Promise,Map
import "core-js/modules/es.promise.js";
// 使用降级 API
const promise = Promise.resolve();
在需要降级的 API 调用前 require 对应的 core-js 模块,就可以以污染全局变量或者原型链的方式实现 API 降级
手动插入 core-js 即麻烦又不安全,所以我们可以使用@babel/preset-env
帮助我们自动插入 core-js 模块
@babel/preset-env
根据项目中 browserlist 定义的用户环境,选择性插入垫片代码,减少垫片代码体积
在配置@babel/preset-env 时,useBuiltIns 属性非常重要,有两个值"entry"|"usage"
,分别为全量降级和按需降级
entry 非常直接,首先我们需要手动在代码的第一行import 'core-js'
,在执行编译时,会按照 browserlist 中定义的环境,把可能需要降级的 API 一次性插入并替换到 core-js 声明的位置
开发者不再需要手动插入垫片,但这有个问题,即没有使用的 API 仍然会被打进 bundle 中,由于 ECMAScript 标准的不断发展,core-js 在 g-zip 压缩后也有 50kb 左右的体积,显然还是太大了
当选择 usage 时,babel 会扫描所有需要编译的 JS 代码,根据实际使用到的 API 选择性插入所需垫片
看起来是相比 entry 的更优解,但实际过于理想
node_modules
下的模块不会参与 Babel 编译,仅参与 Webpack 打包,如果此时恰巧某个依赖包里没有声明所需的垫片,那么就可能出现垫片缺失,最终导致线上环境 JS 运行异常。实际上这种情况在混乱的 npm 生态中非常普遍,有不少 npm 包直接使用 tsc 打包,除非开发者手动介入,否则构建产物中就会缺少 API 垫片,遇到这种情况往往只能在线上发现异常后手动添加依赖到babel.include
中进行编译
2. 并不是所有 JS 代码都会参与编译,例如通过一些平台动态下发的脚本,这些平台动态下发的代码完全不经过编译,如果使用了未经降级的 api 也可能会出现 JS 运行异常。
可以看到 entry,usage 都是存在问题的,所以也就有了平台化的方案,polyfill.io。
如果使用最新的现代化浏览器访问该服务,那么返回的 JS 内容则是空的,反之它会响应浏览器所需的降级 API,既控制了包体积,也能确保未经编译的 JS 获得降级 API。
Untitled
出于安全考虑,我们需要自部署服务,目前 polyfill.io 的 node.js 代码是完全开源的,支持自部署,但是实际落地还需要考虑缓存和异常兜底
core-js 是为了解决 API 降级问题存在的,但是我们还有语法降级需要解决,例如 class,async
默认情况下 babel 为了实现 class 功能会生成一些内联辅助函数,例如下图的 createClass
。这会产生一个问题,就是当多个模块都使用 class 语法时则会生成多个相同的辅助函数,辅助函数不能复用
Untitled
我们可以通过注册 babel 插件@babel/plugin-transform-runtime,将硬编码辅助函数的方式改为从@babel/runtime
引入辅助函数,实现不同模块间辅助函数的复用
Untitled
从下图可以看到 createClass
函数从硬编码改为require("@babel/runtime/helpers/createClass")
,代码大幅缩小
Untitled
但是@babel/plugin-transform-runtime
的方案也不是毫无问题,和 api 降级一样,同样面临各种依赖包构建不标准带来的困扰
最大的问题就是没有办法保证依赖包的产物一定使用了@babel/plugin-transform-runtime
进行构建,语法降级使用了内联的辅助函数,又或者使用了老版本的babel-runtime
·,导致项目最终的构建产物对辅助函数进行了多次打包
以相对常见的依赖包构建工具 father-build 和 tsc 为例,他们都没有将语法辅助函数通过@babel/runtime
依赖包进行提取,而是都以硬编码的形式存在每个 JS 模块当中。
这类由社区维护的 npm 包我们不好处理,但是可以通过收敛公司内部构建工具的方式,统一处理公司内部维护的依赖包,使它们构建的产物符合应用打包的需求,我们在文章结尾处再说
tree-shaking 是减少构建产物体积最有效的方式,以常用 lodash 为例,g-zip 后的体积 24kb,但是项目中使用到的函数并不多,如果能够为它启用 tree-shaking,代码体积能控制在 1kb 以内
如何为依赖代码启用 tree-shaking?
sideEffects:false
,告诉 Webpack 整个依赖包没有存在副作用,或者指明存在副作用模块的地址ESM 相比 commonjs 具备静态分析能力, 这是 tree-shaking 的前置依赖条件,所以我们需要 babel 构建我们的源代码时保留 import 语法,不要编译成 commonjs
{
"presets": [
[
"@babel/preset-env",
{
"modules": false // 保留ESM语法
}
]
]
}
为什么依赖包的 package.json 需要声明 sideEffects?
这里需要引申出自函数式编程中的纯函数
和副作用函数
概念,如果我们的代码没有存在任何副作用,tree-shaking 确实可以不需要类似 sideEffects 的副作用声明,但实际上副作用普遍存在我们的代码中,如果只依据函数是否被引用过作为 DCE(Dead Code Elimination) 的条件,很容易影响程序运行的正确性
通过 css-loader 引入 css 文件是很典型的例子
import "./button.css";
对于 webpack 来说 button.css 同样是一个模块,这里没有引用任何的具名函数,但是引入 css 模块是会为我们带来一个副作用,它会为 html 插入一个 style 标签。如果 webpack 认为他是没有副作用的,那么在 minify 阶段 webpack 会删除这行代码,最终导致样式错乱
为了告诉 webpack 这个 css 文件是存在副作用的,不能删除,sideEffects 就可以怎么写
{
"sideEffects": ["*.css", "*.less"]
}
公司内部维护的依赖相比开源社区,很容易忽略sideEffects
的声明,如果存在公司内部的依赖构建工具,可以将sideEffects
添加到相关的模板代码中,默认为依赖包开启 tree-shaking
回到社区现状我们再来看 tree-shaking,lodash 推出了支持 tree-shaking 的lodash-es,antd@4 也不再需要安装babel-plugin-import
插件,可以通过 tree-shaking 的方式原生支持代码按需加载,从而大幅缩小构建体积
依赖重复打包是前端开发中的常见问题,容易出现在公司内部长期无人维护的依赖包中
当我们的项目中存在 Root→C→D@2.0.0,Root→B→D@3.0.0类似的依赖关系时,node_module
结构如下
node_modules
-- C <-- depends on D@2.0.0
-- D@2.0.0
-- B <-- depends on D@3.0.0
-- node_modules
-- D@3.0.0
可以看到在 node_modules
下嵌套安装了 2 个版本的依赖 D,即D@2.0.0
,D@3.0.0
。这可能导致在构建的产物中也同样存在两份相同依赖不同版本的代码,除了会影响代码体积,还可能导致代码运行异常
解决方式是升级 B 的依赖D@2.0.0→D@3.0.0,此时重新安装后node_modules
的嵌套结构会恢复扁平
node_modules
-- C <-- depends on D@3.0.0
-- D@3.0.0
-- B <-- depends on D@3.0.0
我们可以使用find-duplicate-dependencies
和webpack-bundle-analyzer
这些工具辅助我们排查依赖重复打包的问题
回顾文章我们对一个典型前端应用可能影响 Bundle 体积的因素进行了分析,同时提出对应的解决方案。在文章的结尾我们可以更进一步通过工程化和平台化的手段,以相对一劳永逸的方式解决上诉问题
如下图,@company/app-builder
负责构建应用,@company/module-builder
负责构建依赖包,然后通过使用封装的 babel 配置@company/babel-base
,统一处理 JS 编译
Untitled
babel-base
关闭 core-js 的 api 降级,由 app-builder
开启平台 polyfill.io 方案,同时babel-base
开启@babel/plugin-transform-runtime
,为应用和依赖包启用语法辅助函数抽离
module-builder
关闭 ESM 语法的转换,为app-builder
做 tree-shaking 时提供必要前置条件
通过这种方式,我们就可以实现在构建过程中减少代码体积的最佳实践
至于重复依赖的问题,由于必定需要开发者介入做版本选择,所以我们可以考虑在部署平台构建时自动上报 Dependency graph 数据,然后由性能分析等平台将重复依赖的问题邮件抄送给相关开发者进行优化
本文从构建工具的角度,阐述了如何减少构建产物的体积。可以看到仅仅处理应用的构建是不够的,为了实现最佳效果,我们还需要介入公司内部依赖包的构建,使依赖包的构建产物符合应用构建的需求。只有具备全场景的构建能力才能最大程度降低代码的构建体积。
https://docs.npmjs.com/cli/v8/commands/npm-dedupe
https://babeljs.io/docs/en/babel-plugin-transform-runtime
https://babeljs.io/docs/en/babel-preset-env
https://babeljs.io/docs/en/babel-polyfill
https://webpack.js.org/guides/tree-shaking/#root
https://cdn.polyfill.io/v3/
本文由哈喽比特于1年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/BxeOE3iCb1I2YF3HU9z0bA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。