手把手用代码教你实现一个 webpack loader

什么是 Loader

webpack 打包时只能处理 js 文件,对于其余类型的文件如 jsx, css, scss, vue, png 等文件,须要专门的东西处理一下再传入 webpack,这个东西就是 loadercss

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

Loader 的开发

在开发本身 loader 以前,咱们得知道 loader 是啥html

loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 loader API,并经过 this 上下文访问。

说白了,loader 就是一个函数,接收源模块,而后处理一番,再导出去,给下一个 loader 或者 webpack前端

module.exports = function(source) {
    // handle source
    ...
    return handled source
}

关于开发一个 loader 遵循的一些原则,你们能够去看文档,本文以一个处理 txt 文件的小例子来讲明如何开发一个 loader。目录结构以下vue

image.png

// webpack.config.js
const path = require('path')
module.exports = {
  mode: 'development',
  entry:  __dirname + "/src/app.js",
  output: {
    path: __dirname + "/dist",
    filename: "bundle.js"
  },
  module: {
    rules: [
      {
        test: /\.txt$/,
        use: [
          'text-loader'
        ]
      }
    ]
  }
}

loaders 文件中存放咱们的 txt-loader.jsnode

// txt-loader.js
module.exports = function(source) {
    console.log(source)
}

源文件 name.txtwebpack

// name.txt
hello [name]!

入口文件 app.jsweb

// app.js
const name = require('./name.txt')
console.log(name)

先执行走一波,终端执行数组

./node_modules/.bin/webpack

确定会报错,由于咱们的 loader 尚未写完,可是源文件内容已经打印出来了浏览器

image.png

报错信息也说,这个 loader没有返回 Buffer 或者 Stringsass

txt-loader 要作的事情就是将任何 .txt 文件中的 [name] 直接替换为咱们想要的名字,而后返回包含默认导出文本的 JavaScript 模块。

须要注意的是,咱们不能再 loader 里面将这个名字写死,而应该在使用 loader 的时候以配置的形式传进去,咱们平时看到的 loader 通常都有个 options 选项,就是为了传些配置进去

{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    loader: 'url-loader',
    options: {
       limit: 10000,
       name: utils.assetsPath('img/[name].[hash:7].[ext]')
    }
},

咱们给 txt-loader 加个配置选项

// webpack.config.js
...
{
    test: /\.txt$/,
    use: {
        loader: path.resolve(__dirname, './src/loaders/txt-loader.js'),
        options: {
            name: 'Jay'
        }
    }
}
...

那咱们在 txt-loader.js 怎么接收配置呢?webpack 提供了一个 loader 工具库

// txt-loader.js
const loaderUtils = require('loader-utils')

module.exports = function(source) {
    this.cacheable && this.cacheable()
    const options = loaderUtils.getOptions(this) || {}
    console.log(options)
    
    source = source.replace(/\[name\]/g, options.name)
    console.log(source)
    
    return source
}

执行一下

image.png

发现咱们期待的结果打印出来了,可是仍是报错了,报错信息说还须要额外的 loader 去处理当前 loader 的结果。

有时咱们处理某种类型的文件须要多个 loader,这些 loader 的执行顺序和 use 数组中 loader 书写顺序是相反的,如解析 scss 文件时

{//处理.scss文件
    test: /\.scss$/,
    use: [{
        loader: "style-loader" // creates style nodes from JS strings
    }, {
        loader: "css-loader" // translates CSS into CommonJS
    }, {
        loader: "sass-loader" // compiles Sass to CSS
    }]
},

上面例子 webpack 是先通过 sass-loader,而后将结果传入 css-loader,最后再进入 style-loader

链路中间的 loader 返回什么样的结果都行,只要下一个接收的 loader 可以正常处理就行,可是最后一个调用 loader 的结果是须要传入至 webpack 中,webpack 指望它返回 JS 代码,以及可选的source map

注意:若是是处理顺序排在最后一个的 loader,那么它的返回值将最终交给 webpack 的 require,换句话说,它必定是一段可执行的 JS 脚本 (用字符串来存储),更准确来讲,是一个 node 模块的 JS 脚本。
// 处理顺序排在最后的 loader
module.exports = function (source) {
    // 这个 loader 的功能是把源模块转化为字符串交给 require 的调用方
    return `module.exports = ${JSON.stringify(source)}`
}

本例处理 txt 文件只有一个 txt-loader,最终传入至 webpack 中的是 hello Jay!,不是个可执行的 JS 脚本。最终代码以下

module.exports = function(source) {
    this.cacheable && this.cacheable()
    const options = loaderUtils.getOptions(this) || {}
    source = source.replace(/\[name\]/g, options.name)
    return `module.exports = ${JSON.stringify(source)}`
}

dist 中生成的 bundle.js 文件放入浏览器控制台中运行一下,能够看到输出 hello Jay!

image.png

咱们再看一个使用多个 loader 的例子,处理 html 文件并压缩,解析 html 并使之成为 JS 可执行的脚本的任务就交给现有的 html-loader,压缩的任务就我们本身来实现,就叫 html-optimize-loader 吧。

修改一下 webpack.config.js

// webpack.config.js
...
module: {
    rules: [
        {
            test: /\.txt$/,
            use: {
                loader: 'name-loader',
                options: {
                    name: 'Jay'
                }
            }
        },
        {
            test: /\.html$/,
            use: ['html-loader',
            {
                loader: 'html-optimize-loader',
                options: {
                    comments: false
                }
            }]
        }
    ]
},
resolveLoader: {
// html-loader 在 'node_modules'
modules: ['node_modules', path.resolve(__dirname, './src/loaders')]
},
...

这里咱们改为多个 loader 配置的模式,也在咱们新加的 html-optimize-loader 中加入了配置,压缩时是否保留注释。

src 中新建 test.html 文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <!-- 这里是 main 内容 -->
</body>
</html>

入口文件 app.js 改为 test.html

const html = require('./test.html')
console.log(html)

loaders 文件中新建 html-optimize-loader.js

// hmtl-optimize-loader.js
const Minimize = require('minimize')
const loaderUtils = require('loader-utils')

module.exports = function (source) {
    var callback = this.async()
    this.cacheable && this.cacheable()

    var options = loaderUtils.getOptions(this) || {} 
    var minimize = new Minimize(options)
    console.log(source)
    console.log(minimize.parse(source))
    return minimize.parse(source, callback)
}

这里 loader 咱们采用异步的方式,执行一下

image.png

发现 source 和压缩后的 source 都打印出来了,这里咱们直接将压缩后 source 直接传入 html-loader 中去处理了。你们能够将 options 中的 comment 设成 true,发现注释就会保留了,最终生成的 bundle 文件也能够丢进浏览器的控制台跑一下。

就这样咯,下一篇写实现一个 webpack plugin

相关文章
相关标签/搜索