webpack5从入门到放弃(笔记)
webpack
什么是webpack
webpack就是一个打包工具,核心宗旨就是一切静态资源皆可打包。
webpack能做什么
- 将ESModule, CommonJS代码转换为浏览器支持的语法
- 预编译Less,Sass语言
- 图片压缩
- 全局注入依赖文件
- 提供本地服务器
- 热更新
基础部分
五大核心概念
- entry(入口) 用来指定文件打包的入口文件
- output(输出) 用来指定文件的输出路径
- loader(加载器) webpack默认只支持js/json文件打包,需要配置loader来解析其他类型资源
- plugin(插件) 用来扩展webpack的功能
- mode(模式)
- 开发模式 development
- 生产模式 production(此模式下,会自动进行代码压缩,预处理等优化)
基础的webpack.config.js格式(在项目根目录下创建此文件)如下
const path = require('path')
module.exports = {
mode: 'development', // 模式
entry: './src/main.js', //入口文件
output: { // 输出目录
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist') // 绝对路径
},
module: { // 加载器
rules: [
]
},
plugins: [ // 插件
]
}
资源模块
资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。
在webpack5之前, 通常使用
raw-loader
将文件导入为字符串url-loader
将文件作为 data URI 内联到 bundle 中file-loader
将文件发送到输出目录
资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:
asset/resource
发送一个单独的文件并导出 URL。之前通过使用file-loader
实现。asset/inline
导出一个资源的 data URI。之前通过使用url-loader
实现。asset/source
导出资源的源代码。之前通过使用raw-loader
实现。asset
在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用url-loader
,并且配置资源体积限制实现。
处理样式资源
webpack默认是不能解析css文件,需要添加loader加载器来处理
目前常见的几个样式loader
style-loader
将模块导出的内容作为样式并添加到 DOM 中css-loader
加载 CSS 文件并解析 import 的 CSS 文件,最终返回 CSS 代码less-loader
加载并编译 LESS 文件sass-loader
加载并编译 SASS/SCSS 文件postcss-loader
使用 PostCSS 加载并转换 CSS/SSS 文件stylus-loader
加载并编译 Stylus 文件
- 常规配置
// webpack.config.js
module.exports = {
// ...其他配置省略
module: {
rules: [
// loader配置,loader执行顺序从下往上,从右往左
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
}
- 使用预编译样式语言配置(以scss为列)
- 安装对应的依赖
npm install sass-loader sass -D
- 将sass-loader和style-loader进行链式调用
// webpack.config.js
module.exports = {
// ...其他配置
module: {
rules: [
// loader配置
{
test: /\.s[ac]ss$/i,
use: [
'style-loader', // 将js字符串生成style节点
'css-loader', // 将css转为commonJS 模块
'sass-loader' // 将sass编译为css
]
}
]
}
}
- 样式兼容性处理
- 安装依赖
npm install postcss-loader postcss postcss-preset-env -D
- 引入配置(sass-loader之前,css-loader之后使用)
module.exports = {
module: {
rules: [
{
test: /.css$/i,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
'postcss-preset-env',
{
// 其他选项
},
],
],
},
},
},
],
},
],
},
};
- package.json配置兼容的级别
// package.json
{
//...其他配置
"browserslist": [// 选项之间取交集
"> 1%", // 市面上99%的浏览器
"last 2 versions" // 兼容最近的两个版本
],
}
处理图片资源
在webpack5中已经内置了file-loader和url-loader,只需要指定type为'asset'(通用类型)
module.exports = {
// ... 其他配置
module: {
rules: [
// 加载图片资源
{
test: /.(png|jpg|svg|gif|jpeg)$/,
type: 'asset'
}
]
}
}
// index.scss
.box2 {
width: 400px;
height: 400px;
background: url('../assets/bg.png')
}
// 打包之后 会在dist文件夹下生成ee6e171b64f6ffb89d31.png,并将scss中的图片URL改为生成的图片路径
默认情况下,
asset/resource
模块以[hash][ext][query]
文件名发送到输出目录。可以通过在 webpack 配置中设置output.assetModuleFilename
来修改此模板字符串
// webpack.config.js
module.exports = {
// ...其他配置
output: {
filename: 'main.js', // 入口文件的输出路径
path: path.resolve(__diranme, 'dist'),
assetModuleFilename: 'images/[hash:8][ext][query]' // hash:8取前8位hash值,ext:后缀名,query:参数
}
}
另一种自定义输出文件名的方式,通过在加载器中配置generator将某些资源发送到指定目录
module.exports = {
// ...其他配置
module: {
rules: [
// 加载图片资源
{
test: /.(png|jpg|svg|gif|jpeg)$/,
type: 'asset',
generator: {// 将资源文件输出到指定目录
filename: 'static/images/[hash:8][ext][query]'
}
}
]
}
}
处理字体图标
webpack5中默认已经内置file-loader,默认无需配置就能解析字体图标文件,也可以通过设置type来方式来解析
// webpack.config.js
module.exports = {
// ... 其他配置
module: {
rules: [
{
test: /.(ttf|woff2?)$/,
type: 'asset/resource', // 不会转base64 asset根据文件大小会自动转换base64
generator: {// 将资源文件输出到指定目录
filename: 'static/fonts/[hash:8][ext][query]'
}
}
]
}
}
其他资源比如音频视频等
// webpack.config.js
module.exports = {
// ... 其他配置
module: {
rules: [
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
type: 'asset/resource',
generator: {
filename: 'static/media/[hash:8][ext][query]'
}
}
]
}
}
eslint
可组装的JavaScript和JSX检查工具
基本使用
- 安装依赖
npm install eslint-webpack-plugin eslint -D
- 然后把插件添加到你的 webpack 配置
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
// ...
plugins: [new ESLintPlugin({
context: path.resolve(__dirname, 'src') //指定文件根目录,类型为字符串
})],
// ...
};
- 根目录下创建.eslintrc文件
// .eslintrc.js
module.exports = {
// 继承eslint规则
extends: ["eslint:recommended"],
env: { // 指定你想启用的环境
node: true, // Node.js 全局变量和 Node.js 作用域
browser: true, // 浏览器环境中的全局变量。
es6: true
},
parserOptions: {
sourceType: "module"
},
rules: {
"no-var": 2,
}
}
- 配置忽略文件.eslintignore
// .eslintignore
build/*.js
src/assets
public
dist
babel
babel是一个JavaScript编译器,主要用于将 ECMAScript 2015+ 代码转换为当前和旧版浏览器或环境中向后兼容的 JavaScript 版本
- 转换语法
- 目标环境中缺少的 Polyfill 功能(通过第三方 polyfill,例如core-js
- 源码转换
基本使用
- 安装依赖
npm install -D babel-loader @babel/core @babel/preset-env
- 配置 loader
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
- 根目录下新建babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
'env': {
'development': {
'plugins': ['dynamic-import-node']
}
}
}
HtmlWebpackPlugin(简化了 HTML 文件的创建)
该插件将为你生成一个 HTML5 文件, 在 body 中使用
script
标签引入你所有 webpack 生成的 bundle
基本使用
- 安装插件
npm install --save-dev html-webpack-plugin
- 引入插件
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js',
},
plugins: [new HtmlWebpackPlugin({
// 以public/index.html为模板
template: path.resolve(__diranme, 'public/index.html')
})],
};
webpack-dev-server(开发服务器)
webpack-dev-server
为你提供了一个基本的 web server,并且具有 live reloading(实时重新加载) 功能
基本使用
- 安装依赖
npm install --save-dev webpack-dev-server
- 引入配置
// webpack.config.js
module.exports = {
// ...其他配置
devServer: {
host: 'localhost',
port: '3000',
open: true // 是否启动之后自动打开浏览器
}
}
- 启动
webpack server
CopyWebpackPlugin
将已存在的单个文件或整个目录复制到构建目录
基本使用
- 安装依赖
npm install copy-webpack-plugin --save-dev
- 引入配置
const CopyPlugin = require("copy-webpack-plugin");
const path = require('path');
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{ from: path.resolve(__dirname, 'public/*'), to: path.resolve(__dirname, 'dist') },
],
}),
],
};
MiniCssExtractPlugin
本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载
与 extract-text-webpack-plugin 相比:
- 异步加载
- 没有重复的编译(性能)
- 更容易使用
- 特别针对 CSS 开发
基本使用
- 安装依赖
npm install --save-dev mini-css-extract-plugin
- 引入插件
建议
mini-css-extract-plugin
与 [css-loader
]
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
// ...其他配置
plugins: [new MiniCssExtractPlugin()],
module: {
rules: [
{
test: /.css$/i,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
};
优化
- 提升开发体验
- 提升打包构建速度
- 减少代码体积
- 优化代码运行性能
SourceMap
SourceMap(源代码映射)是一个将源代码与构建后的代码一一映射的方案
- 使用Devtool
// webpack.config.js
module.exports = {
// ... 其他配置
devTool: 'sourceMap'
}
- 以下选项非常适合开发环境:
eval
- 每个模块都使用 eval()
执行,并且都有 //# sourceURL
。此选项会非常快地构建。主要缺点是,由于会映射到转换后的代码,而不是映射到原始代码(没有从 loader 中获取 source map),所以不能正确的显示行数。
eval-source-map
- 每个模块使用 eval()
执行,并且 source map 转换为 DataUrl 后添加到 eval()
中。初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,并且生成实际的文件。行数能够正确映射,因为会映射到原始代码中。它会生成用于开发环境的最佳品质的 source map。
eval-cheap-source-map
- 类似 eval-source-map
,每个模块使用 eval()
执行。这是 "cheap(低开销)" 的 source map,因为它没有生成列映射(column mapping),只是映射行数。它会忽略源自 loader 的 source map,并且仅显示转译后的代码,就像 eval
devtool。
eval-cheap-module-source-map
- 类似 eval-cheap-source-map
,并且,在这种情况下,源自 loader 的 source map 会得到更好的处理结果。然而,loader source map 会被简化为每行一个映射(mapping)。
- 这些选项通常用于生产环境中:
(none)
(省略 devtool
选项) - 不生成 source map。这是一个不错的选择。
source-map
- 整个 source map 作为一个单独的文件生成。它为 bundle 添加了一个引用注释,以便开发工具知道在哪里可以找到它。
hidden-source-map
- 与 source-map
相同,但不会为 bundle 添加引用注释。如果你只想 source map 映射那些源自错误报告的错误堆栈跟踪信息,但不想为浏览器开发工具暴露你的 source map,这个选项会很有用。
HMR(热更新)
模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面
- 保留在完全重新加载页面期间丢失的应用程序状态。
- 只更新变更内容,以节省宝贵的开发时间。
- 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
webpack-dev-server默认只支持css的热更新,因为style-loader实现了HMR接口,当HRM接收到更新之后,style-loader会打补丁,并追加到style标签中
webpack-dev-server支持js热更新,需要手动在js文件增加处理
// count.js
import count from './js/count'
import sum from './js/sum'
import './css/index.scss'
import './css/iconfont.css'
console.log(count(2,1))
console.log(sum(1,2,3,4,5,6));
if (module.hot) { // 需要开启热更新
module.hot.accept('./js/count.js') // 开启指定js文件的热更新
}
如果使用脚手架搭建的项目 vue-loader和react-loader中已经实现了文件的热更新,不需要再做额外处理
include/exclude
通过指定include和exclude来减少loader需要处理的文件
如果include和exclude同时存在, exclude的优先级大于include
cache
- 开启babel缓存
module.exports = {
// ... 其他配置
module: {
rules: [
// ...
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
cacheDirectory: true, // 开启指定目录缓存babel的结果, 默认缓存路径: node_modules/.cache/babel-loader
cacheCompression: false, // 是否对缓存结果进行压缩
}
}
},
]
}
}
- eslint开启缓存
module.exports = {
// ... 其他配置
plugins: [
// ...
new ESLintPlugin({
context: path.resolve(__dirname, 'src'), //指定文件根目录,类型为字符串
cache: true, // 开启缓存
cacheLocation: path.resolve(__dirname, 'node_modules/.cache/eslint/') // 指定缓存的路径, 不指定的话 默认是在根目录下.eslintcache文件中
}),
]
}
Thread
当项目变庞大之后,项目打包速度会越来越慢,而项目中主要耗时的是对js部分的处理,如babel, eslint等操作,此时可以通过thread-loader来开启多个线程来单独处理
使用此loader,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行
在 worker 池中运行的 loader 是受到限制的。例如:
- 这些 loader 不能生成新的文件。
- 这些 loader 不能使用自定义的 loader API(也就是说,不能通过插件来自定义)。
- 这些 loader 无法获取 webpack 的配置。
每个 worker 都是一个独立的 node.js 进程,其开销大约为 600ms 左右。同时会限制跨进程的数据交换。(一般只针对耗时的操作进行使用,因为启动线程也是不小的开销)
基本使用
- 安装依赖
npm install --save-dev thread-loader
- 依赖配置
module.exports = {
module: {
rules: [
{
test: /.js$/,
include: path.resolve('src'),
use: [
{
loader: "thread-loader",
// 有同样配置的 loader 会共享一个 worker 池
options: {
// 产生的 worker 的数量,默认是 (cpu 核心数 - 1),或者,
// 在 require('os').cpus() 是 undefined 时回退至 1
workers: 2,
// 一个 worker 进程中并行执行工作的数量
// 默认为 20
workerParallelJobs: 50,
// 额外的 node.js 参数
workerNodeArgs: ['--max-old-space-size=1024'],
// 允许重新生成一个僵死的 work 池
// 这个过程会降低整体编译速度
// 并且开发环境应该设置为 false
poolRespawn: false,
// 闲置时定时删除 worker 进程
// 默认为 500(ms)
// 可以设置为无穷大,这样在监视模式(--watch)下可以保持 worker 持续存在
poolTimeout: 2000,
// 池分配给 worker 的工作数量
// 默认为 200
// 降低这个数值会降低总体的效率,但是会提升工作分布更均一
poolParallelJobs: 50,
// 池的名称
// 可以修改名称来创建其余选项都一样的池
name: "my-pool"
},
},
// 耗时的 loader (例如 babel-loader)
],
},
],
},
};
TreeShaking
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性
webpack5生产模式下自动开启了TreeShaking
针对组件库,或者工具类库,我们只会用到其中部分方法或者组件,未开启TreeShaking时,默认是将所有的内容打包进去,使用只会会去除未引用代码(dead code)
.
减少babel-loader的辅助代码
Babel 在每个文件都插入了辅助代码,使代码体积过大, babel 对一些公共方法使用了非常小的辅助代码,比如
_extend
。默认情况下会被添加到每一个需要它的文件中。
可以引入 Babel runtime 作为一个独立模块,来避免重复引入
基本使用
- 安装依赖
npm install -D @babel/plugin-transform-runtime @babel/runtime
- 配置插件
// webpack.config.js
module.exports = {
// ... 其他配置
module: {
rules: [
// ...
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
cacheDirectory: true, // 开启指定目录缓存babel的结果, 默认缓存路径: node_modules/.cache/babel-loader
cacheCompression: false, // 是否对缓存结果进行压缩
plugins: ['@babel/plugin-transform-runtime'] // 减少代码体积
}
}
},
]
}
}
图片压缩 - ImageMinimizerWebpackPlugin
- 安装依赖
npm install image-minimizer-webpack-plugin --save-dev
- 无损压缩依赖
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev
- 有损压缩依赖
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev
- 配置依赖
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
module.exports = {
// ...其他配置
optimization: {
minimizer: [
new ImageMinimizerPlugin({ // 压缩图片
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
// Lossless optimization with custom option
// Feel free to experiment with options for better result for you
options: {
plugins: [
["gifsicle", { interlaced: true }],
["jpegtran", { progressive: true }],
["optipng", { optimizationLevel: 5 }],
// Svgo configuration here https://github.com/svg/svgo#configuration
[
"svgo",
{
plugins: [
"preset-default",
"prefixIds",
{
name: "sortAttrs",
params: {
xmlnsOrder: "alphabetical"
}
}
],
},
],
],
}
},
}),
]
},
}
如果出现包下载不下来, 可以尝试通过cnpm 或者梯子进行
CodeSplit代码分割
webpack5中对于动态导入(import()异步导入)的模块会自动分割到单独的文件,但是对于静态导入的模块,则会统一打包到入口文件(main.js)中,会导致入口文件过大,首页加载慢
遇到的问题
- eslint不认识import()动态导入语法
// .eslintrc.js
module.exports = {
// ...其他配置
parserOptions: {
ecmaVersion: 11 // 指定ecmascript的版本 或者配置成2020
}
}
- 动态导入的模块没有生成独立的js文件
检查动态导入的内容是否已经在别的页面进行静态导入,这样会导致动态导入的文件不会生成单独的文件
- 在开发环境中,动态打包生成的文件名太长,默认是以文件路径为js文件名(src_js_sum_js.js)
// 通过配置webpackChunkName来指定chunk的名称
import(/*webpackChunkName: "sum" */ './js/sum.js').then(res => {
console.log('res: ', res)
})
配置
从 webpack v4 开始,移除了
CommonsChunkPlugin
,取而代之的是optimization.splitChunks
- splitChunks默认配置
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async', // 有效值为 `all`,`async` 和 `initial`。设置为 `all` 可能特别强大,因为这意味着 chunk 可以在异步和非异步 chunk 之间共享
minSize: 20000, // 生成 chunk 的最小体积(以 bytes 为单位)
minRemainingSize: 0, // 确保拆分后剩余的最小 chunk 体积超过限制来避免大小为零的模块
minChunks: 1, // 拆分前必须共享模块的最小 chunks 数。
maxAsyncRequests: 30, // 按需加载时的最大并行请求数。
maxInitialRequests: 30, // 入口点的最大并行请求数。
enforceSizeThreshold: 50000, // 强制执行拆分的体积阈值和其他限制(minRemainingSize,maxAsyncRequests,maxInitialRequests)将被忽略。
cacheGroups: { // 缓存组可以继承和/或覆盖来自 `splitChunks.*` 的任何选项
defaultVendors: {
test: /[\/]node_modules[\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
- 常用配置
// webpack.config.js
module.exports = {
// ...其他配置
optimization: { // 优化
splitChunks: { // 代码分割
chunks: 'all',
// 分隔组
cacheGroups: {
// 抽取第三方模块
vendors: {
test: /node_modules/,
priority: -10,
name: "vendors",
reuseExistingChunk: true,
},
// 抽取
commons: {
minSize: 0, // 抽取的chunk最小大小
minChunks: 2, // 最小引用数
priority: -20,
name: "common",
reuseExistingChunk: true,
},
},
},
},
}
预获取/预加载模块(prefetch/preload module)
利用浏览器的空闲时间去下载未来要使用的资源
- prefetch(预获取):将来某些导航下可能需要的资源
- preload(预加载):当前导航下可能需要资源
与 prefetch 指令相比,preload 指令有许多不同之处:
- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
- preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
- preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
- 浏览器支持程度不同。
基本使用
- 开启预获取prefetch 在动态导入的模块中使用 /* webpackPrefetch: true */
import(/* webpackPrefetch: true */ './js/sum.js').then(res => {
console.log('res: ', res)
})
打包之后 在index.html中会自动拼上link标签,告知浏览器预获取的资源
<link rel="prefetch" as="script" href="http://localhost:9530/static/js/../../static/js/src_js_sum_js.js">
- 开启预加载preload 在动态导入的模块中使用 /* webpackPrefetch: true */
import(/* webpackPreload: true */ './js/sum.js').then(res => {
console.log('res: ', res)
})
扩展
转载自:https://juejin.cn/post/7133093188889215012