1. Webpack 常用 Loader
Loader 用于对模块源代码进行转换处理 ,webpack 本身只识别 JS、JSON,其他文件靠 loader 解析。
两类 loader:
- 普通 loader:返回转换后的代码
- pitch loader:执行在读取文件之前,可以直接返回,短路后面 loader。
样式类 loader
- css‑loader :解析
@import、url(),处理 css 文件中的依赖 - style‑loader :把编译后的 css 插入页面
<style>标签 - sass‑loader / sass‑loader:编译 sass/scss
- less‑loader:编译 less
- postcss‑loader:css 后处理,自动加浏览器前缀、兼容处理,配合 autoprefixer
常见顺序:
style-loader←css-loader←postcss-loader←sass‑loader(从右往左执行)
文件资源类 loader
- file‑loader:复制文件,输出到输出目录,返回文件路径(webpack4)
- url‑loader:小文件转 base64,大文件交给 file‑loader(webpack4)
webpack5 已内置
asset/resource、asset/inline模块,替代 file‑loader、url‑loader
JS/脚本处理 loader
- babel‑loader :转译 ES6+、TS,把高版本 JS 转成 ES5,依赖
@babel/core、@babel/preset‑env - ts‑loader :编译 TypeScript;也可以用 babel‑loader +
@babel/preset‑typescript
性能增强优化Loader
thread‑loader作用:开启多线程编译,把耗时 loader 的工作丢到子进程,提升打包速度 。-
CPU 是 8 核,workers 设置为
3~4,不要写os.cpus().length满开;主线程也要占用一个 CPU。 -
只给 babel‑loader 套上 thread‑loader(babel 编译最耗 CPU),css、sass 不要套 thread‑loader,收益很低。
-
开发 watch 模式,设置
poolTimeout: Infinity,避免进程反复销毁重建。 -
小项目不要上 thread‑loader几百行代码的 demo 项目,创建子进程本身有开销,打包速度反而变差。中大型项目才有收益。
-
cache: true, // webpack5持久化缓存,优先开这个,成本最低,比thread‑loader收益更稳定;优化打包优先级顺序:开启 cache > thread‑loader 多线程
-
⚠️ use 数组顺序:thread‑loader 写在数组最前面,loader 执行从右向左。执行流程:babel‑loader 做转译 → 交给 thread‑loader 丢到子进程。
Vue 相关
- vue‑loader :解析
.vue单文件组件,拆分 template、script、style
其他常用
- eslint‑loader:代码校验,打包时做 eslint 检查
- raw‑loader :把文件内容原样导出字符串(webpack5 用
asset/source替代)
webpack.config.js 配置
js
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// 通过环境变量区分开发/生产
const isProduction = process.env.NODE_ENV === 'production'
module.exports = {
mode: isProduction ? 'production' : 'development',
entry: './src/index.js',
output: {
filename: 'js/bundle.[contenthash:8].js', // ✅contenthash考点
path: path.resolve(__dirname, 'dist'),
clean: true, // 每次打包清空dist目录
publicPath: '/'
},
devtool: isProduction ? 'source-map' : 'eval-cheap-module-source-map', // ✅考点优化构建速度方式之一
// webpack5 内置持久化缓存,性能优化第一优先级
cache: {
type: 'filesystem'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/, // three等第三方库不转译,加快打包✅考点
use: [
// 多线程,只给babel用;开发watch模式不销毁进程
{
loader: 'thread-loader', // 多线程加快构建速度✅考点
options: {
workers: 3,
workerParallelJobs: 20,
poolTimeout: isProduction ? 2000 : Infinity
}
},
{
loader: 'babel-loader', // 考点✅ 转译js语法
options: {
presets: [
[
'@babel/preset-env', // 考点✅ 负责调度 polyfill。
{
useBuiltIns: 'usage', // 考点✅ 所以配置 `useBuiltIns: 'usage'` 的时候,**必须手动安装 core‑js@3**,不装就报错 / 警告。
corejs: 3,
targets: '> 0.25%, not dead'
}
]
]
}
}
]
},
// css处理:生产抽离css文件;开发用style-loader注入页面
{
test: /\.css$/,
// 考点✅ minicssextractplugin 分包,style-loader是pitchloader
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader'
]
},
// scss/sass
{
test: /\.s[ac]ss$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader',
'sass-loader'
]
},
// webpack5内置资源模块:图片、字体、three模型 glb/gltf/obj
{
test: /\.(png|jpe?g|gif|svg|glb|gltf|obj|woff2?|ttf)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 小于8kb转base64内联
}
},
generator: {
filename: 'assets/[name].[contenthash:8][ext]'
}
}
]
},
// 考点✅ 提高构建速度,缩小查找范围
resolve: {
extensions: ['.js', '.json']
},
plugins: [
// 生产环境才实例化抽离css插件
...(isProduction
? [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css'
})
]
: [])
],
// 开发服务器
devServer: {
static: path.resolve(__dirname, 'dist'),
open: true,
hot: true,
compress: true,
port: 8080
}
}
⚠️ loader 执行顺序:数组从后往前 / 从下往上执行。
生产环境不要用 style-loader ,换成mini-css-extract-plugin抽离单独 css 文件:
2. 手写 Webpack Loader
loader本质就是一个函数,接收源代码字符串,处理后返回新代码。
- 入参:
source源文件内容 - 返回:处理过后的代码字符串
不能写箭头函数,webpack需要this上下文(this.query、this.callback)
1. 最简单同步loader demo:replace-loader
功能:把源码里的 hello 替换成 Hello Webpack Loader
新建 loaders/replace-loader.js
js
// 同步loader
module.exports = function (source) {
// source:读取到的文件原始内容
const result = source.replace(/hello/g, 'Hello Webpack Loader')
// 返回处理完的代码
return result
}
webpack.config.js 使用自定义loader
自定义loader需要配置 resolveLoader 告诉webpack去哪里找loader
js
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
resolveLoader: {
// 优先去 ./loaders 目录找自定义loader
modules: [path.resolve(__dirname, './loaders'), 'node_modules']
},
module: {
rules: [
{
test: /\.js$/,
use: 'replace-loader'
}
]
}
}
src/index.js
js
console.log('hello world')
打包后输出变成:console.log('Hello Webpack Loader world')
2. 接收loader配置参数
通过 this.getOptions() 获取 options(webpack5推荐,替代 this.query)
loaders/replace-loader.js
js
module.exports = function (source) {
// 获取loader的options配置
const options = this.getOptions()
const from = options.from || 'hello'
const to = options.to || 'hi'
const output = source.replace(new RegExp(from, 'g'), to)
return output
}
webpack配置传参:
js
rules: [
{
test: /\.js$/,
use: {
loader: 'replace-loader',
options: {
from: 'hello',
to: '🎉你好webpack🎉'
}
}
}
]
3. 异步loader this.callback
当内部有异步逻辑(读文件、网络请求),不能return,使用 this.callback(err, content)
loaders/async-replace-loader.js
js
module.exports = function (source) {
// 告诉webpack这是异步loader
const callback = this.async()
// 模拟异步操作
setTimeout(() => {
const res = source.replace(/hello/g, '异步处理完成')
// 参数1:错误,参数2:处理后的源码
callback(null, res)
}, 100)
}
callback完整参数:
callback(error, content, sourceMap, ast)
4. loader分类(重要面试点)
- 同步loader:直接 return 结果
- 异步loader :
this.async()获取callback,异步回调返回 - pitch loader :loader上写
pitch方法,从左到右执行,正常loader是从右往左执行。
pitch阶段:在读取文件内容之前执行,可以直接返回,跳过后面loader执行。
pitch简单示例:
js
module.exports = function (source) {
return source
}
// pitch函数
module.exports.pitch = function (remainingRequest) {
console.log('pitch执行', remainingRequest)
// 如果这里return字符串,会直接作为结果,后面loader全部跳过
}
执行顺序:pitch(左→右) → 读取文件 → loader执行(右→左)
5. 小实操:简易raw‑loader
读取文件,直接把文件内容导出字符串
js
// raw-loader.js
module.exports = function(source) {
return `module.exports = ${JSON.stringify(source)}`
}
面试考点总结
- loader本质是函数,输入源代码,输出转换后的代码;只处理内容,不处理文件读写。
- 普通loader:从右向左执行 ;pitch:从左向右执行。
- 同步return;异步使用
this.async()拿到callback。 - webpack5获取配置:
this.getOptions()。 - loader不能依赖webpack内部API,只操作字符串。
小坑:自定义loader不要用箭头函数,会丢失this上下文!