webpack之loader入门

57 阅读2分钟

webpack的loader其实就是一个函数,通过获取上文的代码字符串,对其进行处理来达到需求,一个loader对应一个功能符合单一职责原则

同步loader
//第一种写法
module.exports= function  (content,map,meta) { 
    console.log('test1')
    console.log('content=============>',content)
    console.log('meta=============>',meta)
    return content
} 
//第二种写法
module.exports= function  (content,map,meta) { 
    //callback在原型上
    /**
     * 参数1 err, 代表是否有错误
     * 参数2 content  文件内容
     * 参数3 sourcemap  继续传递sourcemap 保证不中断
     * 参数4 meta  给下一个loader 传递参数
     */
   this.callback(null,content,map,{name:'xiaoming'})
} 
异步loader
module.exports= function  (content,map,meta) { 
    console.log('test2')
    //调用async
   const callback = this.async()
    setTimeout(() => {
              callback(null,content,map,{name:'张三'})
    }, 1000);
} 

loader在执行完逻辑后 会默认调用callback函数,如果在异步逻辑中直接使用this.callback而不是通过async获取

首先webpack会认为你loader返回为undefined,下一个loader将收不到文件内容,其次因为默认已经调用了callback,而异步逻辑完成后调用this.callback的做法会报错 callback was already called

raw loader(二进制内容)

把module.exports.raw 改为true

module.exports= function  (content,map,meta) { 
    console.log('content=============>',content)
    return content
} 
//把raw属性改为true就可以了  一般用于图片媒体等的处理,
module.exports.raw =true
pitch loader(熔断机制)

当为一个loader添加上pitch方法,且方法有了返回值就有了熔断机制

module.exports.pitch =function(){
    console.log('pitch loader test3')
}

执行顺序

当有pitch方法但没有返回时,loader的排序为

 use:['test3-loader.js','test2-loader.js','test1-loader.js']

执行顺序为 test3的pitch->test2的pitch->test1的pitch->test1->test2->test3

当test2的pitch方法有了return

执行顺序为 test3的pitch->test2的pitch->test3

loader的api

image.png 更多api查询webpack官网loader 的api webpack.docschina.org/api/loaders…

自定义loader

清除consolo.log的loader
module.exports=function  (content,map,meta) { 
    content=content.replace(/(console.log()(.*)())/g,'')
    this.callback(null,content,map,meta)
}
添加作者注释loader
const   schema = require( './schema.json')
module.exports= function (content,map,meta) {
   const {author} =   this.getOptions(schema)
   const prefix = `
   /*
   * Author: ${ author.join(',')}
   */
 `;
   return  prefix+content
}


schema.json
{
    "type": "object",
    "properties": {
      "author": {
        "type": "array"
      }
    },
    "additionalProperties": false
  }
自定义babel-loader
const  babel = require('@babel/core')
const schema = require('./schema.json')
module.exports = function  (content,map,meta) {
    const call = this.async()
    const  options = this.getOptions(schema) 
    babel.transform(content, options, function(err, result) {
        if(err)return  call (err)
       const {code  }= result; // => { code, map, ast }
        call(null,code)
      });
} 
file-loader
const loaderUtils= require('loader-utils')
module.exports= function  (content,map , meta) { 
    // console.log('content=============>',content)
    //根据文件内容得到hash名
    let hashName = loaderUtils.interpolateName(
        this,
        '[hash].[ext][query]',
        {content}
      );
      hashName=`images/${hashName}`
      //将文件输出出去
    this.emitFile(hashName, content);
    return `module.exports = "${hashName}"`
}
module.exports.raw = true
style.loader
module.exports = function  (content) { 
}
module.exports.pitch= function  (remainingRequest) { 
    //remainingRequest包含了还未执行的loader路径和对应被处理的文件的路径
    //将remainingRequest里的绝对路径改为相对,后续处理只能用相对
    console.log('remainingRequest=============>',remainingRequest)
    const  relativePath = remainingRequest.split('!').map((absulotePath)=>{
         //返回absulotePath路径相对于this.context(当前执行文件的路径)的相对路径
       return this.utils.contextify(this.context,absulotePath)
     }).join('!')
     console.log('relativePath=============>',relativePath)
     
    //import style from "!!${relativePath}" 是inline loader的用法
     const script = `
        import style from "!!${relativePath}"
    const styleEl= document.createElement('style')
    styleEl.innerHTML= style
    document.head.appendChild(styleEl)
    `
return script
}