loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 loader API,并通过 this 上下文访问。
Webpack的配置离不来 loader
,官方也有关于如何编写一个loader的文档介绍,这篇文章会通过手写一些常见的loader
,加深对loader的认识,提高工作中的开发效率。
导出loader
loader
是一个函数,接受匹配到的文件资源字符串和SourceMap
,我们可以通过修改文件内容的字符串返回给下个一loader
处理:
module.exports = function(source,map){
return source;
}
准备工作
为了方便我们编写loader
,我们先准备好webpack
环境:
生成一份 package.json
:
npm init -y
安装webpack
:
npm install webpack webpack -D
创建webpack.config.js
文件,并输入以下内容:
// webpack.config.js
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
package.json
加入scripts
命令:
"scripts": {
"build": "webpack",
"dev": "webpack --watch"
},
配置别名
我们知道,webpack
默认会到 node_modules
里面找对应的loader
,这样不方便我们调试,我们可以通过给webpack.config.js
添加resolveLoader
属性,将loader
指向我们创建的loaders
文件夹
...
resolveLoader: {
modules: [
path.resolve(__dirname, "node_modules"),
path.resolve(__dirname, "./loaders"),
],
}
...
创建loaders
文件夹,里面将存放我们自己编写的loader
:
mkdir loaders
另外通过resolveLoader.alias
也能配置loader
别名:
resolveLoader: {
alias: {
loader1: resolve(__dirname, "./loaders/loader1.js"),
loader2: resolve(__dirname, "./loaders/loader2.js"),
loader3: resolve(__dirname, "./loaders/loader3.js"),
},
},
module:{
rules:[
{
test:/\.(js)$/,
use:["loader1.js","loader2.js","loader3.js]
}
]
}
第一个 loader
先写一个简单的loader
练练手,前面说到loader
可以替换目标资源文件的内容,这里我要把项目里面的console.log
都干掉,毕竟项目上线的时候控制台出现调试内容不合适。
在loaders
文件夹下新建cleanlog-loader.js
,输入下面内容:
module.exports = function(source){
return source.replace(/console\.log\(.*\);?\n/g, '');
}
到webpack.config.js
添加配置:
module:{
rules:[{
test: /\.(js)$/,
use:"cleanlog-loader"
}]
}
src
文件夹下新建index.js
写点console
内容,然后控制台执行npm run build
,可以发现编译后的文件dist/bundle.js
已经没有console
信息了。
banner-loader
banner-loader
可以在脚本文件添加注释信息,配置方式如下:
module:{
rules:[{
test: /\.(js)$/,
use:{
loader:"banner-loader",
options:{
text:"/**** build from chenwl ****/",
}
}
}]
}
这里有两个地方需要考虑:
- 获取配置信息
loader-utils
- 校验配置参数是否正确
schema-utils
在 loaders
文件夹下创建banner-loader.js
,输入下面内容:
const fs = require("fs");
const {resolve} = require("path");
const loaderUtils = require("loader-utils");
const { validate } = require("schema-utils");
module.exports = function (source) {
// 获取配置参数
let options= loaderUtils.getOptions(this);
let schema = {
type: "object",
properties: {
text: {type: "string"}
}
}
// 校验参数是否正确
validate(schema, options,"banner-loader")
return `${options.text} ${source}`;
}
当然也可以通过读取文件内容写入,新增配置参数 filename
,对模板文件进行读取:
if(options.filename){
// 依赖某个文件变化,做到实时更新;
this.addDependency(path.resolve(__dirname, `../${options.filename}`))
return fs.readFileSync(options.filename, "utf-8") + source;
}
利用addDependency
,如果目标文件发生变化,可以在观察模式(watch mode)下重编译,用npm run dev
启动并修改filename
对应的文件试试
babel-loader
babel-loader
依赖@babel-core
和@babel/preset-env
,通过npm先安装:
npm install @babel-core @babel/preset-env -D
webpack-config.js
添加rules:
{
test: /\.(js)$/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
]
}
可以通过this.async
方法在loader中编写异步代码:
// babel-loader.js
const loaderUtils = require("loader-utils");
const babel = require("@babel/core");
module.exports = function(source){
const options = loaderUtils.getOptions(this);
const cb = this.async(); // 异步函数
babel.transform(source,{
...options,
sourceMaps:true
},function(err,result){
cb(err, result.code)
})
}
style-loader 和 less-loader
// less-loader
let less = require("less");
module.exports = function (source) {
let cssStr = "";
// 用less转成css
less.render(source,function(error,result) {
if(!error){
cssStr = result.css
}
});
return cssStr
}
// style-loader
module.exports = styleLoader(source) {
// js 字符串,生成style标签插入到模板文件中
let code = `
let styleEl = document.createElement("style");
styleEl.innerHTML = ${JSON.stringify(source)};
document.head.appendChild(styleEl);
`;
return code.replace(/\/n/,"");
}
file-loader
我们知道 webpack
是识别不了js以外的其它文件的,所以file-loader
需要设置loader.raw = true
,让 loader 知道现在处理的是二进制的内容:
const loaderUtils = require("loader-utils");
function fileLoader(source) {
// interpolate 插值
let fileUrl = loaderUtils.interpolateName(this, "[hash].[ext]", {
content: source,
});
this.emitFile(fileUrl,source);
// 转换后的Buffer最终是要被插入到页面中,返回类型只能是 buffer 或 string
// fileUrl 记得加引号,不然会报错哦
return `module.exports = '${fileUrl}'`
}
// loader 处理的是二进制的内容
fileLoader.raw = true;
module.exports = fileLoader;
url-loader
url-loader
目的是将小图转成base64编码,否则就用file-loader
处理:
// url-loader.js
const loaderUtils = require("loader-utils");
const mime = require("mime");
function urlLoader(source) {
let {limit} = loaderUtils.getOptions(this);
if(limit > source.length){
let code = `data:${mime.getType(this.resourcePath)};base64,${source.toString('base64')}`
return `module.exports = "${code}"`
}else{
return require("./file-loader").call(this, source)
}
}
urlLoader.raw = true;
module.exports = urlLoader;