热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

npm如何才能有效减包?

npm如何才能有效减包commonfiles是什么文件?作者:atomliucommonfiles是什么文件

npm 如何才能有效减包common files是什么文件

作者:atomliucommon files是什么文件,腾讯PCG前端开发工程师

| 导语我是如何把一个 npm 包的接入成本从 500kb 降低到几乎为 0kb 和 关于 npm 减包common files是什么文件你所需要知道的大部分信息

前言

十月份的某一天,有同学找到我说, slide 接入统一顶部栏后,整个站点体积增大了将近 500kb?导致合流被阻断了,需要将体积增长控制在 30kb以下才能允许合入主干common files是什么文件。当时我就震惊了,要知道这可是 gzip 后的体积啊,不少网站整站都没有500kb,统一顶部栏是什么玩意竟有如此威力?

腾讯文档各个品类的顶部工具栏在之前都是品类独立维护的,经常会发现各品类之间存在着不少的 功能/文案 差异,最痛的点还是在于如果需要新增一个功能,需要五六个品类都各自重复开发一遍,效率非常低,而且交付标准很容易不一致common files是什么文件。所以有了统一顶部栏服务(@tencent/docs-titlebar-service)(下称为 TB),使用一个 npm 包将顶部栏的大部分功能收归起来统一开发管理,这样只需要一次开发,各品类只需要升级就好了。

腾讯文档各个品类的顶部工具栏在之前都是品类独立维护的,经常会发现各品类之间存在着不少的 功能/文案 差异,最痛的点还是在于如果需要新增一个功能,需要五六个品类都各自重复开发一遍,效率非常低,而且交付标准很容易不一致common files是什么文件。所以有了统一顶部栏服务(@tencent/docs-titlebar-service)(下称为 TB),使用一个 npm 包将顶部栏的大部分功能收归起来统一开发管理,这样只需要一次开发,各品类只需要升级就好了。

从这个 npm 包的功能可以知道common files是什么文件,基本上没有什么新的功能,只是将之前散落在品类中的功能收归到 npm 中而已,到底为什么会导致如此巨大的体积增长呢?

罪魁祸首是什么common files是什么文件? 产物分析工具选择

要回答这个问题,就需要来分析 npm 包产物的产物到底是由什么东西组成,提起产物分析就绕不开 Webpack Bundle Analyzer这个 webpack 插件common files是什么文件。其原理是通过分析 webpack 构建过程产物 state.json 来分析产物组成,网上大多数文章也是介绍的用这个插件来分析产物组成,但是实践过程中发现其分析结果并不准确,导致走了不少弯路,而且其强绑定了 webpack 这个构建工具,如果是使用 rollup 或者其他构建的就无能为力了(当然 rollup 也有自己的体积分析插件 rollup-plugin-visualize)。一番搜索后发现了 source-map-explorer这个工具,只需要产出 sourcemap 就能够分析产物组成,所以可以兼容任何构建工具和构建方式,其分析结果也比提到的更准确详细一些,当然最好还是综合对比多个工具来进行分析.

npm 包的产物体积分析

在没有经过任何优化的情况下common files是什么文件,使用 webpack 构建 titlebar,然后使用 source-map-explorer对其进行产物分析,得到了这样一张触目惊心的体积图,gzip 前的体积达到了 5MB….

npm 如何才能有效减包?

展开全文

其源码体积甚至都没有排上号显示出来,大量的 node_modules 中的依赖被打包进了产物中,右边的 PcHeaderBadge 也是其一个依赖项,只是因为是本地包所以被没有显示在 node_modules 中common files是什么文件

如何解决依赖问题 external 就能够解决问题吗

如何解决这个问题呢?其实说起来也简单,将所有的依赖 external 掉就好了,简单来说就是不「打包」依赖,在产物中保留对依赖的引用语句,这样产物就只有源码的代码common files是什么文件。所以在构建的时候,获取其package.json文件,将 dependencies与 peerDependencies中的依赖都写入 external 配置中,产物最终就是这个样子的:

npm 如何才能有效减包?

来到 mirrors 源上查看一下体积common files是什么文件,从 5MB 降低到了 64KB~

npm 如何才能有效减包?

那这样问题就解决了吗common files是什么文件,其实远没有,因为 TB 从一开始就是这样做的,但就是这样一个看起来体积数据比较优秀的 npm 包,却给整站带来了将近 500kb 的体积增长,到底是为什么呢?

我们不得不再次回过头来common files是什么文件,仔细查看 ppt 提供的体积增长图,其实信息都藏在这个图里面

npm 如何才能有效减包?

可以发现新增项和增大项主要可以分成两部分,一部分是 TB及其node_modules(~300kb),另一部分则是看起来没有直接依赖关系的依赖(~200kb),通过进一步的查看详细的 ppt 的产物分析图可以得知 TB 部分的体积增长全部都是来自于依赖部分(此处图显示有误差),也就是 node_modulescommon files是什么文件。等等上文不是把依赖都 「external」掉了吗,为什么还有这么大的体积呢,要回答这个问题,我们得了解一下「external」到底是如何减少包大小的

为什么 external 可以减包

关于 external 如何降低 npm 包的体积大小上文已经提到过,很容易理解(不将依赖直接打包进产物,而是利用 import / require 这样的语句保持对依赖的引用),那么 npm 包最终在 ppt 中会是如何进行打包的呢?那些 require 语句是如何找到依赖的common files是什么文件

当 ppt 下载 TB 这个 npm 包的时候common files是什么文件,会同时下载其`dependencies到子 node_modules中,而 peerDependencies中的依赖则会被下载到和TB同级的 node_modules中, devDependencies中的依赖则会忽略,比如当 package.json 中是如下的依赖关系时:

"`dependencies`": { "a": "1.0.0", "b": "1.0.0" }, "devDependencies": {}, "`peerDependencies`": { "c": "1.0.0" },

当下载到 ppt 的时候common files是什么文件,会形成如下的目录结构:

--src--node_modules(1)----c----TB------dist------node_modules(2)--------a--------b

TB dist 中的引入语句在 ppt 构建的时候common files是什么文件,会首先去同级的`node_modules(2)中寻找依赖,如果找不到的话就会向上寻找找到 node_modules(1)中,那这不是 external 了个寂寞吗,一样的是会下载依赖和进行打包,别急,上面提到的目录结构是只有 TB 一个包存在的情况,如果有多个 npm 存在,npm 有一个概念叫做 npm dedupe —— 当有「符合版本范围」的依赖多次出现时,会被提升到依赖树的上层,实现共享依赖

a+-- b <-- depends on c@1.0.x| `-- c@1.0.3`-- d <-- depends on c@~1.0.9 `-- c@1.0.10

在这种依赖关系下,npm dedupe 会将依赖树简化成这样,因为 c@1.0.10 符合 b 和 d 对其的版本需求common files是什么文件

a+-- b+-- d`-- c@1.0.10

这样依赖,依赖树中就只存在一份 c了,从而降低「重复打包」,没错 external 并不能神奇的把依赖去除掉,而是通过保留引用语句,然后通过 npm dedupe机制来降低重复打包的次数common files是什么文件。 如果没有 external,在 npm 构建的时候,依赖就已经被打包进产物中了,那就没有丝毫优化空间了。

external 没有彻底

了解了 external 减包的原理common files是什么文件,回过头来看 TB 到底是有什么问题,跑了一下 source-map-explorer很直观的可以看到还有一些依赖被打包了

npm 如何才能有效减包?

排查发现主要是以下几个原因导致的

引用了 common 部分的代码common files是什么文件,而这部分代码的依赖没有并写到TB的 package.json 中,导致 external 不了

有部分使用到的依赖common files是什么文件,并没有写在 package.json 中,而是直接写在了根目录中的 package.json 中

因为 external 是从 package.json 中获取的common files是什么文件,所以字段中的只是@tencent/dui,而实际使用中用到的是 import Modal from '@tencent/dui/lib/components/Modal'这种写法,导致没有匹配上,external 没有生效

第一个问题其实是由于历史原因,之前这个仓库所有的包都是共用一份依赖,在上半年改造成了 pnpm workspace 的仓库,每个子包具有了自己的 package.json,有自己的依赖,但是 common 这个目录因为工作量比较大的原因并没有进行改造,所以还存在着大量的包「直接引用 common 源码」这种不符合 workspace 规范的行为,common 目录中的依赖都还是直接写在根目录的,导致没有 external common files是什么文件。这里选择直接将 common 目录也改造成一个 npm 包,将依赖写入子包中,这样在 commom 这个包构建的时候依赖就会被 external 掉,其他 npm 包通过 npm 包的方式引用 common 中的代码,这样 common 包也就被 external 掉了,这样彻底解决了 common 目录中的依赖打包问题,而且降低了 common 目录的重复编译次数,提高了构建速度。

使用common files是什么文件了不在子包 package.json 中的依赖

同样是由于历史原因,之前的依赖都是写在根目录的,或者新写代码的同学会直接将依赖安装在根目录,这样子代码是能够运行的,因为依赖是会一层一层的向上进查找,但是就会导致 external 失效common files是什么文件。于是写了一个脚本通过 depcheck 这个 npm 包,批量扫描源码中使用到的依赖,然后去根目录下的 package.json 中查找版本,最终写入到子包的 package.json 中

带路径的依赖包需要单独的 external

第三个问题好解决,为有类似这种用法的依赖包,单独写上一条正则匹配,匹配上所有以其包名为开头的的引入语句就好了common files是什么文件

// 从 package.json 中获取依赖, 用于 rollup 的 externalconst getExternal = (path: string) => { const packageJson = require(path); return [ ...Object.keys(packageJson.`dependencies` || {}), ...Object.keys(packageJson.`peerDependencies` || {}), ...Object.keys(packageJson.devDependencies || {}), /@babel\/runtime/, /^\@tencent\/docs-design-resources/, /@tencent\/dui/ ];};

这样几个方向的改造过后,所有依赖都被 external 了,为后续的优化打下了坚实的基础common files是什么文件

不规范的依赖写法

在经过上节的一些改造之后common files是什么文件,在查看 TB 的产物分析图可以发现确实没有相关依赖被打包进来了,但是在查看 ppt 的产物分析图会发现,还是有一些包最终被重复打包了,分析发现问题还是出在依赖的写法上:

存在一个依赖同时写在 dependencies与 peerDependencies中,在 npm 中,其会被视为 dependencies下载到 ts 的 node_moduls 中,失去了 peerDependencies的意义,可能造成重复打包common files是什么文件。而在 pnpm@7.9.2 以上的版本才会被优先视为 peerDependencies

peerDependencies写了具体的版本common files是什么文件,导致在 npm 判断已有的依赖不符合其版本需求,下载了多份依赖,造成多版本的重复打包

dependencies中使用到的依赖版本与其他 npm 包或者 ppt 自己使用到的依赖版本不一致common files是什么文件,导致依赖没有被 dedupe,造成多版本打包

dui 系列包一直没有发布正式版本(1.0.0)common files是什么文件,在 npm 的理解中,1.0.0 版本之前每一个 patch 版本都有可能是不兼容的,所以对这种包是不会使用 npm dedupe 策略来复用的,所以有大量 dui 的重复打包

要解决这些问题common files是什么文件,也只能规范写法

package.json 中 dependencies的依赖版本号应该带上 ^(没有已知的兼容问题下)common files是什么文件,让其能够接受的版本比较宽泛,提高 npm dedupe 的可能性

依赖不要重复写在 peerDependencies和 dependencies中common files是什么文件, dependencies加上 dedupe 机制能够解决大部分重复打包的问题

像 dui 这种包可以使用 "@tencent/dui": ">=0.108.0 <1.0.0"这种版本写法common files是什么文件,强制 dedupe 生效

如果你明确的知道需要写 peerDependencies依赖,也请不要写具体的版本号,可以使用类似 1.x 这种写法,最大程度的复用外部的依赖common files是什么文件。除非你的包确实只能在 "react": ">=16.14.0"这个版本之上运行。不然在能够自动下载 peerDependencies的包管理器中就会被下载多版本的依赖,导致重复打包

npm7 以上的版本会自动安装 peerDependenciescommon files是什么文件,而 pnpm 在截止 7.14.0 的 latest 版本中都不会自动安装,可以通过配置开启

npm7 以上的版本会自动安装 peerDependenciescommon files是什么文件,而 pnpm 在截止 7.14.0 的 latest 版本中都不会自动安装,可以通过配置开启

ppt 增大的体积中common files是什么文件,不少都不是 TB 的直接依赖,通过分析发现其实是其依赖 workbench 的子依赖,为什么子依赖的体积会增多,通过经验分析应该是由于 TB 产出的是 cjs 产物,而 trees haking 只有在 esm 格式下才能生效,导致全量引入了 workbench

esm 是“Ecma module”的缩写,cjs 是“CommonJS module”的缩写,各个模块之前的定义和详细区别可以看 各模块类型详解,这里不赘述了common files是什么文件。简而言之,早期各个端使用不同的模规范,而CJS 是node原有使用的规范。ESM 是为了统一各端模块标准而推出的新标准,但是在各端的支持度不太一样。现代浏览器中基本可以无脑使用,不过为了兼容老旧的浏览器webpack 等构建工具一般都会将其转化为 CJS 再供浏览器使用,而 vite 在开发模式下就是原生输出 ESM 文件给浏览器使用。最重要的是 tree shaking只有才 ESM 中才会生效。而node环境下的 ESM 支持 仍然有一些问题,所以为了通用性考虑,目前仍然建议大家库产出 CJS 产物。 – – 来自「应该如何打包一个 ts 库」

esm 是“Ecma module”的缩写,cjs 是“CommonJS module”的缩写,各个模块之前的定义和详细区别可以看 各模块类型详解,这里不赘述了common files是什么文件。简而言之,早期各个端使用不同的模规范,而CJS 是node原有使用的规范。ESM 是为了统一各端模块标准而推出的新标准,但是在各端的支持度不太一样。现代浏览器中基本可以无脑使用,不过为了兼容老旧的浏览器webpack 等构建工具一般都会将其转化为 CJS 再供浏览器使用,而 vite 在开发模式下就是原生输出 ESM 文件给浏览器使用。最重要的是 tree shaking只有才 ESM 中才会生效。而node环境下的 ESM 支持 仍然有一些问题,所以为了通用性考虑,目前仍然建议大家库产出 CJS 产物。 – – 来自「应该如何打包一个 ts 库」

由于涉及到 less文件的处理,所以选择使用了 rollup 来打包 esm 产物,支持同时产出 esm 与 cjs 格式的产物(因为 bundless 的打包方式不太好处理 less文件)common files是什么文件。同时将 package.json 中 main 字段指向 cjs 产物,module 字段指向 esm 产物。如果是没有样式文件的包,更加推荐使用 bundless 的构建工具(tsc,father),进行 file to file 的构建,保持目录结构,同时在 package.json 中配置上 sideEffects,提供最佳的 tree shaking 效果。

再来看下产物分析结果common files是什么文件,猜测果然正确,与workbench相关的依赖全部都去除掉了~

处理一些巨石依赖

通过前面 external 的章节我们可以得知,external 并不是银弹,并不是把依赖 external 之后就万事大吉了,应该有一些还是会在最终构建中被打入产物中,所以一些非常典型的巨石依赖同样也需要处理common files是什么文件

lodash 很容易被全量引入 common files是什么文件,可以切换成使用 lodash-es,esm 版本的lodash,可以完美的 tree-shaking 掉没有使用到的函数,如果存量的代码太多难以修改的话,也可以通过配置 babel-plugin-lodash来实现「按需引用」,并不推荐使用 lodash.get 这种子包,原因 官方有提到,且在下个大版本中将会被删除

dui 系列包并没有 esm 格式的产物common files是什么文件,无法被 tree shaking,所以同样推荐使用 babel-plugin-import 插件来实现「按需引用」

moment 这个库很大common files是什么文件,且没有提供很好的维护,导致没法减包,可以切换成 dayjs 来降低体积,其体积降低了98%以上,且api完全兼容

npm 如何才能有效减包?

Dynamic import

通过上文的一些手段common files是什么文件,将绝大部分的外部依赖都去除掉了(这个图并不是完全按照文章顺序来优化的,所有还有一些 dui 的问题

npm 如何才能有效减包?

剩下的大头主要是 @tencent/docs-scenario-components-certification`@tencent/docs-scenario-components-folder-selector与 @tencent/docs-scenario-components-pc-header-badge这几个内部依赖包,所以对他们进行了上述的类似改造,优化了一波体积,但是还是有 100 多kb体积,因为这两个包全局确实只有一份,没办法 dedupe(去重)common files是什么文件

不过这几个包之前也有用到,按道理来说不是新增的才对,后来询问得知,ppt 的产物体积对比只是分析了同步js,而这几个依赖在以前是通过 Dynamic imports 导入的,所以没有记录在体积数据中common files是什么文件。而 TB在之前接入过程中发现使用 Dynamic imports 的时候(webpack4 cjs),会导致 ppt 构建报错(wenpack4),一时没有排查到原因,所以只能同步的引用这几个包。 知道原因了就很简单,因为现在已经切换成了 rollup 构建的 esm 产物,理论上是完美支持 Dynamic imports 的,果然很顺利的切换成了 Dynamic imports,可以看到基本上只留下了一些零碎的依赖。

npm 如何才能有效减包?

css 文件应该单独打包成文件吗

我的结论是应该独立成css文件common files是什么文件,让外部单独引用,理由如下:

降低 js 大小common files是什么文件,同时使得外部可以通过工具去除未使用的 css,压缩效果也更好

使得 app 可以通过 head style 提前加载 csscommon files是什么文件,避免样式跳动

降低 js 运行的耗时

ts 会使用一些「兼容性语法」来把你的高级语法转变成低级语法common files是什么文件,默认是会直接写入到代码中,开启 importHelpers 这个选项之后可以变成从 tslib 中导入,避免「兼容性语法」被重复打包多次(文件级),tslib 同样可以被 dedupe 降低重复打包次数

作为 npm 包,可以不需要将 corejs 打包进产物中,不过这一块相当的复杂,本文也不详细探讨了,作为参考,可以看看 antd 的构建配置,谨慎调整common files是什么文件

在这次减包过程中common files是什么文件,处理了非常多的问题,但是这个仓库参与开发人数比较多,没办法向每一个开发同学来宣导这些注意事项,如果没有办法告诉大家要怎么做,没几个星期就会被打回原形,所以需要配合工具来避免仓库持续腐坏掉:(其中的 eslint 检查都是增量检查,仅避免问题增多)

经过上文我们可以发现,关于减包,我们应该站在品类的视角来看待common files是什么文件。只关注 npm 包的大小是不够的,因为此时这个 js 的体积是不完整的(还保留着对依赖的引用关系),最终应该以品类中的「打包大小」来衡量才是更加真实的数据,不然就会造成TB这种看起来只有几十kb,最终导致品类增大了几百kb的“事故”。经过一系列的优化,最终将接入统一顶部栏对于 ppt 品类的体积影响降低到了几乎为 0

回顾一下减包要注意的一些地方

产出 esm 产物common files是什么文件,使品类可以 tree shaking,避免依赖被全量引入

使用common files是什么文件了样式文件

rollup 产出 esm && cjs 产物common files是什么文件,因为 bundless 的打包方式不能很好的处理 less文件

纯 ts 包

bundless 打包common files是什么文件,同时配置上 sideEffects,提供最佳的 tree shaking 效果

external 所有依赖common files是什么文件,是 dedupe 机制生效避免重复打包的必要条件

注意依赖版本的写法common files是什么文件,没有兼容性问题存在的情况下,可以使用版本要求比较宽松的 ^ 写法

带路径使用的依赖需要单独使用正则写 external 语句

不要混用 dep 和 peerdepcommon files是什么文件,不要滥用 peerdep,合理配置的 dep 就可以实现减包,peerdep 只会让依赖安装更加的复杂

确定要用 peerdep 的时候common files是什么文件,也不要写死版本,请用1.x这种写法

lodash / dui 这种用到的内容占总体积比较小的依赖common files是什么文件,优先使用其 esm 格式的产物,利用好 tree shaking 去除没有用到的代码,没有 esm 产物的配合 babel 插件实现按需引用

改不了的顽固分子就换common files是什么文件,典型的就是 moment->dayjs

css 文件总是应该单独打包common files是什么文件,优点上文提到过了,这也是 vite 默认的打包行为

开启 tsconfig 的 importHelpers 选项common files是什么文件,可以不要打包 corejs

使用common files是什么文件了样式文件

rollup 产出 esm && cjs 产物common files是什么文件,因为 bundless 的打包方式不能很好的处理 less文件

纯 ts 包

bundless 打包common files是什么文件,同时配置上 sideEffects,提供最佳的 tree shaking 效果

rollup 产出 esm && cjs 产物common files是什么文件,因为 bundless 的打包方式不能很好的处理 less文件

bundless 打包common files是什么文件,同时配置上 sideEffects,提供最佳的 tree shaking 效果

注意依赖版本的写法common files是什么文件,没有兼容性问题存在的情况下,可以使用版本要求比较宽松的 ^ 写法

带路径使用的依赖需要单独使用正则写 external 语句

不要混用 dep 和 peerdepcommon files是什么文件,不要滥用 peerdep,合理配置的 dep 就可以实现减包,peerdep 只会让依赖安装更加的复杂

确定要用 peerdep 的时候common files是什么文件,也不要写死版本,请用1.x这种写法

全文终common files是什么文件,感谢阅读,有任何错误欢迎指出交流~


推荐阅读
author-avatar
贝贝不离
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有