# webpack loader
# 1. 介绍
# 1.1. 概念
帮助 webpack 将不同类型的文件转换为 webpack 可识别的模块
参考:
# 1.2. 分类
category | desc |
---|---|
pre | 前置 loader |
normal | 普通 loader |
inline | 内联 loader |
post | 后置 loader |
# 1.3. 执行顺序
说明:
- 相同类别的 loader 执行顺序: 从右到左,从下到上
- 不同类别的 loader 执行顺序:
pre > normal > inline > post
示例1:
// 此时 loader 执行顺序:loader3 - loader2 - loader1
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [ 'loader1', 'loader2', 'loader3' ]
},
],
},
};
示例2:
// 此时 loader 执行顺序:loader3 - loader2 - loader1
module.exports = {
module: {
rules: [
{ test: /\.jsx$/, loader: 'loader1' },
{ test: /\.css$/, loader: 'loader2' },
{ test: /\.jpg$/, loader: 'loader3' },
],
},
};
示例3:
// 此时 loader 执行顺序:loader1 - loader2 - loader3
module.exports = {
module: {
rules: [
{ test: /\.jsx$/, loader: 'loader1', enforce: 'pre' }, // 前置 loader
{ test: /\.css$/, loader: 'loader2' }, // 普通 loader
{ test: /\.jpg$/, loader: 'loader3', enforce: 'post' }, // 后置 loader
],
},
};
# 1.4. 使用方式
- 配置方式,指定 pre、normal、post loader
- 内联方式,指定 inline loader
# 1.4.1. 配置方式
说明:
- 在 webpack.config.js 中配置 module.rules.Rule 即可
示例:
module.exports = {
module: {
rules: [
{
test: /\.jsx$/,
loader: 'loader1',
enforce: 'pre'
},
],
},
};
# 1.4.2. 内联方式
说明:
- inline loader (内联 loader)
- 在
import
语句中指定用哪些 loader 来处理这个模块,指定的那些 loader 都是 inline loader
示例1. import 'style-loader!css-loader?modules!./styles.css';
- 使用
style-loader
、css-loader
处理./styles.css
模块 css-loader?modules
中的?modules
是 query string(查询字符串)- 各个部分(loader、资源路径)之间用
!
分隔
示例2. import '!style-loader!css-loader?modules!./styles.css';
- 打头的前缀
!
,表示跳过 normal loader - 也就是说,跳过其它也能处理这个文件的 普通 loader (webpack.config.js 中配置的)
示例3. import '-!style-loader!css-loader?modules!./styles.css';
- 打头的前缀
-!
,表示跳过 pre、normal loader
示例4. import '!!style-loader!css-loader?modules!./styles.css';
- 打头的前缀
!!
,表示跳过 pre、normal、post loader
# 2. 第一个 loader
目录:
项目目录
proj/ loaders/ my-first-loader.js public/ index.html src/ main.js package.json webpack.config.js
配置:
webpack.config.js
module.exports = { // ... module: { rules: [ { test: /\.js$/, loader: './loaders/my-first-loader.js' } ], }, }
my-first-loader.js:
/** * @param {string|Buffer} content Content of the resource file * @param {object} map SourceMap data consumable by https://github.com/mozilla/source-map * @param {any} meta Meta data, could be anything, 别的 loader 传递的数据 * @return {string} */ function myFirstLoader(content, map, meta) { console.log('hello, my first loader!'); return content; } module.exports = myFirstLoader;
参考:
# 3. loader 分类
- 同步 loader (Synchronous Loaders)
- 异步 loader (Asynchronous Loaders)
- "Raw" Loader
- Pitching Loader
# 3.1. 同步 loader
方式一:
module.exports = function (content, map, meta) {
const transformedContent = someSyncOperation(content, map, meta)
return transformedContent;
};
方式二:
module.exports = function (content, map, meta) {
/*
第一个参数:(err) 代表是否有错误
第二个参数:(content) 处理后的内容
第三个参数:(source-map) 继续传递 source-map
第四个参数:(meta) 给下一个 loader 传递参数
*/
this.callback(null, content, map, meta);
return; // always return undefined when calling callback()
};
参考:loaders#synchronous-loaders (opens new window)
# 3.2. 异步 loader
module.exports = function (content, map, meta) {
const callback = this.async();
setTimeout(() => {
const transformedContent = someSyncOperation(content, map, meta)
// 参数列表与 this.callback 相同
callback(null, transformedContent, map, meta);
}, 1000)
};
参考:loaders/#asynchronous-loaders (opens new window)
# 3.3. "Raw" Loader
说明:
- 可以使用异步、同步操作
- 接收 Buffer 数据,通常用于处理媒体文件(图片等)
示例:
module.exports = function rawLoader(content) {
assert(content instanceof Buffer);
return someSyncOperation(content);
// return value can be a `Buffer` too, This is also allowed if loader is not "raw"
};
// raw 属性也可以设置在函数对象(rawLoader)上
module.exports.raw = true;
参考:
# 3.4. Pitching Loader
说明:
所有的 pitch 方法从左到右执行完后,再从右到左执行 loader,如下:
loader-1 ~ pitch method loader-2 ~ pitch method loader-3 ~ pitch method loader-3 loader-2 loader-1
熔断机制,一旦某个 pitch 方法有返回值,则该 pitch 所在 loader 及后续 loader 都不执行
loader-1 ~ pitch method loader-2 ~ pitch method loader-1
示例:
/* webpack.config.js */
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: ['./pitch-loader-1.js', './pitch-loader-2.js', './pitch-loader-3.js']
}
],
},
}
/* pitch-loader-1.js */
module.exports = function (content) {
console.log('loader-1')
return content;
};
module.exports.pitch = function () {
console.log('loader-1 ~ pitch method')
};
/* pitch-loader-2.js */
module.exports = function (content) {
console.log('loader-2')
return content;
};
module.exports.pitch = function () {
console.log('loader-2 ~ pitch method')
// return 'something';
};
/* pitch-loader-3.js */
module.exports = function (content) {
console.log('loader-3')
return content;
};
module.exports.pitch = function () {
console.log('loader-3 ~ pitch method')
};
结果:
正常情况
loader-1 ~ pitch method loader-2 ~ pitch method loader-3 ~ pitch method loader-3 loader-2 loader-1
pitch-loader-2.js 中 pitch 函数有返回值,则会熔断:
loader-1 ~ pitch method loader-2 ~ pitch method loader-1
参考:
# 4. loader API
The Loader Context: (this
):
- The loader context represents the properties that are available inside of a loader assigned to the
this
property. - loaders/#the-loader-context (opens new window)
this.callback:
A function that can be called synchronously or asynchronously in order to return multiple results.
参数
this.callback( err: Error | null, content: string | Buffer, sourceMap?: SourceMap, meta?: any );
示例
module.exports = function(content, map, meta) { this.callback(null, content, map, meta); }
this.async:
Tells the loader-runner that the loader intends to call back asynchronously. Returns this.callback.
示例
module.exports = function(content, map, meta) { const callback = this.async(); setTimeout(() => { callback(null, content, map, meta); }, 2000) }
this.getOptions(schema?: JSONSchema):
- Extracts given loader options. Optionally, accepts JSON schema as an argument.
- Since webpack 5, this.getOptions is available in loader context. It substitutes getOptions method from loader-utils.
- loaders/#thisgetoptionsschema (opens new window)
this.emitFile:
- Emit a file. This is webpack-specific.
emitFile(name: string, content: Buffer|string, sourceMap: {...})
- loaders/#thisemitfile (opens new window)
- JSON Schema (opens new window)
this.utils:
Access to contextify and absolutify utilities.
contextify
: Return a new request string avoiding absolute paths when possible.(返回相对路径)absolutify
: Return a new request string using absolute paths when possible.(返回绝对路径)
示例
module.exports = function (content) { // 获取相对路径,相对 当前 loader 所在文件 this.utils.contextify( this.context, this.utils.absolutify(this.context, './index.js') ); this.utils.absolutify(this.context, this.resourcePath); // … return content; };
# 5. clean-log-loader
说明:
- 自定义一个 loader ,用于清除文件中的
console.log(xxx);
语句
配置:
webpack.config.js:
module.exports = { module: { rules: [ { test: /\.js$/, loader: './loaders/clean-log-loader.js' }, ], }, }
clean-log-loader.js:
module.exports = function (content, map, meta) { const transformedContent = content.replace(/console\.log\(.*\);?/g, ''); this.callback(null, transformedContent, map, meta); }
# 6. copyright-loader
说明:
- 自定义一个 loader,用于给文件设置版权信息
配置:
webpack.config.js:
module.exports = { module: { rules: [ { test: /\.js$/, loader: './loaders/copyright-loader.js', options: { author: '吴钦飞', } }, ], }, }
copyright-loader.js:
const schema = { type: 'object', properties: { author: { type: 'string' } }, // 额外的属性 additionalProperties: false }; module.exports = function (content, map, meta) { const { author } = this.getOptions(schema); const copyright = `/* 当前代码归 ${author} 所有 */`; const transformedContent = ` ${copyright} ${content} `; this.callback(null, transformedContent, map, meta); }
# 7. babel-loader
说明:
- 自定义一个 loader,用于 ES6 转 ES5
配置:
webpack.config.js:
module.exports = { module: { rules: [ { test: /\.js$/, loader: './loaders/babel-loader.js', options: { presets: ['@babel/preset-env'], } }, ], }, }
babel-loader.js:
const { transform } = require("@babel/core"); const schema = { type: 'object', properties: { presets: { type: 'array' } }, // 额外的属性 additionalProperties: false }; module.exports = function (content, map, meta) { const options = this.getOptions(schema); const callback = this.async(); transform(content, options, function(err, result) { if (err) { callback(err); return; } const { code, map, ast } = result; callback(null, code, map, ast); }); }
参考:
# 8. file-loader
说明:
- 自定义一个 loader,用于处理图片资源
安装:
npm i -D loader-utils
配置:
webpack.config.js:
module.exports = { module: { rules: [ { test: /\.(png|jpe?g|gif)$/, loader: './loaders/file-loader.js', // webpack5 默认会处理 asset 资源, // 组织 Asset Module 处理 asset 资源 type: 'javascript/auto', }, { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, ], }, }
file-loader.js
const { interpolateName } = require('loader-utils'); module.exports = function (buffer) { // 根据文件内容生成 hash,并格式化文件名 const interpolatedName = interpolateName( this, `images/[name].[hash:8].[ext][query]`, { content: buffer } ); // 生成文件 this.emitFile(interpolatedName, buffer); // 暴露出去,交给 webpack 使用 // 注意:使用 export default 会出问题 const content = `module.exports = '${interpolatedName}'` return content; } module.exports.raw = true;
main.js:
import './css/style.css';
style.css:
.img-png { background-image: url(../images/1.png); }
参考:
- loader-utils#interpolatename (opens new window)
- loaders/#thisemitfile (opens new window)
- asset-modules (opens new window)
# 9. style-loader
说明:
- 自定义一个 loader,用于将 css 代码插入页面
配置:
webpack.config.js:
module.exports = { module: { rules: [ { test: /\.less$/, use: [ './loaders/style-loader.js', 'css-loader', 'less-loader' ] }, ], }, }
style-loader.js
const styleLoader = function () {}; /* css-loader 处理后的结果是 JS 代码, 这些 JS 代码执行完毕后导出的才是 css 代码 执行后续 loader 处理完的 JS 代码: import result from '!!css-loader!less-loader!./style.less' */ styleLoader.pitch = function(remainingRequest) { /* W:\dev\blog\codes\frontend\webpack\guide\custom-loaders\node_modules\css-loader\dist\cjs.js ! W:\dev\blog\codes\frontend\webpack\guide\custom-loaders\node_modules\less-loader\dist\cjs.js ! W:\dev\blog\codes\frontend\webpack\guide\custom-loaders\src\css\style.less */ console.log('remainingRequest:', remainingRequest) const relativeRequest = remainingRequest .split("!") .map((part) => this.utils.contextify(this.context, part)) .join("!"); /* ../../node_modules/css-loader/dist/cjs.js ! ../../node_modules/less-loader/dist/cjs.js ! ./style.less */ console.log('relativeRequest:', relativeRequest); const script = ` import css from "!!${relativeRequest}" const styleEl = document.createElement('style') styleEl.innerHTML = css document.head.appendChild(styleEl) `; return script; }; module.exports = styleLoader;
main.js:
import './css/style.less';
style.css:
.img-png { background-image: url(../images/1.png); }
参考:
上一篇: 下一篇: