webpack@4与webpack@的区别
1.在webpack中5个重要的元素
- entry
- ouput
- mode
- loader
- plugin
2.所有东西都要导出,通过
module.exports = {
entry: './src/index.js', // 设置入口文件
// 设置打包出口
output: {
filename: 'dist.js',
path: path.join(__dirname, '/dist') // 出口路径要求必须是绝对路径
},
module:{
rules: [
// loader
]
},
plugins: [],
mode: ''
}
3.图片的打包
- 在webpack@4中
通过:url-loader file-loader html-loader
rules: [
...,
{
test: /\.(gif|jpg|jpeg|png|webp)/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024 * 100,
name: 'images/[hash].[ext]',
esModule: false
}
}
]
}
]
- 在webpack@5中
通过type='‘accect’
type
参数详解
asset/resource
发送一个单独的文件并导出 URLasset/inline
导出一个资源的 data URIasset/source
导出资源的源代码asset
在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用url-loader
,并且配置资源体积限制实现
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 // 4kb 以下打包base64
}
}
}
资源目录的自定义
// 未修改
output: {
filename: 'dist.js',
path: path.join(__dirname, '/dist'),
clean: true,
},
// 修改后
output: {
filename: 'js/dist.js', // js入口文件的输出目录修改
path: path.join(__dirname, '/dist'),
clean: true,
assetModuleFilename: 'assets/[hash:8][ext]' // 资源目录整体修改
},
rules配置中增加 generator 配置项
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset', // 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。
parser: { // 设置解析规则(主要是图片资源的base64的转化规则)
dataUrlCondition: {
maxSize: 4 * 1024 // 4kb 以下打包base64
}
},
generator: { // 配置图片资源的输出目录
filename: 'static/images/[hash:8][ext]'
}
}
2.3 说明:两种修改方式同时出现时,以generator 为准
html文件中引入(html-loader处理) ,这样就能直接在html模板文件引入图片或者css
{
test: /\.html$/i,
loader: "html-loader",
},
3.加载css资源(loader的使用)
4… 单独提取css文件(需要配合 html-webpack-plugin 来使用) ,也就是单独打包css文件在html里面用
-
安装插件
-
npm install --save-dev mini-css-extract-plugin
-
使用(替代原本的style-loader)@4里面也是这个插件
-
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { // 插件中引入 plugins: [new MiniCssExtractPlugin({ filename: 'static/css/main.css' // 指定输出目录 })], module: { rules: [ { test: /\.css$/i, // 在css-loader之后调用 MiniCssExtractPlugin.loader use: [MiniCssExtractPlugin.loader, "css-loader"], }, ], }, };
5.css 样式的兼容性处理
修改配置项(需要在css-loader之前处理兼容性)
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env', // 可以解决大多数样式兼容性问题
]
}
}
}
创建.browserslistrc
文件(取约束条件的交集)
last 20 version // 支持浏览器最近的哪些版本
> 5% // 支持市面上使用占比大于这个数值的浏览器
- css压缩
-
依赖下载 在@4中是 optimize-css-assets-webpack-plugin
-
npm install css-minimizer-webpack-plugin --save-dev
- 配置插件,方法1
-
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); optimization: { minimizer: [ // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释 new CssMinimizerPlugin(), ], },
-
配置插件,方法2
-
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); ... plugins: [new CssMinimizerPlugin()],
7.html-webpack-plugin
作用
- 基于模板html文件生成一个html文件,自动引入webpack打包生成的js文件
- . 引入和配置
// webpack.config.js // 引入 const HtmlWebpackPlugin = require('html-webpack-plugin') // 使用 new HtmlWebpackPlugin({ template: path.join(__dirname, './index.html'), // 模板文件目录(相对路径也可以) filename: 'webpack.html', // 生成的html文件的名字 inject: 'body' // webpack打包生成的js文件插入的位置(默认插入head标签) })
-
8.webpack-dev-server
帮助我们在开发时,自动检测代码的变化重新打包文件,打包的文件放入内存中反应更迅速
配置( )
devServer: {
static: './dist' // 服务器根目录地址
port:7000
host:'0.0.0.0'//host设置的是服务器的主机号
publicPath: "/assets/
proxy: {
'/proxy': {
target: 'http://your_api_server.com',
changeOrigin: true,
pathRewrite: {
'^/proxy': ''
}
}
}
- webpack-dev-server支持热模块更新,相较于(开启本地服务(live-server)+ --watch监听文件变化重新打包(webpack -w))的模式虽然表现上没有区别,但效率更高,打包的文件放入内存中反应更迅速
9.js文件的处理
eslint格式化js文件
-
创建eslint配置文件(三种方法都可以)
-
.eslintrc
-
.eslintrc.js(推荐,注释方便,符合js习惯)
-
.eslintrc.json
-
基础的配置文件
-
module.exports = { // 解析器选项 parserOptions: { ecmaVersion: 6, //支持的es语法版本 sourceType: 'module', // es模块化 ecmaFeatures: { // 支持的其他特性 jsx: true, // 如果是react项目打开这个 } }, // eslint.bootcss.com/docs/rules/ rules: { // key: value /** * key: 规则名 * value: 规则的控制 * off/0 关闭该规则 * warn/1 警告级别 * error/2 错误级别 */ 'no-var': 2, 'no-unused-vars': 0 }, // 继承规则,比如: eslint 官方推荐规则 extends: ['eslint:recommended'], env: { node: true, // node的全局内置api变量可用 browser: true // bom模型的内置api变量可用 es6: true // es6 新特性,比如:promise } }
-
安装和使用插件(之前的版本使用 loader 处理)
-
npm install eslint-webpack-plugin eslint --save-dev
-
创建
.eslintignore
文件-
vscode 的 eslint 插件也会扫描eslint配置文件,但它无法读取我们在webpack.config.js中设置的扫描范围,所以我们需要创建
.eslintignore
文件 -
// .eslintignore 文件 dist
-
9.2babel 处理js兼容性
-
.babelrc
-
.babelrc.js( 推荐 )
-
.babelrc.json
-
module.exports = { // 预设 // @babel/preset-env: 一个智能预设,允许使用最新的javascript语法(比如箭头函数, ...) presets: ['@babel/preset-env'], }
-
安装依赖
-
npm install -D babel-loader @babel/core @babel/preset-env
-
使用
-
{ test: /\.js$/, exclude: /(node_modules)/, // 设置哪些目录不需要扫描 use: [{ loader: 'babel-loader?cacheDirectory', //开启缓存,可以设置缓存目录 }, ...] },
-
babel存在的一些问题:
-
Babel 在每个文件都插入了辅助代码,使代码体积过大!
-
解决方案:
-
安装依赖
-
npm install -D @babel/plugin-transform-runtime npm install @babel/runtime
-
module.exports = { // 禁用 Babel 自动对每个文件的 runtime 注入,改为引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用。 plugins: ['@babel/plugin-transform-runtime'] }
-
babel-loader 很慢!
- 开启缓存:
'babel-loader?cacheDirectory'
- 排除不需要检测的目录:
exclude: /(node_modules)/, // 设置哪些目录不需要扫描
- 开启缓存:
9.3 使用 core-js 解决 babel 无法解决的兼容性问题
-
babel 的 preset-env 可以解决一些 es6 语法的兼容性问题(箭头函数、…语法等),但比如 async、promise、Array.includes()等preset-env是处理不了的,所以我们要用到 core-js来处理 es6以及 es6+的 polyfill
-
安装依赖
-
npm install core-js
-
使用方式 1: 完全引入
-
import 'core-js'
-
自动按需引入,修改 babel 的 preset-env 设置( 不需要再按照方式1 引入 core-js了 ),在.babelrc文件下写的
-
module.exports = { // 预设 // @babel/preset-env: 一个智能预设,允许使用最新的javascript语法(比如箭头函数, ...) presets: [ // 预设的配置是需要使用数组实现的,配置对象作为数组的第二个元素出现 ['@babel/preset-env', { useBuiltIns: 'usage', // core-js 按需引入 corejs: 3 // core-js 版本 }] ], }
-
-
9.4 js代码的压缩
-
在webpack5中
- 自动对js(terser)和html文件进行压缩,terser-webpack-plugin
- 自动开启 tree shaking
-
在webpack@4中
- uglifyjs-webpack-plugin 用这个插件
webpack优化
source-map的设置(错误提示)
- devtool
1.1 开发模式使用:cheap-module-source-map
@eval-source-map(精确到开发环境的行)
- 打包编译速度更快,只映射到行,
1.2 生产环境使用:source-map/
@4 nosource-source-map (是为了报错的时候并不显示源码,只显示报错的哪一行)
- 因为生产环境的代码会压缩,所以必须映射到行和列的信息
2. HMR(只能在开发环境下使用)
- webpack的默认行为会在一个模块发生变化的时候把所有模块都重新打包
- 开启HMR功能后,只重新打包变化的模块,其它模块使用缓存
- 样式文件天然支持这个功能(devserver 的hot配置默认为true)
- js的支持:(vue-loader和react-loader都默认支持了这样的功能,不需要我们单独实现)
3. oneOf
- webpack的默认行为是当一个类型的检查是否使用某一个loader进行处理时,即使匹配到了也会在接下来的所有规则中进行再次匹配(走完所有的rules规则匹配)
- 使用oneOf解决上面的问题
rules: [
{
oneOf:[
...rules
]
}
]
4. 所有文件处理范围(include和exclude)
-
两个配置只能同时是用一个,可以设置包含(include)或者设置排除(exclude)
-
一般对js处理(babel和eslint比较耗时),因为代码量比较大
-
// loader 的配置 { test: /\.js$/, exclude: /(node_modules)/, // 使用这种 include: path.join(__dirname, 'src'), // 或者使用这种 use: [...] } // 插件的配置 new ESLintPlugin({ context: path.join(__dirname, 'src'), // 配置eslint检测范围 exclude: 'node_modules' // 这是一个默认值 }),
5. webpack缓存的使用
-
前面提到js文件的编译需要进行babel和eslint的处理,比较耗时,所有这里说的缓存也只针对这两个过程
-
bebel-loader 配置
-
{ loader: 'babel-loader?', options: { cacheDirectory: true, // 开启babel缓存 cacheCompression: false // 关闭缓存文件压缩(耗时无意义,线上用不到) } }
-
eslint 配置
-
new ESLintPlugin({ context: path.join(__dirname, 'src'), // 配置eslint检测范围 exclude: 'node_modules', // 这是一个默认值 cache: true, // 开启缓存 cacheLocation: path.resolve(__dirname, './node_modules/.cache/eslint') // 设置缓存目录 }),
6. 多进程打包(大数据量的项目才去使用)
-
注意: 把注意写在前面,每个进程打开大概需要 600ms 的时间,建议在文件量很大的情况下使用
-
一样是针对最耗时的任务 js 处理 babel 和 eslint 检查使用
-
安装依赖(用于babel编译开启多进程)
-
npm install thread-loader -D
-
获取 cpu 核心数量
-
const os = require('os') const threads = os.cpus().length
-
增加多进程配置添加到 babel-loader 前面
-
{ test: /\.js$/, exclude: /(node_modules)/, // 设置哪些目录不需要扫描 use: [ { loader: 'thread-loader', // 开启多线程处理 babel options: { works: threads // 设置线程数量 } }, { loader: 'babel-loader?cacheDirectory', //开启缓存,可以设置缓存目录 } ] }
-
引入 webpack5 内置压缩 js 代码的插件
-
const TerserWebpackPlugin = require('terser-webpack-plugin')
-
js代码压缩开启多进程
-
plugins: [ new TerserWebpackPlugin({ parallel: threads // js 代码压缩开启多进程,threads:cpu 核心数量 }) ] // 或者放入optimization中,和之前的 css 压缩一样 optimization: { minimizer: [ new CssMinimizerPlugin(), new TerserWebpackPlugin({ parallel: threads // js 代码压缩开启多进程,threads:cpu 核心数量 }) ] }
-
eslint 开启多进程
-
new ESLintPlugin({ context: path.join(__dirname, 'src'), // 配置eslint检测范围 exclude: 'node_modules', // 这是一个默认值 cache: true, // 开启缓存 cacheLocation: path.resolve(__dirname, './node_modules/.cache/eslint') // 设置缓存目录 + threads, // +++ eslint 开启多进程 }),
7. tree-shaking
- webpack 内置了这个功能,生产模式下自动打开
- tree shaking 依赖于 esm实现,commonjs 语法不支持
8. 图片压缩
-
依赖安装1
-
npm install image-minimizer-webpack-plugin -D npm install @squoosh/lib --save-dev
-
有损压缩:
-
// 引入依赖 const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); // 使用插件(放入plugins) new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.squooshMinify, options: { // Your options for `squoosh` }, }, }), // 再说一次还可以在 optimization 中使用 optimization: { minimizer: [ new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.squooshMinify, options: { // Your options for `squoosh` }, }, }), ], },
-
无损压缩
-
optimization: { minimizer: [ new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.squooshMinify, options: { encodeOptions: { mozjpeg: { // That setting might be close to lossless, but it’s not guaranteed // https://github.com/GoogleChromeLabs/squoosh/issues/85 quality: 100, }, webp: { lossless: 1, }, avif: { // https://github.com/GoogleChromeLabs/squoosh/blob/dev/codecs/avif/enc/README.md cqLevel: 0, }, }, }, }, }), ], },
splitchunk
-
多入口的配置文件
module.exports = { // entry: './src/main.js', // 单入口 // 多入口 entry: { app: './src/app.js', main: './src/main.js' }, output: { path: path.join(__dirname, 'dist'), filename: [name].js } }
- 多入口存在的问题:
- 如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个 bundle 中
2. splitchunks
- 多个入口引用相同模块的 splitchunk 配置
optimization: { splitChunks: { chunks: 'all', // 设置哪些文件会被检测 cacheGroups: { // defaultVendors: {}, 这里主要是给 node_modules 里面第三方依赖合使用,通常使用默认配置就好 default: { // 这个配置项给我们自己写的模块代码使用 minSize: 0, // 分割代码最小值(实际开发的时候我们就使用默认值(20000)就好了,为了演示改为 0) // 下面的都是默认值(用就好) minChunks: 2, // 最少被引用次数大于等于这个数值才会合并 priority: -20, // 权重(设置优先级,权重越大优先级越高) reuseExistingChunk: true // 重用从主文件中分离出来的模块 }, // 这种自定义的选项主要用于当项目过大,导致defaultVendors的包太大需要拆分的时候使用 anyname: { test: /[\\/]node_modules[\\/]react(.*)?[\\/]/, // 通过正则匹配要单独打包的第三方依赖包 name: 'chunk-any', // 单独打包的第三方依赖 priority: 10, // 权重要大于 node_modules的默认权重 } } } }
- 多入口存在的问题:
- splitchunk 结合 import 动态导入实现按需加载
webpackChunkName: '[chunkname]'
可以设置动态打包的包名- 之前还需要配置
output.chunkFilename: '[name].js'
(加上肯定不会出错)
const button = document.createElement('button')
button.innerHTML = 'click'
button.addEventListener('click', () => {
// import 动态导入的文件会被 splitchunk 代码分割成单独的模块打包,在 import 调用的时候引入
import(/* webpackChunkName: '[chunkname]' */ './js/sum').then((res) => {
console.log(res)
}).catch()
})
document.body.appendChild(button)
- eslint 如果动态导入报错,在 eslintrc 中添加如下代码
plugins: ['import']
3. 打包输出文件命名规则整理
// 添加 contenthash 当内容发生变化的时候加载新的资源
output: {
filename: '[name].[contenthash:8]js', // 正常 js 打包文件
chunkFilename: '[name].chunk.[contenthash:8].js', // splitchunk 单独打包的文件增加一个.chunk标识
assetModuleFilename: 'static/[hash:8][ext]', // 通过 'type: asset' 这种方式打包的资源文件
path: path.join(__dirname, 'dist'),
clean: true,
},
plugins: [new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css', // 指定输出目录
// 多次引入同一个 css 文件也可能单独打包 css 文件
chunkFilename: 'static/css/[name].chunk.[contenthash:8].css', // splitchunk 单独打包的文件增加一个.chunk标识
})],
4. preload 和 prefetch 结合 splitchunk
-
当我们想利用浏览器空闲时间,加载后续使用的资源,我们就会用到 preload 和 prefetch(偷偷加载未来要用到的资源),比如我们的按需加载的使用场景(点击按钮触发的按需引入早就下载好了等待使用多么美好),通过埋点决定先显示什么
-
共同点:
-
加载未来要用的资源
-
只加载不执行
-
加载完毕后缓存起来等待调用
-
兼容性有一点问题(preload: 92%(ie 完全不支持), prefetch: 79%)
-
-
不同点:
- preload 相较于 prefetch 有更高的优先级
- preload 只能加载当前页面要用的资源(怎么理解:页面跳转缓存的资源就失效了),prefetch 可以加载当前以及后续页面要用到的资源
- preload 会立刻下载,prefetch 会在浏览器空闲时下载
-
如何使用:
-
安装依赖:
-
npm install --save-dev @vue/preload-webpack-plugin
-
引入
-
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');
-
配置插件(它是结合 htmlwebpackplugin 使用的)
-
plugins: [ new HtmlWebpackPlugin(), // 它是结合 htmlwebpackplugin 使用的 new PreloadWebpackPlugin({ rel: 'preload', as: 'script' }) ]
-
5. runtimeChunk解决重复打包的问题
-
当 a 文件依赖一个单独打包的 b 文件时,如果 b 文件发生变化,重新打包的时候发现 a 文件也重新打包了,这是因为 a 直接引用了 b 打包后的文件(它的文件名包含了 hash 值),而它的 hash 值发生了变化,导致了 a 文件的缓存失效,重新打包,为了解决这个问题,引入一个 runtime 的概念, runtime类型一个对象,key 表示模块(比如:b),对应的value值存储 b 文件打包后的文件名(比如:b.chunk.490905b5.js),所以当 b 文件变化,重新打包时,只有 b 文件和 runtime 对象发生变化,不会导致 a 文件缓存失效,这在大型项目的负载文件依赖关系种非常有效
-
实现
-
optimization: { runtimeChunk: { name: (entry) => `runtime-${entry.name}` } }
resolve
-
webpack 解析模块的时候用到的选项
-
resolve: { // 用于自动补全文件扩展名 extensions: ['.js', '.vue', '.json'], // 配置别名 alias: { @: path.join(__dirname, 'src'), } }