Rollup 官方插件 @rollup/plugin-inject 详解

@rollup/plugin-inject是rollup官方自带的一个插件,是对标webpack中ProvidePlugin的存在。inject插件功能实用,源码也很值得阅读。今天我就为大家详解inject插件的使用。

一句话概括插件是干嘛的?

@rollup/plugin-inject 会通过语法解析与作用域分析,扫描代码中未声明的全局变量使用,自动在对应模块顶部插入精准的 import 语句,将全局变量的引用替换为从指定模块的具名 / 默认导入,彻底替代 "全局污染式" 的变量引用。

核心本质:把 "全局变量" 变成 "显式导入",保留使用便捷性的同时,遵循 ES Module 规范,消除全局依赖隐患。

应用场景

环境变量注入

javascript 复制代码
inject({
    modules: {
        PUBLIC_KEY: ["@/config", "PUBLIC_KEY"]
    }
});

示例代码

javascript 复制代码
console.log(PUBLIC_KEY);

转换后

javascript 复制代码
import { PUBLIC_KEY } from "@/config";

console.log(PUBLIC_KEY);

相比replace插件,inject插件包括了语法解析和作用域分析,可以确保是全局变量才给替换。而且避免了超大数据重复而增大体积。

Polyfill注入

我们常用的polyfill是污染性的,本质就是挂在全局变量上。使用inject插件,可以在不污染全局变量的情况下使用polyfill。

比如许多库中常有判断某些特性是否支持,理想情况是遇到什么差异,就应该用什么特性来判断。然而业界常常一言不合就用 XMLHttpRequest 来判断是否是IE6,也不管是不是用于请求的场景。如果我们在全局加入 XMLHttpRequest 就会导致许多地方误判。

我们使用inject插件纯净注入polyfill并指定范围,只在有限的地方注入,避免其他地方误判。

配置

javascript 复制代码
inject({
    modules: {
        XMLHttpRequest: ["any-xhr-polyfill", 'default']
    },
    // 控制注入范围,只在ajax封装工具内有效
    include: ["some-ajax-util"]
});

转换前

javascript 复制代码
// xxx-ajax.js
var xhr = new XMLHttpRequest();

// 其他位置
var isIE6 = typeof XMLHttpRequest === "undefined"

转换后

javascript 复制代码
// xxx-ajax.js
// 由于这个文件在插件的include范围内。会被改成import引入。
import XMLHttpRequest from 'any-xhr-polyfill';
var xhr = new XMLHttpRequest();

// 其他位置
// 由于这个文件不在插件的include范围内。保持原样。
var isIE6 = typeof XMLHttpRequest === "undefined"

理论上,所有不可完全实现功能的polyfill(sham)都需要纯净注入。因为使用时如果无法完整实现时需要判断和降级。

全局变量模拟

在传统js引入时往往采用全局变量来使用。在工程化项目中往往使用npm包来管理。这时候可以使用inject插件来转化。

javascript 复制代码
// 配置
inject({
    modules: {
        $: "jquery"
    }
});

// 原始代码
$("#app").addClass("active");

// 转换后
import $ from "jquery";

$("#app").addClass("active");

特定运行环境SDK模拟

例如,我在编写云闪付小程序时发现一个问题。使用云闪付开发者工具无法模拟云闪付upsdk的行为。于是,可以使用inject插件来模拟:

javascript 复制代码
inject({
    modules: {
        upsdk: ["@/utils/upsdk", "*"]
    }
});

这样,在开发环境中,代码中的 upsdk 会自动指向我们模拟的实现,而在生产环境中则可以指向真实的SDK。

全局变量隔离

在一些复杂的项目中,可能存在多个模块或库同时使用相同的全局变量名,但实际指向不同的实现。如果这些变量直接挂在全局,可能会导致冲突或不可预期的行为。@rollup/plugin-inject 可以帮助我们隔离全局变量,为每个模块提供独立的变量实例,而不是共享同一个全局对象。

javascript 复制代码
module.exports = {
    plugins: [
        inject({
            modules: {
                api: ["@/utils/impl1", '*']
            },
            include: ["src/xxxx"]
        }),
        inject({
            modules: {
                api: ["@/utils/impl2", '*']
            },
            include: ["src/yyyy"]
        })
    ]
};

上述示例配置表达了:一个全局变量"api",在不同文件夹的源代码中有不同的实现。

插件顺序与最佳实践

@rollup/plugin-inject 应该放在 plugins 数组靠后的位置。因为inject插件是一个兜底插件,如果业务插件中产生了全局变量,要在后面用inject插件才是正确的。

但是inject插件要在处理import的插件前运行,比如和rollup-plugin-import在一起时就要在rollup-plugin-import的前面。

大致转换顺序如下:

  • 非es格式转es(如json、typescript)
  • 业务转换规则(如vite的import.meta.glob、new URL('xxx', import.meta.url)转化)
  • @rollup/plugin-inject插件(这是兜底性的插件)
  • rollup-plugin-import插件(这也是兜底性的插件,要处理inject插件生成的import)

插件在边界场景的处理

inject插件有语法解析和作用域分析,因此内部变量能够正常处理。业界的一些构建策略一味地追求构建速度,直接字符串替换了,也是令人难绷。

javascript 复制代码
// 看看以下示例配置在极端情况的表现
inject({
    modules: {
        foo: ["@/config", "foo"]
    }
});

作用域分析

inject插件能正确识别到内部变量

javascript 复制代码
// 这个foo是全局变量,会被注入
console.log(foo);

export function bar() {
    var foo = 1;
    // 这个foo是内部变量,保持原样
    console.log(foo);
};

转化后

javascript 复制代码
import { foo } from "@/config";

// 这个foo是全局变量,被转化为import引入了
console.log(foo);

export function bar() {
    var foo = 1;
    // 这个foo是内部变量,保持原样
    console.log(foo);
};

对象表达式的属性缩写

inject插件能正确识别到对象表达式的属性缩写

javascript 复制代码
console.log({ foo });

转化后

javascript 复制代码
import { foo } from "@/config";

console.log({ foo });

静态块

inject插件在 static 块下作用域分析不正确

javascript 复制代码
// 此处foo是全局变量,但是会被inject插件误判为static内的var,导致没有被替换
console.log(foo);

export class Bar {
    static {
        var foo = 1;
    }
}

这个不是rollup特有的问题,是业界普遍存在的问题。好在rollup是个活跃项目,笔者在发稿前已经提了PR,相信不久以后就能更新。

插件的局限性

inject能够按模块注入全局变量,但是在polyfill处理场景,更希望根据情况选择性注入。

javascript 复制代码
const KEY = typeof Symbol === "function" ?
    Symbol() :
    '__key'

上面示例代码中更希望typeof Symbol不注入,判断的是原生变量;调用Symbol()要注入,用来支持不兼容的浏览器。

再有,setTimeout 2个参数不注入,多个参数要注入等。

这些针对polyfill处理的场景,应该使用更专业的rollup-plugin-polyfill-inject。

总结

@rollup/plugin-inject 是一个精巧且实用的官方插件,它通过对 AST 的语法解析和作用域分析,优雅地解决了全局变量转换为模块导入的问题。无论是环境变量注入、还是开发环境的 SDK 模拟,它都能胜任。虽然它在 static 块等极少数边界场景下还有待完善,且缺乏更细粒度的条件注入能力,但在绝大多数场景下,它都是替代粗暴的字符串替换方案的最佳选择。掌握 inject 插件,能让你的 rollup 工程化方案更加健壮与优雅。

相关推荐
kyriewen8 分钟前
折腾了半年 AI 编程工作流,最后发现效率瓶颈是桌上那块屏幕
前端·javascript·ai编程
蜗牛前端35 分钟前
codex 全流程开发上线的高颜值礼簿小程序
前端·微信小程序
大龄秃头程序员1 小时前
我在图文流 App 里落地双层缓存、弱网降级与 OOM 治理
前端
老王以为1 小时前
React Renderer 分离的多平台架构
前端·react native·react.js
hunterandroid1 小时前
Kotlin Coroutines 与 Flow:让异步任务更清晰
前端
Bigger2 小时前
从零搭建 AI 代码审查服务:一份前端也能看懂的 Python 学习笔记
前端·ci/cd·ai编程
lichenyang4532 小时前
JSAPI、NAPI、Biz、Imp:ASCF Demo 如何真正调用系统能力和 C++ 能力
前端
lichenyang4533 小时前
IPC、JSVM、UIThread、libuv:ASCF 架构图里最容易混的几个词
前端
用户059540174463 小时前
Redis记忆存储故障恢复测试踩坑实录:手动测试让我漏掉了2个一致性Bug
前端·css
用户2136610035723 小时前
Vue2脚手架工程化与Axios集成
前端·vue.js