# 实现常用的loader

# loader知识查漏补缺

# 什么是loader

webpack只能处理js的模块,如果要处理其他类型的文件,需要使用loader来进行转换。loaderwebpack中一个重要的概念,它是指用来将一段代码转化为另一段代码的webpack加载器

# loader的加载顺序

默认来说loader的执行顺序是从右到左,从下到上。

但是根据loader的分类loader的执行顺序也可以为pre - normal - inline - post

# inline-loader的用法

行内loader我们用的比较少,我们一般是在js文件中引入行内loader,而不是在配置文件了配置



const str = require(inline-loader!'./a.js')

表示加载a.js这个文件使用行内loader来处理

我们还需要注意行内loader的一些规则写法




 
const str = require(-!inline-loader!'./a.js') //-! 表示文件不会通过pre-normal来处理了
const str = require(!inline-loader!'./a.js') //! 表示文件不会通过normal来处理了
const str = require(!!inline-loader!'./a.js') //!! 表示文件只经过行内loader来说处理了

# pitchLoader 和 normalLoader

每个loader都有两部分组成pitchLoadernormalLoader, pitchnormal的执行顺序是相反的

pitchLoader没有返回值

An image

pitchLoader有返回值

An image

# loader的特点

  • 第一个loader要返回js脚本

  • 每个loader只做一件内容,为了让更多的loader在更多的场景链式调用

  • 每一个loader都是一个模块

  • 每个loader都是无状态的,确保loader在不同模块转换之间不保存状态

# 实现babel-loader

首先安装需要的包



npm i @babel/core @babel/preset-env loader-utils

我们来看下使用babel-loader时候的配置




 








{
    test: /\.js$/,
    use: {
        loader: 'babel-loader',
        options: {
            presets: [
                '@babel/preset-env'
            ]
        }
    }
}   

然后我们来实现一个babel-loader




 













const babel = require('@babel/core')
const utils = require('loader-utils')

function loader (source) {
    const options = utils.getOptions(this) //拿到当前loader的配置项
    const cb = this.async() //因为babel转换是异步 这里用提供的方法来处理异步
    babel.transform(source, {
        ...options, //拿到配置中配置的preset
        sourceMap: true, //开启soucreMap 方便调试
        filename: this.resourcePath.split('/').pop() //拿到当前文件名作为map文件的名字
    }, function (err, res) {
        cb(err, res.code, res.map) // 将处理之后的代码返回
    })
}

module.exports = loader

# 实现file-loader

file-loader的大致作用是根据图片生成一个md5,发射到dist目录下,dile-loader还会返回当前的图片路径




 









const utils = require('loader-utils')

function loader(source) {
    const filename = utils.interpolateName(this, '[hash].[ext]', {
        content: source
    }) //将图片拼上hash戳
    this.emitFile(filename, source) //发射文件
    return `module.exports = "${filename}"` //替换生成的字符串
}

loader.raw = true //将源码转成二进制
module.exports = loader

# 实现url-loader

url-loader可以把某一些图片转化为base64,并且会自动判断转化为base64还是使用file-loader处理




 










const utils = require('loader-utils')
const mime = require('mime')

function loader(source) {
    const {limit} = utils.getOptions(this)
    if (limit && limit > source.length) { //如果设置的大小大于文件的大小,要把图片转化为base64
        return `module.exports = "data:${mime.getType(this.resourcePath)};base64,${source.toString('base64')}"`
    } else {
        return require('./file-loader').call(this, source)
    }
}
loader.raw = true
module.exports = loader

# 实现less-loader




 







let less = require('less');
function loader(source) {
  let css;
  less.render(source,function (err,r) { // r.css
    css = r.css; 
  });
  return css
}

module.exports = loader;

# 实现css-loader




 
















function loader(source) {
  let reg = /url\((.+?)\)/g;
  let pos = 0;
  let current;
  let arr = ['let list = []'];
  while (current = reg.exec(source)) { // [matchUrl,g]
    let [matchUrl, g] = current;
    let last = reg.lastIndex - matchUrl.length;
    arr.push(`list.push(${JSON.stringify(source.slice(pos, last))})`);
    pos = reg.lastIndex;
    // 把 g 替换成 require的写法  => url(require('xxx'))
    arr.push(`list.push('url('+require(${g})+')')`);
  }
  arr.push(`list.push(${JSON.stringify(source.slice(pos))})`)
  arr.push(`module.exports = list.join('')`);
  return arr.join('\r\n');
}

module.exports = loader;

# 实现style-loader




 





















let loaderUtils = require('loader-utils');
function loader(source) {
  // 我们可以在style-loader中导出一个 脚本
  let str = `
    let style = document.createElement('style');
    style.innerHTML = ${JSON.stringify(source)};
    document.head.appendChild(style);
  `
  return str;
}
// 在style-loader上 写了pitch
// style-loader     less-loader!css-loader!./index.less
loader.pitch = function (remainingRequest) { // 剩余的请求
  // 让style-loader 去处理less-loader!css-loader/./index.less 
  // require路径 返回的就是css-loader处理好的结果 require('!!css-loader!less-loader!index.less')
  let str = `
    let style = document.createElement('style');
    style.innerHTML = require(${loaderUtils.stringifyRequest(this, '!!' + remainingRequest)});
    document.head.appendChild(style);
  `
  return str;
}
module.exports = loader;