webpack 深入系列
利用 webpack 可以做哪些事情?
- 代码转换
- 文件优化
- 代码分割
- 模块合并
- 自动刷新
- 代码校验
- 自动发布 ...
常见配置
npm install webpack webpack-cli -D
扩展:
-D
等价于--save-dev
, 用于开发环境时的依赖-S
等价于--save
, 用于生产环境时的依赖
- 指定配置文件
npx webpack --config 自定义名称
- 开发服务器
npm install webpack-dev-server -D
devServer: { // 开发服务器的配置
port: 3000, // 指定端口
progress: true, // 开启进度条
contentBase: './dist', // 指定静态服务的目录
open: true, // 自动打开网页
compress: true, // 启动gzip压缩
}
模块
loader 特点:希望单一,每个 loader 做对应的事情
loader 的用法:字符串只用一个 loader, 多个 loader 需要[]
loader 的顺序:从右向左执行, 从下到上执行
loader 还可以写成对象的形式
- style-loader: 是把 css 插入到 head 标签中
- css-loader: 解析 @import 语法
- less-loader: 把.less 文件转换为.css 文件
- stylus-loader: 把.styl 文件转换为.styl 文件
- sass-loader: 把.scss 文件转换为.scss 文件
- postcss-loader: 一般配合 autoprefixer 一起使用,添加浏览器前缀
- 插件
- html-webpack-plugin
npm install html-webpack-plugin -D
new HtmlWebpackPlugin({
template: './src/index.html', // 以哪个html文件作为模板
filename: 'tmc.html', // 指定模板的名称,
minify: {
removeAttributeQuotes: true, // 删除双引号
collapseWhitespace: true, // 压缩为一行
},
hash: true, // hash 值
title: '汤小梦', // 设置网站标题
favicon: './src/public/favicon.ico', // 设置网站的 icon 图
inject: true | 'head' | 'body' | false, // 设置为 true 或 body,所有的 javascript 资源将被放置到 body 元素的底部,'head' 或 false 将放置到 head 元素中
})
注意:动态设置 title 时,需在模板的<title>
标签中添加 <%= htmlWebpackPlugin.options.title %>
- mini-css-extract-plugin
npm install mini-css-extract-plugin -D
new MiniCssExtractPlugin({
filename: 'css/index.css', // 输出的文件名
})
// 使用
style-loader ==> MiniCssExtractPlugin.loader
- optimize-css-assets-webpack-plugin
npm install optimize-css-assets-webpack-plugin -D
optimization: { // 优化项
minimizer: [
new OptimizeCssAssetsWebpackPlugin(), // 压缩打包过后的CSS
]
},
- uglifyjs-webpack-plugin
npm install uglifyjs-webpack-plugin -D 【弃用】
npm install terser-webpack-plugin -D
optimization: { // 优化项
minimizer: [
new UglifyJsPlugin({ // 压缩打包过后的JS
cache: true, // 启动缓存
parallel: true, // 并发压缩
sourceMap: true // 启动源码调试
}),
new TerserWebpackPlugin()
]
},
注意:uglify-js 只支持压缩 ES5,uglify-es 用来支持 es6+,之后放弃维护了。如果要使用 uglify-js 压缩 ES6+需要先使用 babel 转码成 ES5. 取代 ugligy-es 的是 uglify-es 的一个分支 terser; webpack 放弃了 uglifyjs-webpack-plugin
转而使用 terser-webpack-plugin
;
高级配置
优化策略
AST 抽象语法树
Tapable
webpack 的流程
手写 loader
什么是 Loader
Webpack 只能处理 JavaScript 的模块,如果要处理其他类型的文件,需要使用 loader 进行准转换。它是指用来将一段代码转换成另一段代码的 webpack 加载器. loader 由两部分组成,一分部是 pitchLoader, 另一部分是 normalLoader. pitchLoader 和 normalLoader 执行顺序相反. pitchLoader 中有返回值就直接不走后面的了.
loader 的顺序
从右向左,从下到上
loader 的使用方式
- 单个 loader 可以写成字符串也可以写成对象
- 多个 loader 写成数组
loader 的特点
- 第一个 loader 要返回一个 js 脚本. (默认只能接受 String Or Buffer)
- 每个 loader 只能做一件事,为了 loader 再更多场景下链式调用
- 么讴歌 loader 都是一个模块
- 每个 loader 都是无状态的, 确保 loader 在不同模块转换直接不保存状态
loader 的分类
- pre 在前面的
- post 在后面的
- normal 正常的
行内 loader 的用法:
// 符号的含义 // -! 不会让文件 去通过 pre + normal loader来处理了 // ! 没有normal // !! 什么都不要 只要inline-loader let str = require('inline-loader!./xx.js')
顺序:pre -> normal -> inline -> post
查找 loader 的几种方式
- 绝对路径
use: path.resolve(__dirname, 'loader.js')
- 设置别名
resolveLoader: { alias: { loader: path.resolve(__dirname, 'loader.js') } } use: loader
- 自动查找
resolveLoader: { // 默认在node_modules下查找,找不到就自动到loaders目录下查找 modules: ['node_modules', path.resolve(__dirname, 'loaders')] }
babel-loader 的实现
let babel = require('@babel/core')
let loaderUtils = require('loader-utils')
function loader(source) {
// console.log(this.resourcePath) 绝对路径 this loader上下文
let options = loaderUtils.getOptions(this)
let cb = this.async()
babel.transform(
source,
{
...options,
sourceMaps: true,
filename: this.resourcePath.split('/').pop(),
},
function(err, result) {
cb(err, result.code, result.map) // 异步
}
)
}
module.exports = loader
手写 plugin
优化
打包的数据分析
npm install friendly-errors-webpack-plugin node-notifier -D
const path = require('path')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
const notifier = require('node-notifier')
const icon = path.join(__dirname, 'icon.jpg')
module.exports = {
plugins: [
new FriendlyErrorsWebpackPlugin({
onErrors: (severity, errors) => {
let error = errors[0]
notifier.notify({
title: 'webpack编译失败',
message: error.name,
subscribe: error.file || '',
icon,
})
},
}),
],
}
npm install speed-measure-webpack-plugin -D // 适用于 webpack4
npm install speed-measure-webpack5-plugin -D // 适用于 webpack5
const SpeedMeasureWebpack5Plugin = require('speed-measure-webpack5-plugin')
const smw = new SpeedMeasureWebpack5Plugin()
module.exports = smw.wrap({
// ...
})
npm install webpack-bundle-analyzer -D
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
module.exports = smw.wrap({
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'disabled', // 不启动展示打包报告的HTTP服务器
generateStatsFile: true, // 要生成stats.json文件
}),
],
})
编译时间的优化: 减少要处理的文件、缩小查找的范围
编译体积优化
如何设置环境变量
mode 的默认值
- webpack 的 mode 默认值为 production
- webpack serve 的 默认值为 development
- 可以在模块内通过 process.env.NODE_ENV 获取当前的环境变量,无法在 webpack 配置文件中获取此变量
// webpack.config.js
console.log('webpack process.env.NODE_ENV', process.env.NODE_ENV) // node环境 默认值是undefined
// src/index.js
console.log('src/index.js process.env.NODE_ENV', process.env.NODE_ENV) // production
设置 mode 值的方式
- 命令行 mode 方式
// package.json
"build": "webpack --mode development"
- 命令行 env 方式
// package.json
"build": "webpack --env development"
module.exports = (env) => {
console.log(env)
return ({
mode: env == 'development' ? 'development' : 'production'
})
})
- DefinePlugin 方式
module.exports = {
plugins: [
new webpack.DefinePlugin({
ENV: JSON.stringify('development'),
}),
],
}
注意:该全局变量定义在 编译 时的,在浏览器 运行 阶段就是值了访问不到了
- cross-env 方式
"build": "cross-env NODE_ENV=development webpack"
- 创建 .env 和 dotenv 方式
// .env
NODE_ENV = production
require('dotenv').config()
preload vs prefetch
- preload 是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源
- 而 prefetch 是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源 所以建议:对于当前页面很有必要的资源使用 preload,对于可能在将来的页面中使用的资源使用 prefetch