本文主要目的是让阅读者能简单的实现一个loader插件,并且了解loader基本运行机制。

1. 了解什么是loader

官方解释:loader 用于对模块的源代码进行转换。loader 可以使你在 import 或”加载”模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!

而我对此定义就是,loader用于处理webpack加载的每个文件

2. 如何加载一个loader

在为webpack做配置的webpack.config.js文件中,loader存放于导出的module.rules中。

module.exports = {
  module:{
    rules:[
      {...loader配置项},
      {...loader配置项},
      {...loader配置项}
    ]
  }
}

webpack支持配置内联CLI方式。个人推荐使用配置方式,来为webpack配置一个loader(具体三者区别,请查看文末使用loader)。

现在,来为webpack配置一个CSS解析器用于代码运行时候将打包后CSS代码通过<style>添加到页面头部。

module: {
    rules: [
      {
        test: /.css$/,
        use: [
          { loader: 'style-loader' }
        ]
      }
    ]
  }

这个配置中,test 表示如果通过了这个正则表达式文件,则使用use数组中的loader对文件进行处理。在这里有个规则需要说明一下,loader加载解析器的先后顺序是从右往左从下往上

执行webpack则默认开始执行编译,如果项目中包含了CSS文件,会调用style-loader对他进行处理。

另外注意的是loader是运行在node环境下的,也就是说在我们编写的loader是可以调用node上下文与node包来进行各种处理的,而loader插件的区别在于,loader用于解析模块,而插件则是介入了整个webpack生命周期。

3. 编写第一个loader

最近在处理个业务需求,就是将less文件中的图片放置到阿里的CDN上,优化打包速度,减小包体积,并且利用浏览器缓存机制缓存图片。

创建一个loader.js文件,用node导出方式,编写一个空的loader

module.exports = function () {
}
// 其实也支持 方式,看喜欢用那种方式吧。
export default function () {
}

现在遇到了第一个问题,就是,如何将这个loader文件加入到webpack编译过程中。

官方文件告诉了我们这个结果:

loader 遵循标准的模块解析。多数情况下,loader 将从模块路径(通常将模块路径认为是 npm install, node_modules)解析。

修改配置文件为下面格式:

module: {
    rules: [
      {
        test: /.css$/,
        use: [
          { loader: 'style-loader' },
          { loader: path.resolve(__dirname,'./loader.js') }
        ]
      }
    ]
  }

注意这一行{ loader: path.resolve(__dirname,'./loader.js') },我们将整个loader的路径传入给webpack。webpack会使用此路径载入这个loader。就如同const loader = require(path.resolve(__dirname,'./loader.js')) 一样。

OK,现在我们知道了webpack运行loader的小机制之后,开始着手编写这个loader

const postcss = require('postcss');
const fs = require('fs');
const file = [];
const errlist = [];
module.exports = function (source) {
      postcss([])
    .process(source)
    .then(value => {
      ...
    });
    return source;
}

由于我们要识别整个CSS中包含的规则,所以使用Postcss来处理css取得css文件的ASTsource为文件内容,传入内容可能是正常的文本内容,也有可能是二进制流(默认均为utf-8编码传入,需要buffer请参考API文档设置 raw)

我们根据需求,从AST中得到自己想要的信息,再把source原模原样传出去,交给下一个loader

现在,我们来测试一下这个解析器是否能正常工作。

运行webpack之后,你会看到类似promise reject之类的异常信息,loader并没有正常执行。这是因为Postcss是异步执行逻辑,而我们的return source则是同步模式所使用的规则。

现在修改loader为异步模式。

const postcss = require('postcss');
const fs = require('fs');
// 保存图片资源地址
const file = []
module.exports = function (source) {
  const callback = this.async();
  	// 加载一个空的 postcss 配置文件
      postcss([])
    // 将source(css text)解析成css AST
    .process(source)
    .then(value => {
      // 遍历所有css name,如: .classname #idname 等等
      value.root.walkRules(rule => {
        // 遍历当前css name下包含的所有的规则
        rule.walkDecls(decl => {
          // {prop,value}
          // prop:规则名
          // value: 规则值
          // 如 backaground:red; 则内容格式如下
          // {prop:'background',value:'red'}
          if (decl.prop === 'background-image' || decl.prop === 'background') {
            // 取得图片地址,并且清洗干净
            // 接着push如存放图片地址的数组
            let url = /url(.*?)/.exec(decl.value);
            if (url && url.length > 0) {
              // eslint-disable-next-line prefer-destructuring
              url = url[0];
              url = url.replace('url(', '').replace(')', '');
              url = url.replace(/"/gi, '').replace(/'/gi, '');
              if (!file.includes(url)) {
                file.push(url);
              }
            } else if (decl.value.indexOf('url') > -1) {
              // 容错机制
              file.push(decl.value);
            }
          }
        });
      });
      // 保存输出内容
      fs.writeFileSync(
        './cssImageFile.txt',
        file.join('n')
      );
      return callback(source);
    });
}

这样,我们就完成了一个异步处理的loader

this上,webpack挂接了很多相关的信息,如sourcepathasync。编写loader时,请及时查阅loader API文档。

参考

官方文档:使用loader

www.webpackjs.com/concepts/lo…

官方文档:编写一个loader

www.webpackjs.com/contribute/…

官方文档:loader API

webpack.docschina.org/api/loaders…

By: yodfz