编写一个webpack的loader(1)

开始之前,先介绍一下loader,下面是webpack中文网loader的描述

所谓 loader 只是一个导出为函数的 JavaScript 模块。loader runner 会调用这个函数,然后把上一个 loader 产生的结果或者资源文件(resource file)传入进去。函数的 this 上下文将由 webpack 填充,并且 loader runner 具有一些有用方法,可以使 loader 改变为异步调用方式,或者获取 query 参数。

loader的参数以及返回值(同样摘抄自webpack中文网)

第一个 loader 的传入参数只有一个:资源文件(resource file)的内容。compiler 需要得到最后一个 loader 产生的处理结果。这个处理结果应该是 String 或者 Buffer(被转换为一个 string) ,代表了模块的 JavaScript 源码。另外还可以传递一个可选的 SourceMap 结果(格式为 JSON 对象)。

loader在webpack中执行的时机

本图片源于网络

loader执行时机.png

loader的分类

loader分为同步loader异步loaderloader本质是一个函数。所以这两种loader的区别,可以理解为只是返回值不同(只是为了方便理解的类比)。实际操作并不是简单的两种返回值。

同步loader

同步loader比较简单,无论是 return 还是 调用this.callback 都可以同步地返回转换后的 content 内容。

  1. 使用return
    这种比较直观,就一个普通函数,执行完成返回一个值。例如:
function loader(source) {
  // do anything
  return source;
}
  1. 使用this.callback
    this.callback的函数参数类型是
// 后两个参数是非必传的
this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
);

这种比return方式更灵活,可以返回多个值。例如:

function loader(source) {
  // do anything
   this.callback(null, source, map, meta);
}

注意: 函数中调用callback后,return的返回值将被忽略。

异步loader

异步loader只有一种处理方式。对于异步 loader,使用 this.async 来获取 callback 函数。这里的callback函数,和同步loader里面的参数是一样的。

function loader (source) {
    console.log('--- loader begin ----');
    console.log(source);
    console.log('--- loader end ----\n');
    const callback = this.async();
    // do anything
    callback(null, source);
}

module.exports = loader;

注意几种特殊情况:

  1. 函数中调用this.async后,return的返回值将被忽略。
  2. 函数中调用this.async后,再调用this.callback,则会将当前loader当做同步loader来处理。
  3. 函数中调用this.async后,再调用this.callback且立即调用this.async返回的callback。程序会报错。

至此,我们就能开始着手写一个简单的loader了。下面便开始,着手写几个loader

banner-loader

这个loader的功能是给每一个文件头部,添加一行段注释,标明作者。例如:

/**
** @author laibao101
** @time 2019-06-04 19:57:20
**/
// source code

功能分析:这个loader,并没有任何的异步操作,所以我们使用同步loader即可。注释方面,暂且先写死,通过参数配置的,后续再做。

function loader (source) {
    const time = new Date().toLocaleString()
    return `
        /**
        ** @author laibao101
        ** @time ${time}
        **/
        ${source}
    `
}

使用模板字符串,将注释代码写进去,返回即可。这样一个简单的loader就完成了。

异步的banner-loader

这个loader是为了写一个异步loader而写的。功能是从一个文件中读取模板,给文件添加banner

// banner-loader
const path = require('path');
const fs = require('fs');
const baseUrl = process.cwd();
const configTextPath = "bannerConfig.txt";
// 拼接完整的配置文件路径
const fullConfigPath = path.resolve(baseUrl, configTextPath);
// 根据模板中的占位符,来获取数据
const configItemMap = {
    author: "laibao101",
    time: new Date().toLocaleString()
};
// 匹配占位符
const reg = /{(\w+)}/gi;

function loader (source) {
    const callback = this.async();
    fs.readFile(fullConfigPath, (err, data) => {
        if (err) {
            // 如果读取文件失败,返回错误
            callback(err);
        }
        // 获取文件内容
        const template = data.toString();
        // 根据模板,修改占位符数据,完成banner
        const banner = template.replace(reg, (match, key) => {
            return configItemMap[key] || key;
        });
        // 拼接返回值
        const ret = `
            ${banner}
            ${source}
        `
        callback(null, ret);
    });
}

module.exports = loader;


// bannerConfig.txt

/**
** @author {author}
** @time {time}
**/

可配置参数的同步banner-loader

前面写到的banner-loader,参数都是loader内部定的。如果需要从webpack.config.js中获取参数,则可以使用webpack提供的this.query参数来获取loader配置中的options参数。这在loader中使用的很频繁。

例如:
babel-loader

module: {
  rules: [
    {
      test: /\.m?js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env']
        }
      }
    }
  ]
}

url-loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192
            }
          }
        ]
      }
    ]
  }
}

所以,我们将上面的banner-loader,修改一下,将参数改为从loader配置中获取

// banner-loader
function loader (source) {
    const time = new Date().toLocaleTimeString();
    const { author = '' } = this.query || {}
    return `
        /**
        ** @author ${author}
        ** @time ${time}
        **/
        ${source}
    `
}

module.exports = loader;


// webpack.config.js
{
  ...
  options: {
    author: "laibao101"
  }
}

这样,我们就可以实现参数从配置文件中获取的loader了。

完整代码

完整代码

下一篇

介绍 raw-loaderpitch-loader。并各自编写一个demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 使用Webpack往往离不开loader的安装配置,手写一个loader其实非常简单,类似手写一个功能函数,下面我...
    Adoins阅读 810评论 0 2
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 27,406评论 1 45
  • 编写 Loader Loader就像是一个翻译员,能把源文件经过转化后输出新的结果,并且一个文件还可以链式的经过多...
    oWSQo阅读 7,572评论 0 8
  • 本文为阮一峰大神的《ECMAScript 6 入门》的个人版提纯! babel babel负责将JS高级语法转义,...
    Devildi已被占用阅读 1,969评论 0 4
  • 文:小 boy(沪江网校Web前端工程师) 本文原创,转载请注明作者及出处 经常逛 webpack 官网的同学应该...
    iKcamp阅读 7,953评论 4 22