NiceLeeのBlog 用爱发电 bilibili~

Webpack Plugin制作2-在打包目录输出文件列表

2021-10-13
nIceLee

阅读:


现在分析一波官方文档的简单例子,这个例子把所有的文件列表输出到一个markdown文件。

准备工作

本文的工程环境承接上文
为了使得输出目录更加丰富,我们添加一个html-webpack-plugin,使得输出文件目录更加丰富。
    + npm i -D html-webpack-plugin
    + webpack.config.js中引入插件
    + webpack.config.js中配置插件

这个简单的工程目录如下:

- build
- node_modules
- package.json
- plugins
    - 0.createLicense.js
- loaders
- src
    - index.js
- LICENSE

我们看一个官方的例子,并稍做分析。

插件实现

再来简单回顾一下怎么实现并使用一个插件,以后就不再复述了。

  • 实现plugin/1.createFileList.js, 内容如下:

    class FileListPlugin {
        // 设置默认配置
        static defaultOptions = {
            outputFile: 'assets.md',
        };
        constructor(options = {}) {
            // 接收参数生成配置
            this.options = { ...FileListPlugin.defaultOptions, ...options };
        }
        apply(compiler) {
            const pluginName = FileListPlugin.name;
            // 从compiler里取出后面要用的东西
            const { hooks, webpack } = compiler;
            const { Compilation } = webpack;
            const { RawSource } = webpack.sources;
            // 为了获得传入的compilation这个关键对象, 我们必须进行一次hook
            // 我们应当尽早hook,于是选择了 hooks.thisCompilation
            hooks.thisCompilation.tap(pluginName, (compilation) => {
                // 我们在assets都已经差不多处理好的阶段(Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE)
                //   遍历assets,将它们的名称汇在一起形成一个文件
                //   将新文件提交给webpack
                compilation.hooks.processAssets.tap({
                    name: pluginName,
                    stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
                }, (assets) => {
                    const content =
                        '# In this build:\n\n' +
                        Object.keys(assets)
                            .map((filename) => `- ${filename}`)
                            .join('\n');
                    compilation.emitAsset(
                        this.options.outputFile,
                        new RawSource(content)
                    );
                }
                );
            });
        }
    }
    module.exports = FileListPlugin;
    
  • webpack.config.js中引入并使用该插件

    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const CreateLicensePlugin = require('./plugins/0.createLicense')
    + const FileListPlugin = require('./plugins/1.createFileList')
    ...
        plugins: [
            new HtmlWebpackPlugin(),
            new CreateLicensePlugin(),
    +        new FileListPlugin(),
        ],
    
  • 结果展示
    最终我们会在build文件夹下发现一个assets.md文件,其内容为:

    # In this build:
    
    - bundle.js
    - index.html
    

简单分析

具体的看代码里的注释就有了,感觉没啥好说的。

两个概念

这个插件出现了另一个特别重要的概念compilation
先不去纠结那些细枝末节,我们脑海里要有个印象:

  • 有两个核心的东西,一个compiler、一个compilation,它们身上挂了相关流程的各种钩子,以及可能会用到的一些重要属性。

官方文档说理解这两个东西是an important first step in extending the webpack engine,但并没有对它们做实质性的详细说明。 在Compilation章节有点干货,它是这样说的:

The Compilation module is used by the Compiler to create new compilations (or builds). A compilation instance has access to all modules and their dependencies (most of which are circular references). It is the literal compilation of all the modules in the dependency graph of an application. During the compilation phase, modules are loaded, sealed, optimized, chunked, hashed and restored.

意思大概是,Compilation是被Compiler用来编译/打包的。 Compilation用来处理模块module的相关事务。 这个就厉害了,因为webpack的东西都是模块化的。

干说没用,我们大致看看它们挂的钩子,应该心里有点数了

  • Compiler: xx environment、initialize、xxRun、 xx compile、xx compilation、xx make、 xx emit
  • Compilation: xxModule、xx seal、xx optimize、xx chunk、 xxHash、xxAssets

说说我的理解

或者我可以通过另外的做法重写这个例子,以便展示我的思考回路。
首先,先确定一个前提:

我们只关注最终的打包不考虑与其它插件的配合,不考虑热重载等等。

我们要取到最终的文件列表,那么肯定要在项目靠后的阶段。
如果可以,我想直接hook compiler.done
但这是不可行的,因为compiler对象上没有我们需要的输出文件信息,而done的hook回调仅仅传出来一个指示成功还是失败的state信息。
我们要找那么一个hook,它的发布函数的回调传参:

  • 包含有输出文件信息assets;
  • 或者是包含compilation也行,因为我们可以通过compilation.getAssets()方法获取到输出文件信息。

为了和例子进行区别,我们使用的hook是compiler.hooks.afterEmit,这对应的阶段是在webpack已经将文件全部输出到build文件夹之后。
以下是按照这一思路重写的plugin:

const { resolve } = require("path");
const fs = require("fs");
class FileListPlugin {
    static defaultOptions = {
        outputFile: 'assets.md',
    };
    constructor(options = {}) {
        this.options = { ...FileListPlugin.defaultOptions, ...options };
    }
    apply(compiler) {
        const pluginName = FileListPlugin.name;
        compiler.hooks.afterEmit.tap(pluginName, (compilation) => {
            const content =
                    '# In this build(diy):\n\n' +
                    Object.keys(compilation.getAssets())
                        .map((filename) => `- ${filename}`)
                        .join('\n');
            const path = resolve(compiler.options.output.path, this.options.outputFile)
            fs.writeFileSync(path, content)
        });
    }
}
module.exports = FileListPlugin;

源代码

https://github.com/nicennnnnnnlee/webpack-plugin-loader-examples

系列文章


内容
隐藏