手把手教你写一个 Webpack Loader小白教程_webpack零基础入门

本文示例源代码请戳github博客,建议大家动手敲敲代码。本文不会介绍loader的一些使用方法,不熟悉的同学请自行查看Webpack loader1、背景首先我们来看一下为什么需要loader,以及他能干什么?webpack 只能理解 JavaScript 和 JSON 文件。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模

手把手教你写一个 Webpack Loader小白教程

本文示例源代码请戳github博客,建议大家动手敲敲代码。

手把手教你写一个 Webpack Loader小白教程_webpack零基础入门

1、背景

首先我们来看一下为什么需要loader,以及他能干什么?
webpack 只能理解 JavaScript 和 JSON 文件。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。

本质上来说,loader 就是一个 node 模块,这很符合 webpack 中「万物皆模块」的思路。既然是 node 模块,那就一定会导出点什么。在 webpack 的定义中,loader 导出一个函数,loader 会在转换源模块resource的时候调用该函数。在这个函数内部,我们可以通过传入 this 上下文给 Loader API 来使用它们。最终装换成可以直接引用的模块。

2、xml-Loader 实现

前面我们已经知道,由于 Webpack 是运行在 Node.js 之上的,一个 Loader 其实就是一个 Node.js 模块,这个模块需要导出一个函数。 这个导出的函数的工作就是获得处理前的原内容,对原内容执行处理后,返回处理后的内容。
一个简单的loader源码如下

module.exports = function(source) {
  // source 为 compiler 传递给 Loader 的一个文件的原内容
  // 该函数需要返回处理后的内容,这里简单起见,直接把原内容返回了,相当于该 Loader 没有做任何转换
  return source;
};

由于 Loader 运行在 Node.js 中,你可以调用任何 Node.js 自带的 API,或者安装第三方模块进行调用:


const xml2js = require('xml2js');
const parser = new xml2js.Parser();

module.exports =  function(source) {
  this.cacheable && this.cacheable();
  const self = this;
  parser.parseString(source, function (err, result) {
    self.callback(err, !err && "module.exports = " + JSON.stringify(result));
  });
};

这里我们事简单实现一个xml-loader;

整个过程相当于这个 loader 把源文件

// 这里是 source 模块

转化为

// example.js
module.exports = '这里是 source 模块';

然后交给 require 调用方:

// applySomeModule.js
var source = require('example.js'); 
console.log(source); // 这里是 source 模块

写完后我们要怎么在本地验证呢?下面我们来写个简单的demo进行验证。

2.1、验证

首先我们创建一个根目录xml-loader,此目录下 npm init -y生成默认的package.json文件 ,在文件中配置打包命令

"scripts": {
    "dev": "webpack-dev-server"
  },

之后npm i -D webpack webpack-cli,安装完webpack,在根目录 创建配置文件webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.xml$/,
        use: ['xml-loader'],
      }
    ]
  },
  resolveLoader: {
    modules: [path.join(__dirname, '/src/loader')]
  },
  devServer: {
    contentBase: './dist',
    overlay: {
      warnings: true,
      errors: true
    },
    open: true
  }
}

在根目录创建一个src目录,里面创建index.js,

import data from './foo.xml';

function component() {
  var element = document.createElement('div');
  element.innerHTML = data.note.body;
  element.classList.add('header');
  console.log(data);
  return element;
}

document.body.appendChild(component());

同时还有一个foo.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<note>
    <to>Mary</to>
    <from>John</from>
    <heading>Reminder  dd</heading>
    <body>Call Cindy on Tuesday dd</body>
</note>

最后把上面的xml-loader放到src/loader文件夹下。完整的demo源码请看
最终我们的运行效果如下图
手把手教你写一个 Webpack Loader小白教程_webpack零基础入门

至此一个简单的webpack loader就实现完成了。当然最终使用你可以发布到npm上。

3、一些议论知识补充

3.1、获得 Loader 的 options

当我们配置loader时我们经常会看到有这样的配置

ules: [{
    test: /\.html$/,
    use: [ {
      loader: 'html-loader',
      options: {
        minimize: true
      }
    }],
  }]

那么我们在loader中怎么获取这写配置信息呢?答案是loader-utils。这个由webpack提供的工具。下面我们来看下使用方法

const loaderUtils = require('loader-utils');
module.exports = function(source) {
  // 获取到用户给当前 Loader 传入的 options
  const options = loaderUtils.getOptions(this);
  return source;
};

没错就是这么简单。

3.2、加载本地 Loader

1、path.resolve
可以简单通过在 rule 对象设置 path.resolve 指向这个本地文件

{
  test: /\.js$/
  use: [
    {
      loader: path.resolve('path/to/loader.js'),
      options: {/* ... */}
    }
  ]
}

2、ResolveLoader
这个就是上面我用到的方法。ResolveLoader 用于配置 Webpack 如何寻找 Loader。 默认情况下只会去 node_modules 目录下寻找,为了让 Webpack 加载放在本地项目中的 Loader 需要修改 resolveLoader.modules。
假如本地的 Loader 在项目目录中的 ./loaders/loader-name 中,则需要如下配置:

module.exports = {
  resolveLoader:{
    // 去哪些目录下寻找 Loader,有先后顺序之分
    modules: ['node_modules','./loaders/'],
  }
}

加上以上配置后, Webpack 会先去 node_modules 项目下寻找 Loader,如果找不到,会再去 ./loaders/ 目录下寻找。
3、npm link
npm link 专门用于开发和调试本地 npm 模块,能做到在不发布模块的情况下,把本地的一个正在开发的模块的源码链接到项目的 node_modules 目录下,让项目可以直接使用本地的 npm 模块。 由于是通过软链接的方式实现的,编辑了本地的 Npm 模块代码,在项目中也能使用到编辑后的代码。

完成 npm link 的步骤如下:

  • 确保正在开发的本地 npm 模块(也就是正在开发的 Loader)的 package.json 已经正确配置好;
  • 在本地 npm 模块根目录下执行 npm link,把本地模块注册到全局;
  • 在项目根目录下执行 npm link loader-name,把第2步注册到全局的本地 Npm 模块链接到项目的 node_moduels下,其中的 loader-name 是指在第1步中的package.json 文件中配置的模块名称。

链接好 Loader 到项目后你就可以像使用一个真正的 Npm 模块一样使用本地的 Loader 了。(npm link不是很熟,复制被人的)

3.3、缓存加速

在有些情况下,有些转换操作需要大量计算非常耗时,如果每次构建都重新执行重复的转换操作,构建将会变得非常缓慢。 为此,Webpack 会默认缓存所有 Loader 的处理结果,也就是说在需要被处理的文件或者其依赖的文件没有发生变化时, 是不会重新调用对应的 Loader 去执行转换操作的。

如果你想让 Webpack 不缓存该 Loader 的处理结果,可以这样:

module.exports = function(source) {
  // 关闭该 Loader 的缓存功能
  this.cacheable(false);
  return source;
};

3.4、处理二进制数据

在默认的情况下,Webpack 传给 Loader 的原内容都是 UTF-8 格式编码的字符串。 但有些场景下 Loader 不是处理文本文件,而是处理二进制文件,例如 file-loader,就需要 Webpack 给 Loader 传入二进制格式的数据。 为此,你需要这样编写 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;

以上代码中最关键的代码是最后一行 module.exports.raw = true;,没有该行 Loader 只能拿到字符串。

3.5、同步与异步

Loader 有同步和异步之分,上面介绍的 Loader 都是同步的 Loader,因为它们的转换流程都是同步的,转换完成后再返回结果。 但在有些场景下转换的步骤只能是异步完成的,例如你需要通过网络请求才能得出结果,如果采用同步的方式网络请求就会阻塞整个构建,导致构建非常缓慢。

在转换步骤是异步时,你可以这样:

module.exports = function(source) {
    // 告诉 Webpack 本次转换是异步的,Loader 会在 callback 中回调结果
    var callback = this.async();
    someAsyncOperation(source, function(err, result, sourceMaps, ast) {
        // 通过 callback 返回异步执行后的结果
        callback(err, result, sourceMaps, ast);
    });
};

参考

编写一个webpack loader

海计划公众号
(0)
上一篇 2020/03/30 16:07
下一篇 2020/03/30 16:07

您可能感兴趣的内容

  • <input type=file>文件上传零基础入门_input指南教程

    type 类型为 file 时使得用户可以选择一个或多个元素以提交表单的方式上传到服务器上,或者通过 Javascript 的 File API 对文件进行操作 . 常用input属性:accept:指示file类型,没有时表示不限制类型,填入格式后选择文件时只能看见被允许的文件accept=”image/png” 或 accept=”.pn

    2020/03/23
  • Js算法之自平衡树入门百科_树基础指南

    准备知识节点的高度和平衡因子节点高度:从节点到任意子节点的彼岸的最大值。这个相对来说容易理解。那么获得节点高度的代码实现如下:getNodeHeight(node) {if (node == null) {return -1;}return Math.max(this.getNodeHeight(node.left), this.getNodeHeight(

    2020/03/26
  • FirefoxSend基础入门_简单、私密的文件分享服务

    FirefoxSend基础入门 官方网址:https://send.firefox.com/ 简介描述:简单、私密的文件分享服务 Firefox Send 是 Mozilla 推出…

    2020/03/06
  • javascript难点是什么?菜鸟教程下载_js知识小白攻略

    javascript难点是什么?下面本篇文章就来给大家介绍一下10个JavaScript难点,感兴趣的小伙伴们可以参考一下,希望对大家有所帮助。1、立即执行函数立即执行函数,即Immediately Invoked Function Expression (IIFE),正如它的名字,就是创建函数的同时立即执行。它没有绑定任何事件,也无需等待任何异步操作:(f

    2020/03/24
  • js是面向对象还是基于对象?小白帮助_对象菜鸟指南

    以前感觉这两个在本质上没有什么区别,面向对象和基于对象都是对一个抽象的对象拥有一系列的行为和状态,本质都是对象层。拜读了winter老师的音频和文档,颇有收获。对象:一个可以触摸或者可以看见的东西;人的智力可以理解的东西;可以指导思考或行动(进行想象或施加动作)的东西。对象的特点:对象具有唯一标识性:即使完全相同的两个对象,也并非同一个对象。对象有状态:对象

    2020/03/30
  • RxJS入门基础知识RxJS 是使用 Observables 的响应式编程的库

    RxJS基础入门 官方网址:http://cn.rx.js.org/ GitHub:https://github.com/ReactiveX/RxJS 简介描述:RxJS 是使用 …

    2020/03/05
  • JavaScript开发常用技巧总汇菜鸟知识_技巧使用说明

    时间对比const time1 = “2019-03-31 21:00:00”;
    const time2 = “2019-05-01 09:00:00”;
    const overtime = time1 > time2;
    // overtime => false
    格式化金钱const ThousandNum = num => num.toString().r

    2020/03/29
  • Principle入门指南_一款交互设计工具

    Principle入门指南 官方网址:http://principleformac.com/ 简介描述:一款交互设计工具 Principle是前 Apple 工程师打造的一款交互设…

    2020/03/06
  • css之word-wrap和word-break的区别入门知识_区别基础知识教程

    对于英文单词,如果有一个连写且长度很长的英文单词,在第一行显示不下的情况下,浏览器默认不会截断显示,而是把这个单词整体挪到下一行。 但是当整体挪到下一行还是显示不完全该肿么办呢?有如下两个方法:word-wrap:break-word // 在浏览器默认处理的结果下进行截断
    word-break:break-all // 不采用浏览器默认处理方式,直接在当前

    2020/03/30
  • 前端性能优化总结小白攻略_优化小白教程

    1.原则多使用内存,缓存或者其他方法减少CPU计算,减少网络请求减少IO操作(硬盘读写)2.加载资源优化静态资源的合并和压缩。静态资源缓存(浏览器缓存策略)。使用CDN让静态资源加载更快。3. 渲染优化CSS放head中,JS放body后图片懒加载减少DOM操作,对DOM操作做缓存减少DOM操作,多个操作尽量合并在一起执行事件节流尽早执行操作 DOMCont

    2020/03/31
  • 自学编程的六个技巧总结攻略教程_编程使用教程

    有一天,我的一个在学编程的朋友问我:“我想快速学习编程,你有什么好的推荐吗?我曾在上大学的时候自学过编程,这么多年过去了,我意识到我或许是在用最困难的方式去学习和了解编程。本来我完全可以用更快的速度学习。因此,在回顾了过去之后,我写下了这些年来我渐渐掌握到的关于如何学习编程的一些事情。对于“如何快速学习编程”这个问题,其实我真的不知道何谓“快速”。我觉得,通

    2020/04/05
  • eadremaining.js小白攻略_记录用户滚动页面的时间差jQuery插件

    eadremaining.js小白攻略 官方网址:http://aerolab.github.io/readremaining.js/ GitHub:https://github….

    2020/03/06
  • UltraEdit基础指南_一套功能强大的文本编辑器

    UltraEdit基础指南 官方网址:http://www.ultraedit.com/ 简介描述:一套功能强大的文本编辑器 UltraEdit 是一套功能强大的文本编辑器,可以编…

    2020/03/06
  • 拦截器和过滤器的区别菜鸟指南_java使用帮助

    过滤器和拦截器的区别:①拦截器是基于java的反射机制的,而过滤器是基于函数回调。②拦截器不依赖与servlet容器,过滤器依赖与servlet容器。③拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。④拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。⑤在action的生命周期中,拦截器可以多次被调用,而过滤器只能在

    2020/03/23
  • Js解构赋值使用帮助_赋值菜鸟教程网

    JavaScript是一种很有趣的语言,我个人很喜欢它,虽然仍还有些人不大喜欢它。在ECMAScript6(ES6)中,有许多有用的特性来使JavaScript开发更有趣。在本文中,我将来探讨一些关于解构赋值的内容,并提供一些可能有用的实际例子。MDN是这样描述解构赋值的:解构赋值语法是一种 JavaScript表达式 用来将 数组中的值或对象中的 属性 取

    2020/03/29
  • css的repaint和reflow入门基础教程_重绘基础知识入门

    简单理解:repaint主要是针对某一个DOM元素进行的重绘,reflow则是回流,针对整个页面的重排。字面意思来说:repaint就是重绘,reflow就是回流。repaint和reflow的目的是:展示一个新的页面样貌。性能消耗:在性能优先的前提下,性能消耗 reflow大于repaint。体现:repaint是某个DOM元素进行重绘;reflow是整个

    2020/03/22