参考网址

为什么需要Loader?

Webpack 它只能处理 js 和 JSON 文件。面对 css 文件还有一些图片等等,Webpack 它自己是不能够处理的,它需要loader 处理其他类型的文件并将它们转换为有效的模块以供应用程序使用并添加到依赖关系图中。

常见的loader

我们先来回顾下常见的 Loader 基础的配置和使用,首先先介绍 处理 CSS 相关的 Loader。

css-loader 和 style-loader

安装依赖

  1. npm install css-loader style-loader

使用加载器

  1. module.exports = {
  2. // ...
  3. module: {
  4. rules: [{
  5. test: /\.css$/,
  6. use: ['style-loader', 'css-loader'],
  7. }],
  8. },
  9. };

其中module.rules代表模块的处理规则。 每个规则可以包含很多配置项。
test 可以接收正则表达式或元素为正则表达式的数组。 只有与正则表达式匹配的模块才会使用此规则。 在此示例中,/.css$/ 匹配所有以 .css 结尾的文件。
use 可以接收一个包含规则使用的加载器的数组。 如果只配置了一个css-loader,当只有一个loader时也可以为字符串。
css-loader 的作用只是处理 CSS 的各种加载语法(@import 和 url() 函数等),如果样式要工作,则需要 style-loader 将样式插入页面。
style-loader加到了css-loader前面,这是因为在Webpack打包时是按照数组从后往前的顺序将资源交给loader处理的,因此要把最后生效的放在前面。
还可以这样写成对象的形式,里面options传入配置

  1. module.exports = {
  2. // ...
  3. module: {
  4. rules: [{
  5. test: /\.css$/,
  6. use: [
  7. 'style-loader',
  8. {
  9. loader: 'css-loader',
  10. options: {
  11. // css-loader 配置项
  12. },
  13. }
  14. ],
  15. }],
  16. },
  17. };

exclude与include。 include代表该规则只对正则匹配到的模块生效。 exclude的含义是,所有被正则匹配到的模块都排除在该规则之外。

  1. rules: [
  2. {
  3. test: /\.css$/,
  4. use: ['style-loader', 'css-loader'],
  5. exclude: /node_modules/,
  6. include: /src/,
  7. }
  8. ],

当然还有相关的 sass/less等等预处理器loader这里就不一一介绍了。

babel-loader

babel-loader 这个loader十分的重要,把高级语法转为ES5,常用于处理 ES6+ 并将其编译为 ES5。 它允许我们在项目中使用最新的语言特性(甚至在提案中),而无需特别注意这些特性在不同平台上的兼容性。
介绍下主要的三个模块

  • babel-loader:使 Babel 与 Webpack 一起工作的模块。
  • @babel/core:Babel核心模块。
  • @babel/preset-env:是Babel官方推荐的preseter,可以根据用户设置的目标环境,自动添加编译ES6+代码所需的插件和补丁。

    安装依赖

    1. npm install babel-loader @babel/core @babel/preset-env

    配置

    1. rules: [
    2. {
    3. test: /\.js$/,
    4. exclude: /node_modules/, //排除掉,不排除拖慢打包的速度
    5. use: {
    6. loader: 'babel-loader',
    7. options: {
    8. cacheDirectory: true, // 启用缓存机制以防止在重新打包未更改的模块时进行二次编译
    9. presets: [[
    10. 'env', {
    11. modules: false, // 将ES6 Module的语法交给Webpack本身处理
    12. }
    13. ]],
    14. },
    15. },
    16. }
    17. ],

    file-loader

    用于打包文件类型的资源,比如对png、jpg、gif等图片资源使用file-loader,然后就可以在JS中加载图片了。

    安装依赖

    1. npm install url-loader

    配置

    1. const path = require('path');
    2. module.exports = {
    3. entry: './index.js',
    4. output: {
    5. path: path.join(__dirname, 'dist'),
    6. filename: 'bundle.js',
    7. },
    8. module: {
    9. rules: [
    10. {
    11. test: /\.(png|jpg|gif)$/,
    12. use: 'file-loader',
    13. }
    14. ],
    15. },
    16. };
    既然介绍了 file-loader 就不得不介绍 url-loader,它们很相似,但是唯一的区别是用户可以设置文件大小阈值。 大于阈值时返回与file-loader相同的publicPath,小于阈值时返回文件base64编码。

    安装依赖

    1. npm install url-loader

    配置

    1. rules: [
    2. {
    3. test: /\.(png|jpg|gif)$/,
    4. use: {
    5. loader: 'url-loader',
    6. options: {
    7. limit: 1024,
    8. name: '[name].[ext]',
    9. publicPath: './assets/',
    10. },
    11. },
    12. }
    13. ],

    ts-loader

    TypeScript使用得越来越多,对于我们平时写代码有了更好的规范,项目更加利于维护…等等好处,我们也在Webpack中来配置loader,本质上类似于 babel-loader,是一个连接 Webpack 和 Typescript 的模块。

    安装依赖

    1. npm install ts-loader typescript
    loader配置,主要的配置还是在 tsconfig.json 中。
    1. rules: [
    2. {
    3. test: /\.ts$/,
    4. use: 'ts-loader',
    5. }
    6. ],

    vue-loader

    用来处理vue组件,还要安装vue-template-compiler来编译Vue模板。

    安装依赖

    1. npm install vue-loader vue-template-compiler

    配置

    1. rules: [
    2. {
    3. test: /\.vue$/,
    4. use: 'vue-loader',
    5. }
    6. ],

    写一个简单的Loader

    写个清除console语句的loader

    新建清除console语句的loader在MyLoader/drop-console.js
    1. // source:表示当前要处理的内容
    2. const reg = /(console.log\()(.*)(\))/g;
    3. module.exports = function(source) {
    4. // 通过正则表达式将当前处理内容中的console替换为空字符串
    5. source = source.replace(reg, "")
    6. // 再把处理好的内容return出去,坚守输入输出都是字符串的原则,并可达到链式调用的目的供下一个loader处理
    7. return source
    8. }
    返回其它结果 this.callback
    1. const reg = /(console.log\()(.*)(\))/g;
    2. this.callback(
    3. // 当无法转换原内容时,给 Webpack 返回一个 Error
    4. err: Error | null,
    5. // 原内容转换后的内容
    6. content: string | Buffer,
    7. // 用于把转换后的内容得出原内容的 Source Map,方便调试
    8. sourceMap?: SourceMap,
    9. // 如果本次转换为原内容生成了 AST 语法树,可以把这个 AST 返回,以方便之后需要 AST 的 Loader 复用该 AST,以避免重复生成 AST,提升性能
    10. abstractSyntaxTree?: AST
    11. );
    打开代码对应的source-map,方便调试源代码。source-map 可以方便实际开发者在浏览器控制台查看源代码。 如果不处理source-map,最终将无法生成正确的map文件,在浏览器的开发工具中可能会看到混乱的源代码。
    为了在使用 this.callback 返回内容时将 source-map 返回给 Webpack
    loader 必须返回 undefined 让 Webpack 知道 loader 返回的结果在 this.callback 中,而不是在 return
    1. module.exports = function(source) {
    2. source = source.replace(reg, "")
    3. // 通过 this.callback 告诉 Webpack 返回的结果
    4. this.callback(null, source, sourceMaps);
    5. return;
    6. };

    常用加载本地 loader 两种方式

    path.resolve
    使用 path.resolve 指向这个本地文件 ```javascript const path = require(‘path’)

module.exports = { module: { rules: [ { test: /.js$/, use: path.resolve(‘./src/myLoader/drop-console.js’), }, ], }, }

  1. <a name="TA5UW"></a>
  2. ##### ResolveLoader
  3. 先去 node_modules 项目下寻找 my-loader,如果找不到,会再去 ./src/myLoader/ 目录下寻找。
  4. ```javascript
  5. module.exports = {
  6. //...
  7. module: {
  8. rules: [
  9. {
  10. test: /\.js$/,
  11. use: ['drop-console'],
  12. },
  13. ],
  14. },
  15. resolveLoader: {
  16. modules: ['node_modules', './src/myLoader'],
  17. },
  18. }

一个 loader的职责是单一的,使每个loader易维护。
如果源文件需要分多步转换才能正常使用,通过多个Loader进行转换。当调用多个loader进行文件转换时,每个loader都会链式执行。
第一个loader会得到要处理的原始内容,将前一个loader处理的结果传递给下一个。 处理完毕,最终的Loader会将处理后的最终结果返回给 Webpack。
所以,当你写loader记得保持它的职责单一,你只关心输入和输出。

option参数

module: {
  rules: [
    {
      test: /\.js$/,
      use: [
        {
          loader: 'drop-console',
          options: {
            flag: true,
          },
        },
      ],
    },
  ],
},

那么我们如何在loader中获取这个写入配置信息呢?
Webpack 提供了loader-utils工具。

在之前写的loader修改

const reg = /(console.log\()(.*)(\))/g;
const loaderUtils = require('loader-utils')
module.exports = function (source) {
  // 获取到用户给当前 Loader 传入的 options
  const options = loaderUtils.getOptions(this)
  console.log('options-->', options)
  // 通过正则表达式将当前处理内容中的console替换为空字符串
  source = source.replace(reg, "")
  // 再把处理好的内容return出去,坚守输入输出都是字符串的原则,并可达到链式调用的目的供下一个loader处理
  return source
}

缓存

如果为每个构建重新执行重复的转换操作,这样Webpack构建可能会变得非常慢。
Webpack 默认会缓存所有loader的处理结果,也就是说,当待处理的文件或者依赖的文件没有变化时,不会再次调用对应的loader进行转换操作

const reg = /(console.log\()(.*)(\))/g;
module.exports = function (source) {
  // 开始缓存
  this.cacheable && this.cacheable();
  // 在这里按照你的需求处理 source
  return source = source.replace(reg, "")
}

一般默认开启缓存,如果不想Webpack这个loader进行缓存,也可以关闭缓存

const reg = /(console.log\()(.*)(\))/g;
module.exports = function (source) {
  // 开始缓存
  this.cacheable && this.cacheable(false);
  // 在这里按照你的需求处理 source
  return source = source.replace(reg, "")
}

同步与异步

在某些情况下,转换步骤只能异步完成。
例如,您需要发出网络请求以获取结果。 如果使用同步方式,网络请求会阻塞整个构建,导致构建非常缓慢。

const reg = /(console.log\()(.*)(\))/g;
module.exports = function(source) {    
  // 告诉 Webpack 本次转换是异步的,Loader 会在 callback 中回调结果
  var callback = this.async()
  // someAsyncOperation 代表一些异步的方法
  someAsyncOperation(source, function (err, result, sourceMaps, ast) {
    // 通过 callback 返回异步执行后的结果
    callback(err, result, sourceMaps, ast)
  })
};

处理二进制数据

默认情况下,Webpack 传递给 Loader 的原始内容是一个 UTF-8 格式编码的字符串。 但是在某些场景下,加载器处理的不是文本文件,而是二进制文件
官网例子 通过 exports.raw 属性告诉 Webpack 该 Loader 是否需要二进制数据

module.exports = function(source) {    
  // 在 exports.raw === true 时,Webpack 传给 Loader 的 source 是 Buffer 类型的    
  source instanceof Buffer === true;    
  // Loader 返回的类型也可以是 Buffer 类型的    
  // 在 exports.raw !== true 时,Loader 也可以返回 Buffer 类型的结果    
  return source;
};
// 通过 exports.raw 属性告诉 Webpack 该 Loader 是否需要二进制数据 
module.exports.raw = true;