她来了她来了她带着礼物走来了。不知道大家webpack4玩的怎么样,反正webpack5现在已经beta几十了,就问你什么感觉🤪
提纲:
- 背景
- webpack4
- 新接项目命令及配置的流程变更和规范
- 关于webpack4的更多
- webpack5
- 先总体说下webpack5带来了什么
- 细分
背景
新接的的项目因为构建速度等问题引起了不适,索性在做调整的同时做个记录,另外对近期webpack5的学习也做个记录~
webpack4
新接项目命令及配置的流程变更和规范
新接项目webpack版本:webpack4
分析工具: speed-measure-webpack-plugin
主要分析命令:npm run dev(npm run build可以部分复用)
问题1: npm run dev(npm run build) 执行时间结合项目经验来说感觉挺长的
对命令执行过程进行一波分析后对最初的npm run dev执行时间做了个统计记录:
1.59.16 1.56.31 1.54.34 1.50.17 1.53.64 1.54.71 平均值约 1.54.71(分钟)
【改进1】: 对package.json中的scripts做优化——将npm install 和 真正的webpack构建解耦
改进后npm run dev执行时间:
50.48 47.42 47.98 56.096 46.11 平均值约 49.617(秒)
问题2: 热编译等待时间明显过长
统计数据:
12.583 9.689 9.925 10.137 9.729 平均值约 10.412(秒)
【改进2】: 把相关的ts校验配置修改为异步的
改进后的热编译时间:
306ms 408ms 394ms 252ms 345ms 平均值约 341(毫秒)
问题3: 遇到多次想在本地快速修改一个功能做验证的情况,但稍不留意 书写不规范就会lint不通过热编译出问题,页面加载失败,并且不看终端有时候还不知道问题出在哪,IDE没有提示
大多数时候我就是单纯的想赶紧看下效果做验证,或者灵感来了想快点做验证,这个时候我并不关心这时候修改的代码到底规范不规范,或者即便不规范只要没出错,我也不想被迫立即进行调整,然后等待再次编辑,直到lint成功,这种打断很多时候让人极其不爽。
产生相关问题的一个原因是lint是在webpack的构建过程中执行的。
【改进3】: eslint-loader的配置从webpack中删除,同时删除webpack中ForkTsCheckerWebpackPlugin插件的配置,升级、新增如下包:
1 | // devDependencies |
修改.eslintrc文件:
修改package.json增加如下代码,我们把代码格式化及质量校验过程变为commit的一个前置操作,如果有问题我们也可以统筹进行修改,相对于热编译这个频次就低多了,更重要的是在我们的编程过程不会被随意打断。
1 | "husky": { |
另一个原因就是IDE配置了, 下边我对IDE配置进行一些调整,通过IDE对代码规范进行实时检测,这样在书写代码时我们可以获得直观显式的规范提示及自动格式化修改。我的IDE是webstorm, 如果使用的其他IDE可以参考这里的思路自己搞下:
问题4: npm run dev速度能够再快些吗,毕竟执行的频率还是比较高的
【改进4】:
babel大版本升级到7,去除webpack中ts-loader配置,把ts的编译工作合并到babel中。让ts、es的编译使用同一个编译器。
加上问题3的处理,最终npn run dev时间:
19.67 22.91 22.52 21.39 18.99 平均值约 21.1(秒)
关于webpack4的更多
现在网上有很多webpack4的介绍文章了,不乏一些优秀的内容,有需要的小伙伴可以自行查询有针对性的了解。
两年前我总结过2篇webpack4的文章,这里贴一下供参考:
webpack配置——基础篇
webpack配置——优化篇
webpack5
先总体说下webpack5带来了什么
webpack5 通过更好的代码生成和树摇等来改善包大小。
webpack5 通过持久缓存提高构建性能。
webpack5 使用更好的算法和默认值来改善长期缓存。
webpack5 在不引入任何破坏性变化的情况下清理内部结构。
webpack5 通过现在引入重大更改来为将来的功能做准备,并允许团队尽可能长时间地使用v5。
细分
自动删除 Node.js Polyfills
早期,webpack 的目标是允许在浏览器中运行大多数 node.js 模块,但是模块格局发生了变化,现在许多模块是专门为前端而编写的。在 v4 及以前的版本中,对于大多数的 Node 模块会自动添加 polyfill 脚本(腻子脚本),polyfill 会加到最终的 bundle 中,其实通常情况下是没有必要的。(eg:使用核心模块crypto进行console test, v4就会主动添加 crypto 的 polyfill,但我们运行的代码不需要,这就导致最后的包变大了)
在 v5 中将停止这一行为。在 v5 中,会提示你进行确认。如果确认不需要 node polyfill,按照提示 alias 设置为 false 即可。
(eg: resolve.alias: { crypto: false } ,否则设置上alias eg:const {Buffer} = require(‘buffer’) {resolve: { alias: { buffer: ‘buffer’}}} 这篇文章 说的Node兼容问题挺好的)
迁移时尽可能尝试使用与前端兼容的模块,需要的话可以为 node.js 核心模块手动添加一个 polyfill ,错误消息将提示如何做。
Tree Shaking & sideEffect 优化
v5 能够处理对嵌套模块的 tree shaking。如下代码:(另外v5能处理对 Commonjs 的 tree shaking)
1 | // inner.js |
v5能够处理多个模块之间的关系,当设置了”sideEffects”: false时,一旦发现test方法没有使用,不但删除test,还会删除”./something”
1 | import { something } from './something'; |
Output
webpack 4 默认只能输出 ES5 代码
webpack 5 开始新增一个属性 output.ecmaVersion, 可以生成 ES5 和 ES6 / ES2015 代码。
默认配置将生成ES2015代码,以使构建出来的包体积更小。如果您需要支持较旧的浏览器(例如IE11),可以将其降低为output.ecmaVersion: 5。
优化持久缓存提高构建性能
之前 webpack 总是在第一次构建时输出全部文件,但是监视重新构建时会只更新修改的文件。
此次更新在第一次构建时会找到输出文件看是否有变化,从而决定要不要输出全部文件。
首先简单说 Webpack 中 graph 的概念:
Webpack 在执行的时候,以配置的 entry 为入口,递归解析文件依赖,构建一个 graph,记录代码中各个 module 之间的关系。 每当有文件更新的时候, 递归过程会重来,graph 发生改变。
如果简单粗暴地重建 graph 再编译,会有很大的性能开销。 Webpack 利用缓存实现增量编译,从而提升构建性能。
缓存(内存 / 磁盘两种形式)中的主要内容是 module objects,在编译的时候会将 graph 以二进制或者 json 文件存储在硬盘上。每当代码变化、模块之间依赖关系改变导致 graph 改变时, Webpack 会读取记录做增量编译。
之前可以使用 loader 设置缓存:
- 使用 cache-loader 可以将编译结果写入硬盘缓存,Webpack 再次构建时如果文件没有发生变化则会直接拉取缓存
- 还有一部分 loader 自带缓存配置,比如 babel-loader,可以配置参数 cacheDirectory 使用缓存,将每次的编译结果写进磁盘(默认在 node_modules/.cache/babel-loader 目录)
v5有一个文件系统缓存配置。 它是可选的,可以通过以下配置启用:
1 | cache: { |
注: 对大部分 node_modules 哈希处理以构建依赖项,代价昂贵,还降低 Webpack 执行速度。为避免这种情况出现,Webpack 加入了一些优化,默认会跳过 node_modules,并使用 package.json 中的 version 和 name 作为数据源。
优化算法确定moduleId&chunkId改善长期缓存
原来的moduleId和chunkId默认都是自增的 – 0.js,1.js,2.js,一方面开发环境是不可读的,另一方面随着模块、entry的增删,这些数字在生产环境是动态变化的,容易导致文件缓存失效。
1 | // src/async.js |
- v5在开发模式下默认启用新命名块ID算法,将为块(和文件名)提供易于理解的名称。moduleId 根据上下文模块路径,chunkId 根据 chunk 内容计算。
(迁移:如果您不喜欢上述在开发中被更改的文件名,可以通过chunkIds: “natural”使用旧的数字模式。) - v5在生产模式下默认启用用于长期缓存的新算法:chunkIds: “deterministic”, moduleIds: “deterministic”,该算法以确定性方式将短(3或4个字符)数字ID分配给模块和块。这是包大小和长期缓存之间的折衷方案。
(迁移:生产环境最好使用默认值chunkIds和moduleIds。您还可以选择使用旧的默认设置chunkIds: “size”, moduleIds: “size”,这将生成较小的包,但使它们更频繁地失效以进行缓存。)
所以上边:
npm run dev 后v5会在dist中生成src_async_js.js,
npm run prod 后v5会在dist中生成一个以数字开头的js文件。比如61.js(增强long-term caching )
1 | /*Webpack4生产环境的默认配置。*/ |
1 | /* Webpack5生产环境的默认配置。 |
编译器的优化
在新的版本中,编译器在使用完毕后应该被关闭,因为它们在进入或退出空闲状态时,拥有这些状态的 hook。 插件可以用这些 hook 来执行不太重要的工作(比如:持久性缓存把缓存慢慢地存储到磁盘上)。同时插件的作者应该预见到某些用户可能会忘记关闭编译器,所以 当编译器关闭所有剩下的工作时应尽快完成。 然后回调将会通知已彻底完成。 当你升级到 v5 时,请确保在使用 const compiler = webpack(…);后,调用 compiler.close(); 关闭编译器。
minSize&maxSize 更好的方式表达
与显示单个数字相比,模块现在以更好的方式表示尺寸。此外,现在有不同类型的尺寸。
SplitChunksPlugin现在知道如何处理这些不同的大小,并将它们用于minSize和maxSize。默认情况下,仅javascript处理大小,但是您现在可以传递多个值来管理它们:
1 | minSize:{ |
迁移:检查构建中使用了哪些尺寸类型,并在其中配置splitChunks.minSize和splitChunks.maxSize(可选)。
webpack5相关参数的默认值
1 | entry: "./src/index.js" |
弃用
那些在v4中已经被抛弃但是仍然可以被使用的特性,将再v5中彻底被废弃。所以在迁移到v5时,请留意那些在v4中抛出“弃用警告”(deprecation warnings)的提示。
有一些东西也被删除了,但是v4中没有像IgnorePlugin和BannerPlugin这样的弃用警告,现在必须传递一个options对象。下面是一个可以用于IgnorePlugin的例子,因为当前文档似乎并未对此进行概述:
1 | new webpack.IgnorePlugin({ resourceRegExp: regex }) |
重大变更:Module Federation
可以让跨应用间真正做到模块共享。
让 Webpack 达到了线上 runtime 的效果,让代码直接在独立应用间利用 CDN 直接共享,不再需要本地安装 NPM 包、构建再发布了!
Webpack 认同多个单独的构建应能够构成一个应用。 这些独立的构建不相互依赖,因此可以单独开发和部署。
让应用具备模块化输出能力,其实开辟了一种新的应用形态,即 “中心应用”,这个中心应用用于在线动态分发 Runtime 子模块,并不直接提供给用户使用。
可以看下这篇文章:联邦模块
参考:
changelog-v5