• 轻松玩转Vite/Rollup/webpack/esbuild/Rspack/babel插件开发(一)


    大家好,我是VoerkaI18n前端多语言解决方案的作者,以下是我的一波开源项目:

    我来给大家分享一下提高自己前端技术段位的一些小技能。

    每个前端开发均会使用到Vite/Rollup/webpack/esbuild/Rspack/babel等前端代码编译工具库,前端代码编译工具负责进行源代码的转译、打包、压缩等工作。
    现代主流的前端编译工具一般设计为由一个编译核心+若干扩展插件的形式,从而形成一个庞大的生态系统。编译核心主要负责AST生成编译生命周期管理,以及插件子系统。而大部份的功能均尽可能使用扩展插件来实现。

    一般情况下,我们只需要使用各编译工作生态内的扩展插件就可以了,但总有一些自动化功能无法满足,或者无法找到合适的插件,此时就需要自已开发一个对应的Vite/Rollup/webpack/esbuild/Rspack/babel插件。

    如何开发编译插件

    那么如何开发一个Vite/Rollup/webpack/esbuild/Rspack/babel插件呢?一般来讲,我们需要了解以下知识:

    Rollup为例:

    • 首先我们得了解编译机制和编译生命周期

    下图是Rollup的编译流程以及命令周期钩子事件,我们得搞懂Rollup是如何对源代码进行编译, 每个环节分别负责什么,每个插件钩子的参数是什么等等等,这样我们才能够在合适时机注册钩子进行各种操作。
    在这里插入图片描述
    从我个人的角度看,要完全掌握Rollup编译流程中的各项细节也是要耗不少精力的。

    • 接着找到合适的插件扩展点

    接下来,我们得了解编译生命周期和流程中的各个钩子的声明、参数以及作用机制等,然后选择一个合适的插件扩展点,注册插件进行处理。

    • 操纵源代码
      最后在插件代码里面就可以对源码进行各种骚操作了。要操作源代码一般不可避免地会涉及到正则表达式、AST抽象语法树等知识。

    通用构建插件开发神器

    对于库开源者而言,要让自己开发的库在不同场景下均可以使用,就要分别开发Vite/Rollup/webpack/esbuild/Rspack/babel插件,想想要花很大的精力去了解各个编译工具的插件开发机制,实在是耗时又耗力。

    以笔者开发的VoerkaI18n多语言解决方案为例,就分别开发babelvitewebpack三种插件。为了开发这三种不同的插件,笔者也不得已花了不小的时间来了解相应的知识。

    现在好了,知名的开源组织unjs(知名大神Antfu就是该组织成员)为我们贡献了一个通用构建插件系统 -Unplugin
    Unplugin提供了一个非常简单的API,适配Vite/Rollup/webpack/esbuild/Rspack/,只需要开发一个unplugin就可以输出Vite/Rollup/webpack/esbuild/Rspack/插件。

    unplugin支持以下hook
    在这里插入图片描述

    最简单用法就是使用transformIncludetransform进行转码,

    • transformInclude(id):用来匹配哪一个文件要进行转码。
    • transform(code): 输入源代码字符串,返回转码后的源代码。

    开发unplugin非常简单,如下:

    import type { UnpluginFactory } from 'unplugin'
    import { createUnplugin } from 'unplugin'
    
    export interface Options {
      // define your plugin options here
    }
    
    export const unpluginFactory: UnpluginFactory<Options | undefined> = options => ({
      name: 'unplugin-starter',
      // webpack's id filter is outside of loader logic,
      // an additional hook is needed for better perf on webpack
      transformInclude(id) {
        return id.endsWith('main.ts')
      },
      // just like rollup transform
      transform(code) {
        return code.replace(/