前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何实现一个Webpack Loader

如何实现一个Webpack Loader

作者头像
前端迷
发布2019-06-05 15:23:08
1K0
发布2019-06-05 15:23:08
举报
文章被收录于专栏:前端迷前端迷

开发 Webpack Loader 前须知

Loader 是支持链式执行的,如处理 sass 文件的 loader,可以由 sass-loader、css-loader、style-loader 组成,由 compiler 对其由右向左执行,第一个 Loader 将会拿到需处理的原内容,上一个 Loader 处理后的结果回传给下一个接着处理,最后的 Loader 将处理后的结果以 String 或 Buffer 的形式返回给 compiler。 这种链式的处理方式倒是和 gulp 有点儿类似,固然也是希望每个 loader 只做该做的事,纯粹的事,而不希望一箩筐的功能都集成到一个 Loader 中。

代码语言:javascript
复制
{
    module: {
        loaders: [{
            test: /\.scss$/,
            loader: 'style!css!sass'
 }]
 }
};

另一方面,虽然链式之间可以依赖其前一个Loader所返回的结果来执行自己的内容。但这并不支持两个 Loader 之间进行数据交流的做法,一个标准的 Loader 应该是要求着 强独立性、以及输入什么,就输出什么的可预见性。

Webpack Loader 基础

官网说了,A loader is a node module exporting a function. 既然是 node module,那么基本的写法可以是

代码语言:javascript
复制
// base loader
module.exports = function(source) {
 return source;
};

如果你所写的 Loader 需要依赖其他模块的话,那么同样以 module 的写法,将依赖放在文件的顶部声明,让人清晰看到

代码语言:javascript
复制
// Module dependencies.
var fs = require("fs");
module.exports = function(source) {
 return source;
};

上面使用返回 return 返回,是因为是同步类的 Loader 且返回的内容唯一,如果你希望将处理后的结果(不止一个)返回给下一个 Loader,那么就需要调用 Webpack 所提供的 API。 一般来说,构建系统都会提供一些特有的 API 供开发者使用。Webpack 也如此,提供了一套 Loader API,可以通过在 node module 中使用 this 来调用,如 this.callback(err,value...),这个 API 支持返回多个内容的结果给下一个 Loader 。

代码语言:javascript
复制
// return multiple result
module.exports = function(source, other) {
 // do whatever you want
 // ...
 this.callback(null, source, other);
};

以上的内容,稍总结下

从右到左,链式执行 上一个 Loader 的处理结果给下一个接着处理 node module 写法 module 依赖 return && this.callback() 而实际上,掌握上面所介绍的内容及思想,就可以开始写一个简单的 Loader 了,不是吗? 由上所说的,在你的 Loader 中,你可以拿到需要处理的文件内容,并且知道了处理后的结果应该怎么去返回,在中间部分,你可以以正常使用 node 的姿态对内容进行怎样的处理,Do Whatever You Want,Loader 没有其他特殊要求。

如何开发更好用的 Webapck Loader

上半部分的介绍虽然确实能搭建起一个普通的 Loader 了,但这样就够了吗?

如何实现一个Loader?

我们在上几节有讲过loader,今天我们来深入了解它们,最暴力的方式莫过于动手实现它们

好了,回到正题, 先来回顾一下loader

loader定义: 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件 简单使用

代码语言:javascript
复制
module.exports = {
 //...
  module: {
    rules: [
 {
        test: /\.js$/,
        use: [
 {
 // loader 是导出为一个a函数的 node 模块。该函数在 loader 转换资源的时候调用
 // 给定的函数将调用 loader API,并通过 this 上下文访问
            loader: path.resolve('loader.js'),
            options: {/* ... */}
 }
 ]
 }
 ]
 }
};

回顾了loader的定义及简单使用后,我们再来分析一下实现loader的思路

单一职责,一个loader只做一件事 链式组合,链中的每个 loader 会将转换应用在已处理过的资源上 模块化,是导出为一个函数的 node 模块 参数合并,loader 可以通过 options 对象配置 基于上面分析的几点,我们开始动手

代码语言:javascript
复制
// 这个就是一个最简单loader,
// 如果我们的loader有依赖其它模块,也得以module的写法将在在顶部引入
import fs from 'fs';
export default function(source){
 return source
}

我们发现上面直接使用了return,是因为是同步类的loader且返回的内容唯一,如果你希望你的loader支持链式调用,将结果返给下一个loader继续使用,这时候就需要用webpack提供的api

这里我们简单看一下this.callback的定义,一个可以同步或者异步调用的可以返回多个结果的函数。预期的参数是

代码语言:javascript
复制
this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
)
// loader-utils 它提供了很多有用的工具
// 最常用的一个就是获取传入 loader 的 options
import { getOptions } from 'loader-utils';
export default function(source, other) {
 const options = getOptions(this) 
 // do whatever you want
 // ...
 this.callback(null, source, other)
}

手写一个loader对没有研究过的听上去好像有点难,事实上, 掌握上面所介绍的内容及思想,就可以开始写一个简单的 Loader 了, 我们再来用简单的代码绥一下loader到底是什么?

代码语言:javascript
复制
// 首先loader它是一个node模块,这很好理解
export const lessToCss = function(source, other) {
 // source 就是你即将要转换的文件源
 // TODO
 // 将转换好的文件源流转至一个管道
 this.callback(null, source, other)
}

loader api中有几个好用的家伙这里就顺便带一下

缓存

从提高执行效率上,如何处理利用缓存是极其重要的。 Mac OS 会让内存充分使用、尽量占满来提高交互效率。回到 Webpack,Hot-Replace 以及 React Hot Loader 也充分地利用缓存来提高编译效率。 Webpack Loader 同样可以利用缓存来提高效率,并且只需在一个可缓存的 Loader 上加一句 this.cacheable(); 就是这么简单

代码语言:javascript
复制
// 让 Loader 缓存
module.exports = function(source) {
 this.cacheable();
 return source;
};

很多 Loader 都是可以缓存的,但也有例外。可以缓存的 Loader 需要具备可预见性,不变性等等。

异步

异步并不陌生,当一个 Loader 无依赖,可异步的时候我想都应该让它不再阻塞地去异步。在一个异步的模块中,回传时需要调用 Loader API 提供的回调方法 this.async(),使用起来也很简单,

代码语言:javascript
复制
// 让 Loader 缓存
module.exports = function(source) {
 var callback = this.async();
 // 做异步的事
    doSomeAsyncOperation(content, function(err, result) {
 if(err) return callback(err);
        callback(null, result);
 });
};

善用 Loader 中的 this

Loader API 将提供给每一个 Loader 的 this 中,API 可以让我们的调用方式更加地方便,更加灵活。 data pitch loader 中可以通过 data 让 pitch 和 normal module 进行数据共享。 query 则能获取到 Loader 上附有的参数。 如 require("./somg-loader?ls"); 通过 query 就可以得到 "ls" 了。 emitFile emitFile 能够让开发者更方便的输出一个 file 文件,这是 webpack 特有的方法,使用的方法也很直接

emitFile(name:string,content:Buffer|String,sourceMap:{...})

在 file-loader 中有调用到 this.emitFile(url, content); 这个方法,具体可以查看其源码了解。 更多的 API 就不在此 一 一 说明了,建议查看官网文档了解。最后推荐一个工具模块 loader-utils,大多数的 Loader 都会用上它来解析或者使用它提供的一些 util 方法,很方便。

下方贴上less-loader的源码,代码很简洁,结合上方我们所分析的,也很容易理解

代码语言:javascript
复制
import processResult from './processResult';
const render = pify(less.render.bind(less));
function lessLoader(source) {
 const loaderContext = this;
 const options = getOptions(loaderContext);
 const done = loaderContext.async();
 const isSync = typeof done !== 'function';
 if (isSync) {
 throw new Error(
 'Synchronous compilation is not supported anymore. See https://github.com/webpack-contrib/less-loader/issues/84'
 );
 }
  processResult(loaderContext, render(source, options));
}

总结

loader是一个node模块 编写loader时要遵循单一原则,每个loader只做一种"转义"工作 webpack为我们提供了丰富的loader api webpack为我们还提供了工具函数集——loader-utils

认识更多的 Loader

pitchingLoader

前面所述的 Loader 从右到左链式执行。这种说法实际说的是 Loader 中 module.exports 出来的执行方法顺序。在一些场景下,Loader 并不依赖上一个 Loader 的结果,而只关心原输入内容。这时候,从左到右执行并没有什么问题。在 Loader 的 module 中,可使用 module.exports.pitch = function(); pitch 方法在 Loader 中便是从左到右执行的,并且可以通过 data 这个变量来进行 pitch 和 normal 之间传递。

代码语言:javascript
复制
module.exports.pitch = function(remaining, preceding, data) {
 if(somothingFlag()) {
 return "module.exports = require(" + JSON.stringify("-!" + remaining) + ");";
 }
    data.value = 1;
};

具体的实践可以查看 style-loader,里面就有使用到 pitch。

raw loader

默认的情况,原文件是以 UTF-8 String 的形式传入给 Loader,而在上面有提到的,module 可使用 buffer 的形式进行处理,针对这种情况,只需要设置 module.exports.raw=true; 这样内容将会以 rawBuffer 的形式传入到 loader 中了

代码语言:javascript
复制
module.exports = function(content) {
};
module.exports.raw = true;
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端迷 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开发 Webpack Loader 前须知
  • Webpack Loader 基础
  • 如何开发更好用的 Webapck Loader
  • 如何实现一个Loader?
  • loader api中有几个好用的家伙这里就顺便带一下
    • 缓存
      • 异步
      • 总结
      • 认识更多的 Loader
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档