目录
4 webpack优化环境配置
17 优化配置介绍
18 HMR
19 source-map
20 oneOf
21 缓存
22 tree shaking
23 code split (代码分割)
24 lazy loading
25 PWA
26 多进程打包
27 externals
28 dll
5 webpack配置详解
29 entry
30 output
31 module
32 resolve
33 dev server
34 optimization
4 webpack优化环境配置
由于笔记文档没有按照之前的md格式书写,所以排版上代码上存在问题😢😢😢😢
17 优化配置介绍
webpack性能优化
* 开发环境性能优化
* 生产环境性能优化
开发环境性能优化
* 优化打包构建速度
* HMR
* 优化代码调试
* source-map
生产环境性能优化
* 优化打包构建速度
* oneOf
* babel缓存
* 多进程打包
* externals
* dll
* 优化代码运行的性能
* 缓存 (hash-chunkhash-contenthash)
* tree shaking
* code split
* 懒加载/预加载
* pwa
18 HMR
当我们重新打包一次css,js会被加载一次;如果修改了js,打包,css也会被重新加载一次,刷新页面。速度就会很慢。如果文件更多,会更慢。
我们需要优化:一个功能模块被改,是修改这一个就够了 ----HMR功能
HMR: hot module replacement 热模块替换 / 模块热替换 作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块) 极大提升构建速度 样式文件:可以使用HMR功能:因为style-loader内部实现了~ js文件:默认不能使用HMR功能 --> 需要修改js代码,添加支持HMR功能的代码 注意:HMR功能对js的处理,只能处理非入口js文件的其他文件。 html文件: 默认不能使用HMR功能.同时会导致问题:html文件不能热更新了~ (不用做HMR功能) 解决:修改entry入口,将html文件引入 |
webpack配置


当修改了某个功能他就只会更新这个一个功能,其他的不会修改:(修改css时)

19 source-map
开发环境下调试代码
webpack配置 devtool: 'source-map'(除了source-map还有别的一些)
打包生成:

测试一下source-map
consloe调用两次,一次打印,第二次就会报错。然后浏览器开发者工具就会提示错误,代码行数

点进去也能追踪到源代码位置

inline-source-map
基本上跟source-map效果类似,只不过是内联(代码就在built.js里面)
hidden-source-map
能提示了错误原因,但是错误的位置有问题,不能追踪到源代码位置,只能提示到构建后代码
eval-source-map
跟inline-source-map一样,只不过多了一个哈希值

nosources-source-map
能找到错误信息,但是没有源代码信息
hidden-source-map和nosources-source-map都是为了隐藏源代码,防止泄露
cheap-source-map
跟source-map 类似,但是 cheap-source-map 只能精确到一整行,而不能精确到列

cheap-module-source-map
错误代码准确信息 和 源代码的错误位置
source-map: 一种 提供源代码到构建后代码映射 技术 (如果构建后代码出错了,通过映射可以追踪源代码错误) [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map source-map:外部 错误代码准确信息 和 源代码的错误位置 inline-source-map:内联 只生成一个内联source-map 错误代码准确信息 和 源代码的错误位置 hidden-source-map:外部 错误代码错误原因,但是没有错误位置 不能追踪源代码错误,只能提示到构建后代码的错误位置 eval-source-map:内联 每一个文件都生成对应的source-map,都在eval 错误代码准确信息 和 源代码的错误位置 nosources-source-map:外部 错误代码准确信息, 但是没有任何源代码信息 cheap-source-map:外部 错误代码准确信息 和 源代码的错误位置 只能精确的行 cheap-module-source-map:外部 错误代码准确信息 和 源代码的错误位置 module会将loader的source map加入 内联 和 外部的区别:1. 外部生成了文件,内联没有 2. 内联构建速度更快 开发环境:速度快,调试更友好 速度快(eval>inline>cheap>...) eval-cheap-souce-map eval-source-map 调试更友好 souce-map cheap-module-souce-map cheap-souce-map --> eval-source-map / eval-cheap-module-souce-map 生产环境:源代码要不要隐藏? 调试要不要更友好 内联会让代码体积变大,所以在生产环境不用内联 nosources-source-map 全部隐藏 hidden-source-map 只隐藏源代码,会提示构建后代码错误信息 --> source-map / cheap-module-souce-map |
20 oneOf
一个文件所有loader都要过一遍,就不太好
oneOf提升构建速度,文件就不会被反复多个loader过一遍
webpack配置
JavaScript const { resolve } = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
// 定义nodejs环境变量:决定使用browserslist的哪个环境 process.env.NODE_ENV = 'production';
// 复用loader const commonCssLoader = [ MiniCssExtractPlugin.loader, 'css-loader', { // 还需要在package.json中定义browserslist loader: 'postcss-loader', options: { ident: 'postcss', plugins: () => [require('postcss-preset-env')()] } } ];
module.exports = { entry: './src/js/index.js', output: { filename: 'js/built.js', path: resolve(__dirname, 'build') }, module: { rules: [ { // 在package.json中eslintConfig --> airbnb test: /\.js$/, exclude: /node_modules/, // 优先执行 enforce: 'pre', loader: 'eslint-loader', options: { fix: true } }, { // 以下loader只会匹配一个 // 注意:不能有两个配置处理同一种类型文件 oneOf: [ { test: /\.css$/, use: [...commonCssLoader] }, { test: /\.less$/, use: [...commonCssLoader, 'less-loader'] }, /* 正常来讲,一个文件只能被一个loader处理。 当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序: 先执行eslint 在执行babel */ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: {version: 3}, targets: { chrome: '60', firefox: '50' } } ] ] } }, { test: /\.(jpg|png|gif)/, loader: 'url-loader', options: { limit: 8 * 1024, name: '[hash:10].[ext]', outputPath: 'imgs', esModule: false } }, { test: /\.html$/, loader: 'html-loader' }, { exclude: /\.(js|css|less|html|jpg|png|gif)/, loader: 'file-loader', options: { outputPath: 'media' } } ] } ] }, plugins: [ new MiniCssExtractPlugin({ filename: 'css/built.css' }), new OptimizeCssAssetsWebpackPlugin(), new HtmlWebpackPlugin({ template: './src/index.html', minify: { collapseWhitespace: true, removeComments: true } }) ], mode: 'production' }; |
21 缓存
babel缓存
eg:100个js功能,修改了一个之后,再次编译100个就太好
开启babel缓存,二次构建的时候,没有修改的就不会重复编译,直接读取之前的缓存

文件资源缓存(??????)
需要写一个服务器代码
缓存 babel缓存 cacheDirectory: true --> 让第二次打包构建速度更快 文件资源缓存 hash: 每次wepack构建时会生成一个唯一的hash值。 问题: 因为js和css同时使用一个hash值。 如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件) chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样 问题: js和css的hash值还是一样的 因为css是在js中被引入的,所以同属于一个chunk contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样 --> 让代码上线运行缓存更好使用 |
22 tree shaking
去除应用程序中没有使用的代码
tree shaking:去除无用代码 前提:1. 必须使用ES6模块化 2. 开启production环境 作用: 减少代码体积 在package.json中配置 "sideEffects": false 所有代码都没有副作用(都可以进行tree shaking) 问题:可能会把css / @babel/polyfill (副作用)文件干掉 "sideEffects": ["*.css", "*.less"] --- 不会处理掉css,less tree shaking 不生效 |
23 code split (代码分割)
代码分割主要研究的是js代码
一个体积太大,我们将他分割成多个js文件
两个js文件,一般打包之后只会生成一个js文件,两个js内容都结合在一个js里面,现在我们需要代码分割
方法一:多入口
- 从入口文件出发
JavaScript entry: { // 多入口:有一个入口,最终输出就有一个bundle index: './src/js/index.js', test: './src/js/test.js' }, |
- 输出文件命名
JavaScript output: { // [name]:取文件名 filename: 'js/[name].[contenthash:10].js', path: resolve(__dirname, 'build') }, |
- 打包生成文件

但是有一个问题:很难去指定多入口,每次入口不同,改来改去也很麻烦怎么办
方法二:打包成单独一个chunk(用得少)
将node_modules中代码单独打包一个chunk最终输出
自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
webpack配置
JavaScript /* 1. 可以将node_modules中代码单独打包一个chunk最终输出 2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk */ optimization: { splitChunks: { chunks: 'all' } }, |
如果想把普通的test.js文件和index.js分割,并且不用多入口办法,怎么办
方法三:不用多入口分割js(用的多)
JavaScript // index.js /* 通过js代码,让某个文件被单独打包成一个chunk import动态导入语法:能将某个文件单独打包 */ import(/* webpackChunkName: 'test' */'./test') .then(({ mul, count }) => { // 文件加载成功~ // eslint-disable-next-line console.log(mul(2, 5)); }) .catch(() => { // eslint-disable-next-line console.log('文件加载失败~'); }); |
24 lazy loading
懒加载
js文件的懒加载
懒加载:触发条件之后加载
Eg. index.js

将import语法放在一个异步的回调函数中,这样,一上来,onclick回调函数并不会调用,那么这个js文件就不会被加载。只有当我们点击按钮之后,回调函数才被调用,js被加载。这样就是懒加载了
预加载


没有点击按钮的时候,test.js就被提前加载出来了
懒加载~:当文件需要使用时才加载~ 预加载 prefetch:会在使用之前,提前加载js文件 正常加载可以认为是并行加载(同一时间加载多个文件) 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源 懒加载可以,预加载慎用,兼容性,用在高版本pc端浏览器 |
25 PWA
间接式网络开发应用程序
离线也可以访问
PWA: 渐进式网络开发应用程序(离线可访问) workbox(库) --> workbox-webpack-plugin(插件) |
- 下载插件 npm i workbox-webpack-plugin@5.0.0
- webpack配置----生成一个 serviceworker 配置文件

- index.js 注册 serviceworker

- package.json有点小问题,eslint不认识 window、navigator全局变量,需要改一下配置

- 构建一下代码 npx webpack
- sw代码必须运行在服务器上,启动构建后的built资源
- npm i serve@11.3.0 -g帮我们快速创建一个静态服务器,安装后指令 serve -s build
- 运行程序离线也能访问

index.js源代码
JavaScript import { mul } from './test'; import '../css/index.css';
function sum(...args) { return args.reduce((p, c) => p + c, 0); }
// eslint-disable-next-line console.log(mul(2, 3)); // eslint-disable-next-line console.log(sum(1, 2, 3, 4));
/* 1. eslint不认识 window、navigator全局变量 解决:需要修改package.json中eslintConfig配置 "env": { "browser": true // 支持浏览器端全局变量 } 2. sw代码必须运行在服务器上 --> nodejs --> npm i serve -g serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去 */ // 注册serviceWorker // 处理兼容性问题 if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker .register('/service-worker.js') .then(() => { console.log('sw注册成功了~'); }) .catch(() => { console.log('sw注册失败了~'); }); }); } |
1. eslint不认识 window、navigator全局变量 解决:需要修改package.json中eslintConfig配置 "env": { "browser": true // 支持浏览器端全局变量 } 2. sw代码必须运行在服务器上 --> nodejs --> npm i serve -g serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去 |
26 多进程打包
js单线程,同一时间只能干一件事,排队等很久才能干下一件事,所以我们就需要使用多线程
- 下载 npm i thread-loader@2.1.3 -D 谁要启动多进程,我们就把他放过去,一般是给babel用
- webpack配置
JavaScript { test: /\.js$/, exclude: /node_modules/, use: [ /* 开启多进程打包。 进程启动大概为600ms,进程通信也有开销。千万不要乱用 只有工作消耗时间比较长,才需要多进程打包 */ { loader: 'thread-loader', options: { workers: 2 // 进程2个 } }, { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: { version: 3 }, targets: { chrome: '60', firefox: '50' } } ] ], // 开启babel缓存 // 第二次构建时,会读取之前的缓存 cacheDirectory: true } } ] }, |
27 externals
比如我们希望用jQuery链接,使用 externals 就可以禁止它打包,我们就可以使用cdn去链接jQuery
- webpack配置

- index.js引入cdn

28 dll
不同的文件打包不同的chunk
- 定义一个文件 webpack.dll.js ----专门去打包一些库

源代码 webpack.dll.js
JavaScript /* 使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包 当你运行 webpack 时,默认查找 webpack.config.js 配置文件 需求:需要运行 webpack.dll.js 文件 --> webpack --config webpack.dll.js */
const { resolve } = require('path'); const webpack = require('webpack');
module.exports = { entry: { // 最终打包生成的[name] --> jquery // ['jquery'] --> 要打包的库是jquery jquery: ['jquery'], }, output: { filename: '[name].js', path: resolve(__dirname, 'dll'), library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字 }, plugins: [ // 打包生成一个 manifest.json --> 提供和jquery映射 new webpack.DllPlugin({ name: '[name]_[hash]', // 映射库的暴露的内容名称 path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径 }) ], mode: 'production' }; |
- 运行 npx webpack --config webpack.dll.js 会生成两个文件jquery.js(希望在将来构建时不用重复打包,防止性能过慢) manifest.json


当我们使用这个dll做完之后,我们将来jQuery就不用打包了,因为已经打包过了,源代码直接引入就可以了,我们需要配置一下
- 配置 webpack --- 告诉webpack哪些不需要打包,通过webpack.DllReferencePlugin,找到manifest文件,知道jquery不用打包,所以打包时jQuery相当于不存在。忽略出去后我们打包的资源就并没有他们,所以我们用插件AddAssetHtmlWebpackPlugin把他再自动引入进来
需要安装 npm i add-asset-html-webpack-plugin@3.1.3 -D
JavaScript const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
module.exports = { entry: './src/index.js', output: { filename: 'built.js', path: resolve(__dirname, 'build') }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }), // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~ new webpack.DllReferencePlugin({ manifest: resolve(__dirname, 'dll/manifest.json') }), // 将某个文件打包输出去,并在html中自动引入该资源 new AddAssetHtmlWebpackPlugin({ filepath: resolve(__dirname, 'dll/jquery.js') }) ], mode: 'production' }; |
- 打包npx webpack---

1.这里的dll可以理解为提前将一些包打包好,=准备辅料=.=
2.辅料准备好要告诉大厨准备了哪些辅料,放在哪里.到时候直接用就好
3.每次都告诉大厨放在哪里也挺麻烦的,通过插件告诉大厨辅料的固定位置
5 webpack配置详解
29 entry
entry: 入口起点 1. string --> './src/index.js' 单入口 打包形成一个chunk。 输出一个bundle文件。 此时chunk的名称默认是 main 2. array --> ['./src/index.js', './src/add.js'] 多入口 所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。 --> 只有在HMR功能中让html热更新生效~ 3. object 多入口 有几个入口文件就形成几个chunk,输出几个bundle文件 此时chunk的名称是 key
--> 特殊用法 { // 所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。 index: ['./src/index.js', './src/count.js'], // 形成一个chunk,输出一个bundle文件。 add: './src/add.js' |
JavaScript const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = { entry: { index: ['./src/index.js', './src/count.js'], add: './src/add.js' }, output: { filename: '[name].js', path: resolve(__dirname, 'build') }, plugins: [new HtmlWebpackPlugin()], mode: 'development' }; |
30 output
JavaScript const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = { entry: './src/index.js', output: { // 文件名称(指定名称+目录) filename: 'js/[name].js', // 输出文件目录(将来所有资源输出的公共目录) path: resolve(__dirname, 'build'), // 所有资源引入公共路径前缀 --> 'imgs/a.jpg' --> '/imgs/a.jpg' publicPath: '/', chunkFilename: 'js/[name]_chunk.js', // 非入口chunk的名称 // library: '[name]', // 整个库向外暴露的变量名 // libraryTarget: 'window' // 变量名添加到哪个上 browser // libraryTarget: 'global' // 变量名添加到哪个上 node // libraryTarget: 'commonjs' }, plugins: [new HtmlWebpackPlugin()], mode: 'development' }; |
31 module
Module 值为rules, rules 里面写 loader 配置
JavaScript const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = { entry: './src/index.js', output: { filename: 'js/[name].js', path: resolve(__dirname, 'build') }, module: { rules: [ // loader的配置 { test: /\.css$/, // 多个loader用use use: ['style-loader', 'css-loader'] }, { test: /\.js$/, // 排除node_modules下的js文件 exclude: /node_modules/, // 只检查 src 下的js文件 include: resolve(__dirname, 'src'), // 优先执行 enforce: 'pre', // 延后执行 // enforce: 'post', // 单个loader用loader loader: 'eslint-loader', options: {} }, { // 以下配置只会生效一个 oneOf: [] } ] }, plugins: [new HtmlWebpackPlugin()], mode: 'development' }; |
32 resolve
解析模块的规则
alias 让写代码更方便,简写路径 extensions 简写文件后缀名 modules 解析模块准确找到node_modules所在,而不是一层一层找,性能太低 |
JavaScript const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = { entry: './src/js/index.js', output: { filename: 'js/[name].js', path: resolve(__dirname, 'build') }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] }, plugins: [new HtmlWebpackPlugin()], mode: 'development', // 解析模块的规则 resolve: { // 配置解析模块路径别名: 优点简写路径 缺点路径没有提示 alias: { $css: resolve(__dirname, 'src/css') }, // 配置省略文件路径的后缀名 extensions: ['.js', '.json', '.jsx', '.css'], // 告诉 webpack 解析模块是去找哪个目录 modules: [resolve(__dirname, '../../node_modules'), 'node_modules'] } }; |
33 dev server
用于开发服务器
JavaScript const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = { entry: './src/js/index.js', output: { filename: 'js/[name].js', path: resolve(__dirname, 'build') }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] }, plugins: [new HtmlWebpackPlugin()], mode: 'development', resolve: { alias: { $css: resolve(__dirname, 'src/css') }, extensions: ['.js', '.json', '.jsx', '.css'], modules: [resolve(__dirname, '../../node_modules'), 'node_modules'] }, devServer: { // 运行代码的目录 contentBase: resolve(__dirname, 'build'), // 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload watchContentBase: true, watchOptions: { // 忽略文件 ignored: /node_modules/ }, // 启动gzip压缩 compress: true, // 端口号 port: 5000, // 域名 host: 'localhost', // 自动打开浏览器 open: true, // 开启HMR功能 hot: true, // 不要显示启动服务器日志信息 clientLogLevel: 'none', // 除了一些基本启动信息以外,其他内容都不要显示 quiet: true, // 如果出错了,不要全屏提示~ overlay: false, // 服务器代理 --> 解决开发环境跨域问题 proxy: { // 一旦devServer(5000)服务器接受到 /api/xxx 的请求,就会把请求转发到另外一个服务器(3000) '/api': { target: 'http://localhost:3000', // 发送请求时,请求路径重写:将 /api/xxx --> /xxx (去掉/api) pathRewrite: { '^/api': '' } } } } }; |
34 optimization
JavaScript const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const TerserWebpackPlugin = require('terser-webpack-plugin')
module.exports = { entry: './src/js/index.js', output: { filename: 'js/[name].[contenthash:10].js', path: resolve(__dirname, 'build'), chunkFilename: 'js/[name].[contenthash:10]_chunk.js' }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] }, plugins: [new HtmlWebpackPlugin()], mode: 'production', resolve: { alias: { $css: resolve(__dirname, 'src/css') }, extensions: ['.js', '.json', '.jsx', '.css'], modules: [resolve(__dirname, '../../node_modules'), 'node_modules'] }, optimization: { splitChunks: { chunks: 'all' // 默认值,可以不写~ /* minSize: 30 * 1024, // 分割的chunk最小为30kb maxSiza: 0, // 最大没有限制 minChunks: 1, // 要提取的chunk最少被引用1次 maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量 maxInitialRequests: 3, // 入口js文件最大并行请求数量 automaticNameDelimiter: '~', // 名称连接符 name: true, // 可以使用命名规则 cacheGroups: { // 分割chunk的组 // node_modules文件会被打包到 vendors 组的chunk中。--> vendors~xxx.js // 满足上面的公共规则,如:大小超过30kb,至少被引用一次。 vendors: { test: /[\\/]node_modules[\\/]/, // 优先级 priority: -10 }, default: { // 要提取的chunk最少被引用2次 minChunks: 2, // 优先级 priority: -20, // 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块 reuseExistingChunk: true } }*/ }, // 将当前模块的记录其他模块的hash单独打包为一个文件 runtime // 解决:修改a文件导致b文件的contenthash变化 runtimeChunk: { name: entrypoint => `runtime-${entrypoint.name}` }, minimizer: [ // 配置生产环境的压缩方案:js和css new TerserWebpackPlugin({ // 开启缓存 cache: true, // 开启多进程打包 parallel: true, // 启动source-map sourceMap: true }) ] } }; |
课程学完问题:
补充:
理解chunk