webpack loader 原理详解

69 阅读4分钟

cloud.tencent.com/developer/a… webpack 中通过compilation对象进行模板编译时,会首先进行匹配loader处理文件得到结果(string/buffer),之后才会输出给webpack进行编译。 loader 本质上就是一个函数,通过它我们可以在webpack处理特定资源文件之前进行提前处理。

Loader配置相关api:常用基础配置参数

/**
* test 是一个正则表达式,我们会对应的资源文件根据test规则去匹配,如果匹配到,那么该文件
* 就会交给loader去处理。
* use 表示匹配到test中匹配到的文件应该使用哪个loader的规则去处理,use可以是一个字符串,
* 也可以是数组
* use 为一个数组时表示多个loader依次处理匹配的资源,按照从右到左(从上到下)的顺序去处理
* enforce参数:有俩个值: pre、post
*   loader中存在一个enforce参数标志这loader的顺序,比如这样一份配置文件:
*   enforce: pre 我们称之为前置loader 
*   enforce: post  我们称之为后置loader
*/
module.exports = {
  module: {
    rules: [
      { test: '/.css$/', use:'sass-loader', enfore: 'pre' },
      { test: '/.css$/', use:'css-loader' },
      { test: '/.css$/', use:'style-loader', enfore: 'post' },
    ]
  }
}

Loader的执行顺序:

pre loader 、 普通loader、 后置loader webpack 中配置Loader的常用的三种方式 第一种:绝对路径的方式 在项目内部存在一些未发布的自定义loader,直接使用绝对路径的形式指向loader文件所在的地址。

const path = require('path')
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: path.resolve(__dirname, '../../xxx-loader.js')
      }
    ] 
  }
}

第二种方式可以使用webpack 中的resolve Loader的别名alias别名进行配置 或者

const path = require('path')
// webpack.config.js
module.exports = {
  ...
  resolveLoader:{
    alias: {
      'babel-loader': path.resolve(__dirname, '../../loaders/xxx-loader.js')
    }
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        laoder: 'babel-loader'
      }
    ]
  }
}

第三种:resolveLoader中的modules resolveLoader.modules 的默认值是【node_modules】

const path = require('path')
// webpack.config.js
module.exports = {
  ...
  resolveLoader: {
    modules: [
      path.resolve(__dirname, '../../loaders/')
    ]
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        laoder: 'babel-loader'
      }
    ]
  }
}

Loader的种类:

根据配置文件的enforce参数可以分为: pre loader / normale loader / post loader / inline loader 默认loader 的执行顺序 webpack 还支持内联的方式配置loader 通过!分割使用loader的方式称之为行内loader 通过内联 import语句添加前缀,可以覆盖配置中的有pre loader / normal loader /post loader

import styles from '!style-loader!css-loader?modules!./styles.css';

使用! 前缀 将会禁用所有normal loader(普通loader)

import styles from '!style-loader!css-loader?modules!./style.css';

使用!!前缀,将禁用所有已配置的loader(preload 、post loader 、loader)

import styles from '!!style-loader!css-loader?modules!./style.css';

使用-!前缀,将禁用已配置的pre loader和loader ,但是不禁用post loader

import styles from '-!style-loader!css-loader?modules!./style.css';

loader的执行阶段其实分为俩个阶段:pitch阶段/normal阶段

loader 的 pictch 阶段

  1. 在处理资源文件之前,首先会经历pitch阶段 2)pitch 结束后,读取资源内容,将读取到的内容交给正常阶段的loader去处理。 Pitch 阶段: loader的pitch方法按照:post loader -->inline loader --> normal loader -->pre loader的顺序调用 Picth阶段返回全是undefined ,一旦在某一个loader的pitch 返回一个非undefined的值就会发生熔断效果。 Normal阶段:loader的正常调用顺序是:pre loader --> normal loader --> inline loader --> post loader的顺序调用

同步/异步loader

同步loader: loader 在normal 阶段返回值可以通过函数内部的return 语句进行返回,同时如果需要返回多个值,也可以通过this.callback()表示loader结束时传入多个值返回,this.callback第一个参数一定是表示错误是否存在。 异步loader的实现方案:

  1. 返回promise
function asyncLoader() {
  return new Promise((relose) => {
    setTimeout(() => {
      resolve('111')
    }, 1000)
  })
}
  1. 通过在loader内部调用this.async()函数将loader变为异步,同时this.async会返回一个callback的方式,只有当我们调用callback方法才会继续执行后续阶段处理。
function asyncloader() {
  let callback = this.async()
  
  callback()
}

Normal loader

normal loader默认接受一个参数,就是需要处理的 文件内容,存在多个loader,它的参数会受上一个loader的影响。 同时normal loader默认会有一个返回值,这个返回值会链式调用给下一个loader作为入参,当最后loader执行完成以后,会将这个返回值返回给webpack进行编译。 normal loader的最后一个阶段一定返回一个js代码(一个module的代码) Pitch loader loader pitch 阶段接受三个参数:分别是remainingRequest、previousRequest、data remainingRequest 表示剩余需要处理的loader的绝对路径以“!”分割组成的字符串。与剩余loader有没有patch属性没有关系。 previousRequest 表示已经处理过的loader按照!组成的字符串。 data 默认是一个空对象。 loader.raw raw == false normal loader的source 是一个字符串,默认行为。 raw == true, normal loader的source接受一个Buffer类型。