自定义loader的配置:
- 可以先在resolveLoader中指定路径,然后直接使用
- 也可以配置modules,默认在node_modules下找loader,如果找不到,可以指定查找路径, 这样也是可以的
复制代码
{
resolveLoader: {
alias: {
loader1: path.resolve(__dirname, "loaders", "loader1.js")
},
modules: ["node_modules", path.resolve(__dirname, "loaders")]
},
module: {
rules: [
{
test: /\.js$/,
use: "loader1"
}
]
}
}
复制代码
配置多个loader
- loader的执行顺序: 从右到左,从下到上
- loader的分类:enforce:”pre” 在前面的; enforce:”post” 在后面; normal
- loader的执行顺序:pre => normal => inline => post
- inline-loader: let str = require(“loader4!./a.js”)
- let str = require(“-!loader4!./a.js”)
- -!不会让文件再去通过pre+normal loader来处理
- 前面加! 没有normal
- 前面加 !! 什么都没有,只要行内loader来处理
- use 配置成数组
rules: [
{
test: /\.js$/,
use: ["loader3","loader2","loader1"]
}
]
// 也可以这样:
rules: [
{
test: /\.js$/,
use: "loader3"
},
{
test: /\.js$/,
use: "loader2"
}
{
test: /\.js$/,
use: "loader1"
}
]
复制代码
pitchLoader和normalLoader
- loader的特点
- 第一个loader要返回js脚本【(最后执行的loader), 因为要将返回的内容放到eval函数中执行】
- 每一个loader只做一件内容,为了使loader在更多场景链式调用
- 每一个loader都是一个模块
- 每个loader都是无状态的,确保loader在不同模块之间不保存状态
// loader默认是由两部分组成:pitchLoader和normalLoader
function loader1(resource) {
console.log("loader1");
return resource;
}
loader1.pitch = function() {
console.log("loader1-pitch");
// return "xxx"; 无返回值时正常向后执行
}
module.exports = loader1;
use: ["loader3","loader2", "loader1"]
当pitchloader没有返回值的时候:执行的顺序是:
loader3-pitch => loader2-pitch => loader1-pitch => loader1 => loader2 => loader3
当pitchloader有返回值的时候,会跳过后面loader-pitch的执行和loader(包括自己的loader)的执行
假设loader2-pitch有返回值,执行顺序会变成:
loader3-pitch => lodaer2-pitch => loader3
复制代码
babel-loader的实现
- 先安装babel相关插件:@babel/core @babel/preset-env
let babel = require("@babel/core");
let loaderUtils = require("loader-utils");
function babelLoader(resource) {
// loader中的this上有很多属性
console.log("babel loader");
// babel-loader本身只是一个桥梁的作用,就是这个函数本身
// 1.要拿到预设(presets),babel要根据这个来转换代码
// 自定义loader可以通过this(this.query)接收到传递过来的options里面的参数
// 可以在自定义loader中通过loader-utils插件来分析和获取options传递的参数
// 需要通过一个工具库拿到loader的定义参数,叫做: loader-utils
let options = loaderUtils.getOptions(this);
const callback = this.async(); // 异步返回时执行 babel.transform异步,不能再同步return resource
// 进行代码转换
babel.transform(resource, {
...options,
sourceMaps: true,
filename: this.resourcePath.split("/").pop(), // 文件名
}, function(err, result) {
callback(err, result.code, result.map);
})
return resource;
}
module.exports = babelLoader;
复制代码
banner-loader的实现
let loaderUtils = require("loader-utils");
let {validate :validateOptions} = require("schema-utils"); // 对参数进行校验
let fs = require("fs");
function bannerLoader(resource) {
let options = loaderUtils.getOptions(this);
let callback = this.async();
let schema = {
type: "object",
properties: {
text: {
type: "string"
},
filename: {
type: "string"
}
}
};
validateOptions(schema,options, "banner-loader");
// 如果在webpack配置中加上watch:true,则是边写边打包,这时候修改banner.js里的内容,是不会触发实时编译的
// 在loader里面去读了一个文件,但是webpack认为这个文件和webpack没有关系
// 希望banner-loader当依赖的文件发生变化的时候 webpack能重新去打包 需要把这个文件加入到依赖当中
if(options.filename) {
this.addDependency(options.filename); // 自动添加文件依赖
fs.readFile(options.filename, "utf8", (err,data) => {
callback(err, `/**${data}**/${resource}`)
})
}else{
callback(null, `/**${options.text}**/${resource}`)
}
}
module.exports = bannerLoader;
复制代码
file-loader的实现
let loaderUtils = require("loader-utils");
// file-loader的目的就是根据图片生成一个md5 发射到dist目录下, file-loader还会返回当前的图片路径
function fileLoader(resource) {
// 将图片buffer内容生成md5
let filename = loaderUtils.interpolateName(this, '[hash].[ext]', {content: resource});
this.emitFile(filename, resource); // 发射文件
// return resource;
// file-loader需要返回一个文件路径
return `module.exports="${filename}"`;
}
// 源码拿到的都是字符串,需要把源码改成buffer
fileLoader.raw = true; // 二进制
module.exports = fileLoader;
复制代码
url-loader的实现
// url-loader会调用file-loader
let loaderUtils = require("loader-utils");
let mime = require("mime");
function urlLoader(resource) {
let options = loaderUtils.getOptions(this);
let limit = options.limit;
if(limit&& limit > resource.length) {
// base64
// 还是返回路径 base64开头都是:data:image/jpeg;base64, 后面是图片的内容
// this.resourcePath是资源的路径
// buffer转base64: toString("base64")
return `module.exports="data:${mime.getType(this.resourcePath)};base64,${resource.toString("base64")}"`
}else{
return require("./file-loader").call(this, resource);
}
}
urlLoader.raw = true;
module.exports = urlLoader;
复制代码
less-loader的实现
// loader就是一个函数, loader里的参数就是源码,对源码进行过滤
let less = require("less");
function lessloader(source) {
let css = "";
less.render(source, function(err, c) {
css = c.css;
});
// css = css.replace(/\n/g, "\\n"); // 用于zf-pack 手写的webpack的实现
return css;
}
module.exports = lessloader;
复制代码
style-loader的实现
function styleloader(source) {
let style = `
let style = document.createElement("style");
style.innerHTML=${JSON.stringify(source)};
document.head.appendChild(style);
`
return style;
}
module.exports = styleloader;
复制代码
css-loader
/*
@color: skyblue;
body {
background: @color;
background: url('./logo.jpeg');
}
如果不作处理 url里的路径打包后就是这个字符串, 找不到对应文件
所以需要将路径转化成require()的形式,这样就可以被打包
*/
function cssLoader(resource) {
let reg=/url\((.+?)\)/g;
let pos = 0;
let current;
let arr = ["let list = []"]
while(current = reg.exec(resource)) {
console.log(current, 123123123)
let [matchUrl, g] = current;
let last = reg.lastIndex - matchUrl.length;
arr.push(`list.push(${JSON.stringify(resource.slice(pos, last))})`)
pos = reg.lastIndex;
// 把g替换成require的写法
arr.push(`list.push('url('+require(${g})+')')`);
}
arr.push(`list.push(${JSON.stringify(resource.slice(pos))})`)
arr.push(`module.exports=list.join(" ")`)
return arr.join("\r\n");
}
module.exports = cssLoader;
// style-loader
// function loader(source) {
// let style = `
// let style = document.createElement("style");
// style.innerHTML=${JSON.stringify(source)};
// document.head.appendChild(style);
// `
// return style;
// }
// module.exports = loader;
let loaderUtils = require("loader-utils");
function loader(source) {
let style = `
let style = document.createElement("style");
style.innerHTML=${JSON.stringify(source)};
document.head.appendChild(style);
`
return style;
}
// 在style-loader上写了pitch 后面的loader都不执行
// style-loader css-loader less-loader
loader.pitch = function(remainingRequest) { // remainingRequest剩余的参数
// require("!!css-loader!less-loader!index.less")
let style = `
let style = document.createElement("style");
style.innerHTML=require(${loaderUtils.stringifyRequest(this,"!!" + remainingRequest)});
document.head.appendChild(style);
`
return style;
}
module.exports = loader;
复制代码
对于加载css文件,配置一般都是 style-loader css-loader
即css代码会先被css-loader处理一次,然后交给style-loader进行处理
css-loader的作用是处理css中的@import和url这样的外部资源
style-loader的作用是把样式插入到DOM中,方法是在head中插入一个style标签,并把样式写入到这个标签的innerHTML
pitch方法:
默认的loader都是从右向左执行,用pitching loader可以从左向右执行
为什么要使用pitching loader?
因为要把css文件的内容插入dom,所以要获取css样式,如果使用css-loader,他返回的结果是一段js字符串
这样就取不到css样式了。
为了获取css样式,我们会在style-loader中直接通过require来获取,这样返回的js就不是字符串而是一段代码,
同样的字符串,在默认模式下是当成字符串传入的,在pitching模式下是当代码运行的,这就是区别
也就是说,我们处理css的时候,其实是style-loader先执行了,他里面会调用css-loader来拿到css的内容,拿到的内容
当然是经过css-loader编译过的
在style-loader的pitch方法有返回值时,剩余的css-loader的pitch方法、css-loader的normal方法
以及style-loader的normal方法都不会执行了。而style-loader的pitch方法里面调用了require(!!.../x.css)
这就会把require的css文件当作新的入口文件,重新链式调用 剩余的loader来进行处理
值得注意的是 !!是一个标志,表示不会再重复递归调用style-loader,而只会调用css-loader处理了
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END