loader

官网上的定义:

loader 是一个转换器,用于对源代码进行转换。

例如 babel-loader 能够将 ES6 代码转换为 ES5 代码;sass-loadersass 代码转换为 css 代码。

个别 loader 的配置代码如下:

module: {        rules: [            {                test: /\.js$/,                use: [                    // loader 的执行程序从下到上                    {                        loader: path.resolve('./src/loader2.js'),                    },                    {                        loader: path.resolve('./src/loader1.js'),                    },                ]            }        ]    },

rules 数组蕴含了一个个匹配规定和具体的 loader 文件。

上述代码中的 test: /\.js$/ 就是匹配规定,示意对 js 文件应用上面的两个 loader。

而 loader 的解决程序是自下向上的,即先用 loader1 解决源码,而后将解决后的代码再传给 loader2。

loader2 解决后的代码就是最终的打包代码。

loader 的实现

loader 其实是一个函数,它的参数是匹配文件的源码,返回后果是解决后的源码。上面是一个最简略的 loader,它什么都没做:

module.exports = function (source) {    return source}

这么简略的 loader 没有挑战性,咱们能够写一个略微简单一点的 loader,它的作用是将 var 关键词替换为 const

module.exports = function (source) {    return source.replace(/var/g, 'const')}

写完之后,咱们来测试一下,测试文件为:

function test() {    var a = 1;    var b = 2;    var c = 3;    console.log(a, b, c);}test()

wepback.config.js 配置文件为:

const path = require('path')module.exports = {    mode: 'development',    entry: {        main: './src/index.js'    },    output: {        filename: 'bundle.js',        path: path.resolve(__dirname, 'dist')    },    module: {        rules: [            {                test: /\.js$/,                use: [                    {                        loader: path.resolve('./src/loader1.js'),                    },                ]            }        ]    },}

运行 npm run build,失去打包文件 bundle.js,咱们来看一看打包后的代码:

eval("function test() {\r\n    const a = 1;\r\n    const b = 2;\r\n    const c = 3;\r\n    console.log(a, b, c);\r\n}\r\n\r\ntest()\n\n//# sourceURL=webpack:///./src/index.js?");

能够看到,代码中的 var 曾经变成了 const

异步 loader

方才实现的 loader 是一个同步 loader,在解决完源码后用 return 返回。

上面咱们来实现一个异步 loader:

module.exports = function (source) {    const callback = this.async()    // 因为有 3 秒提早,所以打包时须要 3+ 秒的工夫    setTimeout(() => {        callback(null, `${source.replace(/;/g, '')}`)    }, 3000)}

异步 loader 须要调用 webpack 的 async() 生成一个 callback,它的第一个参数是 error,这里可设为 null,第二个参数就是解决后的源码。当你异步解决完源码后,调用 callback 即可。

上面来试一下异步 loader 到底有没失效,这里设置了一个 3 秒提早。咱们来比照一下打包工夫:



上图是调用同步 loader 的打包工夫,为 141 ms;下图是调用异步 loader 的打包工夫,为 3105 ms,阐明异步 loader 失效了。

如果想看残缺 demo 源码,请点击我的 github。

plugin

webpack 在整个编译周期中会触发很多不同的事件,plugin 能够监听这些事件,并且能够调用 webpack 的 API 对输入资源进行解决。

这是它和 loader 的不同之处,loader 个别只能对源文件代码进行转换,而 plugin 能够做得更多。plugin 在整个编译周期中都能够被调用,只有监听事件。

对于 webpack 编译,有两个重要的对象须要理解一下:

Compiler 和 Compilation
在插件开发中最重要的两个资源就是 compiler 和 compilation 对象。了解它们的角色是扩大 webpack 引擎重要的第一步。

compiler 对象代表了残缺的 webpack 环境配置。这个对象在启动 webpack 时被一次性建设,并配置好所有可操作的设置,包含 options,loader 和 plugin。当在 webpack 环境中利用一个插件时,插件将收到此 compiler 对象的援用。能够应用它来拜访 webpack 的主环境。

compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变动,就会创立一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象体现了以后的模块资源、编译生成资源、变动的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多要害机会的回调,以供插件做自定义解决时抉择应用。

这两个组件是任何 webpack 插件不可或缺的局部(特地是 compilation),因而,开发者在浏览源码,并相熟它们之后,会感到获益匪浅。

plugin 的实现

咱们看一下官网的定义,webpack 插件由以下局部组成:

  1. 一个 JavaScript 命名函数。
  2. 在插件函数的 prototype 上定义一个 apply 办法。
  3. 指定一个绑定到 webpack 本身的事件钩子。
  4. 解决 webpack 外部实例的特定数据。
  5. 性能实现后调用 webpack 提供的回调。

简略的说,一个具备 apply 办法的函数就是一个插件,并且它要监听 webpack 的某个事件。上面来看一个简略的示例:

function Plugin(options) { }Plugin.prototype.apply = function (compiler) {    // 所有文件资源都被 loader 解决后触发这个事件    compiler.plugin('emit', function (compilation, callback) {        // 性能实现后调用 webpack 提供的回调        console.log('Hello World')        callback()    })}module.exports = Plugin

写完插件后要怎么调用呢?

先在 webpack 配置文件中引入插件,而后在 plugins 选项中配置:

const Plugin = require('./src/plugin')module.exports = {    ...    plugins: [        new Plugin()    ]}

这就是一个简略的插件了。

上面咱们再来写一个简单点的插件,它的作用是将通过 loader 解决后的打包文件 bundle.js 引入到 index.html 中:

function Plugin(options) { }Plugin.prototype.apply = function (compiler) {    // 所有文件资源通过不同的 loader 解决后触发这个事件    compiler.plugin('emit', function (compilation, callback) {        // 获取打包后的 js 文件名        const filename = compiler.options.output.filename        // 生成一个 index.html 并引入打包后的 js 文件        const html = `<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title>    <script src="${filename}"></script></head><body>    </body></html>`        // 所有解决后的资源都放在 compilation.assets 中        // 增加一个 index.html 文件        compilation.assets['index.html'] = {            source: function () {                return html            },            size: function () {                return html.length            }        }        // 性能实现后调用 webpack 提供的回调        callback()    })}module.exports = Plugin

OK,执行一下,看看成果。


完满,和预测的后果截然不同。

残缺 demo 源码,请看我的 github。

参考资料

  • 编写一个 loader
  • 编写一个插件