以前写的《webpack入门必备(一):基础配置》主要介绍了webpack基础解析所需的loader/plugin。而随着平常webpack的使用,咱们会更多关注如何构建更快、构建产物更小、构建产物符合规范...但愿这篇文章可让你找到答案。
这里介绍的主要的几种优化配置以下所示:javascript
缩小构建范围css
多进程html
缓存前端
若是你有没用过的配置能够接着看下面的具体使用方法,若是你已经很熟悉了则能够跳过此节~java
配置来确保转译尽量少的文件(exclude 的优先级高于 include)
const rootDir = process.cwd(); { test: /\.(j|t)sx?$/, include: [path.resolve(rootDir, 'src')], exclude: [ /(.|_)min\.js$/ ], }
PS. 相比exclude能够多用includenode
若是一些库不依赖其它库的库,不须要解析他们,能够引入来加快编译速度。
noParse: /node_modules\/(moment|chart\.js)/
忽略第三方包指定目录。 (他是webpack 内置的插件)
例如: moment (2.24.0版本) 会将全部本地化内容和核心功能一块儿打包,咱们就可使用 IgnorePlugin 在打包时忽略本地化内容(语言包),见下图。react
plugins: [ // 表示忽略moment下的locale文件夹内容 new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/) ]
把 thread-loader 放置在其它 loader 以前,那么它以后的 loader 就会在一个单独的 worker 池中运行。
// 项目中babel-loader通常耗时比较长,因此能够配置thread-loader rules: [ { test: /\.jsx?$/, use: ['thread-loader', 'cache-loader', 'babel-loader'] } ]
运行在Node.js上的webpack是单线程,将文件解析的任务拆分由多个子进程并发进行,而后子进程处理完任务后再将结果发送给主进程,提高项目构件速度。
(可是由于进程的分配和管理也须要时间,因此使用后不必定快,须要项目接入实验一下)
const Happypack = require("happypack"); module.exports = { module: { rules: [ { test: /\.js[x]?$/, use: "Happypack/loader?id=js", include: [path.resolve(__dirname, "src")], }, { test: /\.css$/, use: "Happypack/loader?id=css", include: [ path.resolve(__dirname, "src"), path.resolve(__dirname, "node_modules", "bootstrap", "dist"), ], }, { test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2|.gexf)$/, use: "Happypack/loader?id=file", include: [ path.resolve(__dirname, "src"), path.resolve(__dirname, "public"), path.resolve(__dirname, "node_modules", "bootstrap", "dist"), ], }, ], }, plugins: [ new Happypack({ id: "js", //和rule中的id=js对应 //将以前 rule 中的 loader 在此配置 use: ["babel-loader"], //必须是数组 }), new Happypack({ id: "css", //和rule中的id=css对应 use: ["style-loader", "css-loader", "postcss-loader"], }), new Happypack({ id: "file", //和rule中的id=file对应 use: [ { loader: "url-loader", options: { limit: 10240, //10K }, }, ], }), ], };
在性能开销较大的loader处使用,将构建结果缓存中磁盘中。
(默认存在node_modueles/.cache/cache-loader目录下。 )
cacheDirectory例子:webpack
rules: [ { test: /\.(j|t)sx?$/, use: [ { loader: 'babel-loader', options: { cacheDirectory: true, }, } } ]
cache-loader例子:es6
rules: [ { test: /\.(css)$/, use: [ { loader: 'style-loader' }, { loader: 'cache-loader' }, { loader: 'css-loader' }, { loader: 'postcss-loader' } ] } ]
将复用性较高的第三方模块打包到DLL中,再次构建时直接复用,这样只需从新打包业务代码。
(注意是DLL缓存是大大缩短了首次构建时间
,像以前的cache-loader优化都是缩短rebuild时间
)
使用相关插件:web
具体步骤:
(1) 新增一个webpack配置去编译DLL文件([name].dll.js
、[name].manifest.json
)
// 新增一个webpack-dll.config.js配置文件 const path = require('path'); const DllPlugin = require('webpack/lib/DllPlugin'); const distPath = path.resolve(__dirname, 'dll'); module.exports = { entry: { // 把 React 相关模块的放到一个单独的动态连接库 react: ['react', 'react-dom'], // 把项目须要全部的 polyfill 放到一个单独的动态连接库 polyfill: [ 'core-js/fn/object/assign', 'core-js/fn/object/entries', ... ], }, output: { // 输出的动态连接库的文件名称,[name] 表明当前动态连接库的名称(react 和 polyfill) filename: '[name].dll.js', path: distPath, // 存放动态连接库的全局变量名称,例如对应 react 来讲就是 _dll_react // 之因此在前面加上 _dll_ 是为了防止全局变量冲突 library: '_dll_[name]', }, plugins: [ // 接入 DllPlugin new DllPlugin({ // 动态连接库的全局变量名称,须要和 output.library 中保持一致 // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值(_dll_react) name: '_dll_[name]', context: process.cwd(), // 描述动态连接库的 manifest.json 文件输出时的文件名称 path: path.join(__dirname, 'dll', '[name].manifest.json'), }), ], };
// package.json里新增dll的构建命令 "scripts": { "dll": "webpack --config webpack-dll.config.js", }
(2) dev构建时,告诉 Webpack 使用了哪些动态连接库
// webpack.config.js文件 const DllReferencePlugin = require('webpack/lib/DllReferencePlugin'); plugins: [ // 使用的动态连接库(react和polyfill的) new DllReferencePlugin({ context: process.cwd(), manifest: path.join(rootDir, 'dll', 'react.manifest.json'), }), new DllReferencePlugin({ context: process.cwd(), manifest: path.join(rootDir, 'dll', 'polyfill.manifest.json'), }), ... ]
(3) html template里引入文件
由于我这里只是本地构建加速,因此就以dev的方式引入
<script src="./dll/polyfill.dll.js?_dev"></script> <script src="./dll/react.dll.js?_dev"></script>
到这DLL就配好了。有些人可能比较好奇react.dll.js
和react.manifast.js
究竟是什么文件,作了什么事?你看看他两个文件就知道啦~
react.dll.js
其实主要就是所引用模块的代码集合react.manifast.js
则写明包含哪些模块、模块路径// react.dll.js文件部份内容以下所示。 var _dll_react = (function(modules) { // ... 此处省略 webpackBootstrap 函数代码 }([ function(module, exports, __webpack_require__) { // 模块 ID 为 0 的模块对应的代码 }, function(module, exports, __webpack_require__) { // 模块 ID 为 1 的模块对应的代码 }, // ... 此处省略剩下的模块对应的代码 ])); // react.manifast.js文件部份内容以下所示。 { // 描述该动态连接库文件暴露在全局的变量名称 "name": "_dll_react", "content": { "./node_modules/process/browser.js": { "id": 0, "meta": {} }, // ... 此处省略部分模块 "./node_modules/react-dom/lib/ReactBrowserEventEmitter.js": { "id": 42, "meta": {} }, ... }
经常使用工具:speed-measure-webpack-plugin
使用方法:用其来包裹 Webpack 的配置
这里介绍的主要的几种优化配置以下所示:
@babel/plugin-transform-runtime
具体使用:
将业务代码和第三方依赖库进行分包,减少index.js的大小;
抽离多页应用的公共模块,单独打包。公共代码只须要下载一次就缓存起来了,避免了重复下载。
optimization: { minimize: false, moduleIds: 'named', splitChunks: { chunks: 'all', minSize: 30000, maxSize: 0, minChunks: 1, maxAsyncRequests: 6, maxInitialRequests: 6, automaticNameDelimiter: '~', name: true, cacheGroups: { polyfill: { test: /[\\/]node_modules[\\/](core-js|@babel|regenerator-runtime)/, name: 'polyfill', priority: 70, minChunks: 1, reuseExistingChunk: true }, lib: { test: /[\\/]node_modules[\\/]/, name: 'lib', chunks: 'initial', priority: 3, minChunks: 1, }, ... } } }
提取全部页面所需的helper函数到一个包里,避免重复注入
"plugins": [ "@babel/plugin-transform-runtime" ... ]
若是使用ES6的import 语法,那么在生产环境下,会自动移除没有使用到的代码。
(1) 具体配置
const TerserPlugin = require('terser-webpack-plugin'); const config = { // 生产模式下tree-shaking才生效 mode: 'production', optimization: { // Webpack 将识别出它认为没有被使用的代码,并在最初的打包步骤中给它作标记。 usedExports: true, minimizer: [ // 删除死代码的压缩器 new TerserPlugin({...}) ] } };
(2) 哪类代码会被shake掉?如下有一些事例
// no tree-shaking import Stuff from './stuff'; doSomething(Stuff); // tree-shaking import Stuff from './stuff'; doSomething(); // tree-shaking import './stuff'; doSomething(); // no tree-shaking import 'my-lib'; doSomething(); // 所有导入 no tree-shaking import _ from 'lodash'; // 具名导入 tree-shaking import { debounce } from 'lodash'; // 直接导入具体的模块 tree-shaking import debounce from 'lodash/lib/debounce';
(3) 什么叫有反作用的代码?
`只要被引入,就会对应用程序产生重要的影响。
(一个很好的例子就是全局样式表,或者设置全局配置的js文件。)`
(4) 有反作用的代码咱们不但愿被shake,咱们能够配置以下
// 全部文件都有反作用,全都不可 tree-shaking { "sideEffects": true } // 没有文件有反作用,全均可以 tree-shaking { "sideEffects": false } // 只有这些文件有反作用,全部其余文件均可以 tree-shaking,但会保留这些文件 { "sideEffects": [ "./src/file1.js", "./src/file2.js" ] }
(5) 注意,babel配置须要配modules: false
,忽略import/export代码编译
const config = { presets: [ [ '@babel/preset-env', { // commonjs代码不能被tree-shaking // 因此babel保留咱们现有的 es2015 import/export 语句,不进行编译 modules: false } ] ] };
经常使用工具:webpack-bundle-analyzer
使用方法:用其来包裹 Webpack 的配置
生产环境构建时,会检查构建产物里是否存在es6语法。有则抛出错误并提示你去进行babel编译,这样避免了构建产物不合要求的状况。
具体使用例子:
// package.json 命令里加上es-check检查 "dist:basic": "rimraf public && cross-env NODE_ENV=production webpack --config webpack-dist.config.js && es-check es5 ./public/**/*.js"
编译缓存就是在首次编译后把结果缓存起来,在后续编译时复用缓存,从而达到加速编译的效果。
webpack5默认开启编译缓存,缓存默认是在内存里,你能够自定义。
module.exports = { cache: { // 将缓存类型设置为文件系统 type: "filesystem", // 缓存的位置(默认是node_modules/.cache/webpack) cacheDirectory: path.resolve(__dirname, '.temp_cache'), // 指定构建过程当中的代码依赖。webpack将使用这些项目以及全部依赖项的哈希值来使文件系统缓存无效。 buildDependencies: { // 当配置文件内容或配置文件依赖的模块文件发生变化时,当前的构建缓存即失效。 config: [__filename], // webpack.config、loader和全部从你的配置中require的模块都会被自动添加。若是有其余的东西被构建依赖,你能够在这里添加它们 }, // 指定缓存的版本。当须要更新配置缓存时,经过设置此版本使缓存失效。 version: '1.0' } }
一些参数注解
cache: true
就是 cache: { type: 'memory' }
的别名type
: 'filesystem'|'memory'。若是设置'memory'则缓存在内存且不能配置其余信息,设置成'filesystem'就能够配置更多信息。默认开发模式使用的是'memory',生产模式是false。
version
: 当配置文件和代码都没有发生变化,可是构建的外部依赖(如环境变量)发生变化时,预期的构建产物代码也可能不一样。这时就可使用 version 配置来防止在外部依赖不一样的状况下混用了相同的缓存。例如,能够传入 cache: {version: process.env.NODE_ENV},达到当不一样环境切换时彼此不共用缓存的效果。长效缓存指的是能充分利用浏览器缓存,尽可能减小因为模块变动致使的构建文件hash值的改变,从而致使文件缓存失效。
(因为moduleId和chunkId肯定了,构建的文件的hash值也会肯定。)
chunk、module都是什么?
好比只是jsx删除引用的一个css文件 好多bundle文件的hash就都变了。)
以前须要经过以下配置达到长效缓存:
plugins: [ - new webpack.NamedModulesPlugin(), + new webpack.HashedModuleIdsPlugin(),
或者配置
optimization.moduleIds = 'hashed’ optimization.chunkIds = 'named'
配置说明:
(NamedChunksPlugin 只能对普通的 Webpack 模块起做用,异步模块(异步模块能够在 import 的时候加上 chunkName 的注释,好比这样:import(/ webpackChunkName: “lodash” / ‘lodash’).then() 这样就有 Name 了),external 模块是不会起做用的。)
Webpack5采用新的算法,生产模式下默认启用以下配置不只实现长效缓存,还减小了文件打包大小:
optimization.chunkIds: "deterministic" optimization.moduleIds: "deterministic" mangleExports: “deterministic"
PS.具体采用的算法还须要进一步深刻研究~
Webpack 4版本附带了大多数Node.js核心模块的polyfill,一旦前端使用了任何核心模块,这些模块就会自动应用,致使polyfill文件很大,可是其实有些polyfill是没必要要的。
而如今webpack5将不会自动为Node.js模块添加Polyfills,须要开发者手动添加合适的Polyfills。
升级迁移至webpack5须要注意:
1.嵌套tree-shaking
可以跟踪对export的嵌套属性的访问,分析模块的export和import的依赖关系,去掉未被使用的模块
// inner.js export const a = 1; export const b = 2; // module.js export * as inner from './inner'; // or import * as inner from './inner'; export { inner }; // user.js import * as module from './module'; console.log(module.inner.a); // 在此示例中,能够在生产模式下移除导出 b。
2.内部模块tree-shaking(深度做用域分析)
新属性optimization.innerGraph分析模块导出和导入之间的依赖关系,在生产模式下默认启用。
import { something } from './something'; function usingSomething() { return something; } export function test() { return usingSomething(); } // 在使用 test 导出时才使用 something。
能够分析如下符号:
3.package.json 中的“sideEffects”标志容许将模块手动标记为无反作用,从而在不使用它们时将其移除。
webpack 5 还能够根据对源代码的静态分析,自动将模块标记为无反作用。
更多Webpack5的内容推荐阅读: