webpack5 loader原理

101 阅读2分钟

loader原理

概念

帮助webpack将不同类型的文件转换为webpack可识别的模块。

执行顺序

  1. 分类 除内联外添加enforce属性设置
    • pre: 前置loader
    • normal: 普通loader
    • inline: 内联loader
      import Styles form 'style-loader!css-loader?modules!./index.css';
      // 可以通过加不同前缀 跳过其他类型loader
      // ! 跳过normal loader
      import Styles form '!style-loader!css-loader?modules!./index.css';
      // -! 跳过跳过pre normal loader
      import Styles form '-!style-loader!css-loader?modules!./index.css';
      // !! 跳过pre normal post loader
      import Styles form '!!style-loader!css-loader?modules!./index.css';
      
    • post: 后置loader
  2. 执行顺序
    • 优先级:pre > normal > inline > post
    • 相同优先级 从右到左 从下到上

第一个loader

/*
    loader就是一个函数,当webpack解析资源时,会调用相应的loader去处理
    loader接收文件内容作为参数,并返回内容出去
        content 文件内容
        map SourceMap
        meta 别的loader传递的数据
*/
module.exports = function(content, map, meta){
    return content
}

loader种类

  1. 同步loader
    <!-- module.exports = function(content){
     return content
    } -->
    /*
     第一个参数 error 代表是否有错误
     第二个参数 content 处理后的内容
     第三个参数 source-map 继续传递 source-map
     第四个参数 meta 给下一个loader传递参数
    */
    module.exports = function(content, map, meta){
     this.callback(null, content, map, meta);
    }
    
  2. 异步loader
    module.exports = function(content, map, meta){
        const callback = this.async();
        setTimeout(() => {
            callback(null, content, map, meta);
        }, 1000)     
    }
    
  3. raw loader
     // raw loader接收到的content是Buffer数据(二进制)
     module.exports = function(content){
         return content   
     }
     module.exports.raw = true;
    
  4. pitch loader
    module.exports = function(content){
         return content   
     }
     // pitch方法会从前到后先执行 一旦return 后面pitch不执行 之执行上一个loader的正常方法 再从后往前执行正常方法
     module.exports.pitch = function(){}
    

loader API

  • this.async()
    异步回调loader,返回this.callback。const callback = this.async();
  • this.callback()
    可以同步或异步调用的并返回多个结果的函数
  • this.getOptions(schema)
    获取loader的options
  • this.emitFile(name, content, sourceMap)
    产生一个文件
  • this.utils.contextify(context, request)
    返回一个相对路径
  • this.utiles.absolutify(context, request)
    返回一个绝对路径

手写clean-log-loader

module.exports = function CleanLogLoader(content){
    return content.replace(/console\.log\(*\);?/g, "");
}

自定义banner-loader

    const schema = {
        "type": "object",
        "properties": {
            "author": "string"
        },
        "addtionalProperties": false
    }
    module.exports = functin(content){
        // schema 对options的验证规则
        // schema符合JSON Schema的规则
        const options = this.getOptions(schema)
        const prefix = `
            /*
            * Author: ${options.author}
            */
        `;
        return prefix + content
    }

自定义babel-loader

// 下载 @babel/core @babel/preset-env
const babel = require('@babel/core');
module.exports = function (content) {
    const schema = {
        "type": "object",
        "properties": {
            "presets": "array"
        },
        "addtionalProperties": true,
    }
    const callback = tihs.async();
    const options = getOptions(schema)
    // 使用babel对代码进行编译
    babel.transform(code, options, function (err, result) {
        if (err) callback(err)
        else callback(null, result.code)
    })
}

自定义file-loader

将资源原样输出 type: asset

// 下载loader-utils
const loaderUtils = require('loader-utils');
module.exports = function (content) {
    // 根据文件内容生成hash值文件名
    // 将文件输出到dist
    // return `module.exports = ${interpolatedName}`
    let interpolatedName = loaderUtils.interpolateName(
        this, // loaderContext,
        "[hash].[ext][query]",
        {
            content
        }
    )
    interpolatedName = `images/${interpolatedName}`
    this.emitFile(interpolatedName, content);
    return `module.exports = ${interpolatedName}`
}
// 需要处理字体图片
module.exports.raw = true;
// 配置loader时可以 type: javascript/auto 阻止webpack默认处理图片资源

自定义style-loader

module.exports = function (content) {
    /*
        1. 直接使用style-loader,只能处理样式,不能处理样式中引入的其他资源
        2. 借助css-loader解决样式中引入的其他资源的问题
        问题是css-loader暴露了一段js代码,style-loader需要执行js代码,得到返回值,
        再动态创建style标签,插入到页面中,不好操作
    */
    // const script = `
    //     const styleEl = document.createElement('style');
    //     styleEl.innerHTML = ${JSON.stringify(content)};
    //     document.head.appendChild(styleEL);
    // `
}
module.exports.pitch = function (remainingRequest) {
    // remainingRequest 剩下还需要处理的loader
    // 1. 将remainingRequest中绝对路径改成相对路径(因为后面只能使用相对路径)
    const relativePath = remainingRequest.split('!').map((absolutePath) => {
        return this.utils.contextify(this.context, absolutePath)
    }).join('!');
    // 2. 引入css-loader处理后的资源
    // 3. 创建style,将内容引入到页面中生效
    const script = `
        import style from "!!${relativePath}";
        const styleEl = document.createElement('style');
        styleEl.innerHTML = style;
        document.head.appendChild(styleEL);
     `
    // 终止后面loader执行
    return script;
}