3. loader专

自定义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
喜欢就支持一下吧
点赞0 分享