ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Rolldown Replace Plugin 完全指南:用内置替换插件实现构建期常量注入与代码改写

Rolldown Replace Plugin 完全指南:用内置替换插件实现构建期常量注入与代码改写 Rolldown Replace Plugin 完全指南用内置替换插件实现构建期常量注入与代码改写【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown本文围绕 Rolldown 的内置插件replacePlugin展开它通过纯字符串操作在打包阶段完成代码替换是rollup/plugin-replace的 Rust 原生等价实现。读完本文你将掌握 replace 插件的全部配置项delimiters、preventAssignment、objectGuards、sourcemap及其底层匹配原理能够在实际项目中安全地注入process.env.NODE_ENV、构建版本号等常量并理解长键优先排序、词边界防护等避免误替换的关键机制。插件定位Rolldown 内置的字符串替换能力replacePlugin是 Rolldown 内置built-in插件体系中的一员与 Rollup 生态中广泛使用的rollup/plugin-replace功能对等但实现运行在 Rust 侧而非 JavaScript。从源码看该插件的 Rust 实现位于 crates/rolldown_plugin_replace插件注册名称为builtin:replace见 plugin.rs并通过 N-API 绑定层暴露给 JS API见 binding_replace_plugin_config.rs。它适用的典型场景包括构建期注入环境变量如将process.env.NODE_ENV替换为production配合后续的 Tree Shaking / 死代码消除注入构建元信息如版本号__VERSION__、提交哈希、构建时间戳按目标环境改写代码中的占位标识符。由于替换发生在 Rust 侧且基于高效的HybridRegex与MagicStringstring_wizard实现它比纯 JavaScript 的正则逐文件替换具有更高的性能潜力。快速上手导入与基础用法在 Rolldown 配置中replacePlugin从rolldown/plugins导出见 plugins-index.ts与defineConfig一起使用import { defineConfig } from rolldown; import { replacePlugin } from rolldown/plugins; export default defineConfig({ input: src/index.js, output: { dir: dist, format: esm, }, plugins: [ replacePlugin( { process.env.NODE_ENV: JSON.stringify(production), __buildVersion: 15, }, { preventAssignment: false, }, ), ], });需要注意两个参数的含义第一个参数是替换表values键为要匹配的源码片段值为替换结果。JS 侧的 replace-plugin.ts 会在运行时把所有非字符串值统一String()化因此__buildVersion: 15最终以字符串15参与替换第二个参数是选项对象对应 Rust 侧的ReplaceOptions见 plugin.rs其中values使用FxHashMap存储——源码注释明确说明不在意键的遍历顺序因为插件内部会另行排序。配置选项详解以下四个选项均可在第二个参数中配置与文档定义一致并可在 binding_replace_plugin_config.rs 中看到它们在绑定层的完整字段声明。delimiters自定义键的匹配边界类型[string, string]两个正则表达式字符串默认值[\\b, \\b(?!\\.)]该选项控制每个键在什么前后缀条件下才被认为匹配delimiters[0]左边界必须紧邻在键之前出现的内容delimiters[1]右边界必须紧邻在键之后出现的内容。默认值[\\b, \\b(?!\\.)]的含义是键必须位于词边界\b且右侧的\b(?!\.)额外要求键之后不是点号——这正是为了跳过process.env这类属性访问配置键process不会误伤process.env。从实现看当传入自定义 delimiters 时插件会用两个边界把键包裹起来构造正则{left}({keys}){right}{lookahead}并通过 ECMAScript 引擎编译见 plugin.rs未传时则走优化路径直接使用\b({joined_keys})\b的 Rust regexplugin.rs。绑定层还会校验长度delimiters必须是恰好两个字符串的数组否则抛出delimiters expects a tuple of two strings错误binding_replace_plugin_config.rs。仓库测试目录中的 special_delimiters 与 delimiters 用例覆盖了自定义边界的行为验证。preventAssignment跳过变量声明中的赋值类型boolean默认值false开启后插件会跳过对变量声明语句中的键的替换避免把const DEBUG true;中的DEBUG误替换成常量字面量否则会产出const false true;这样的非法代码replacePlugin({ DEBUG: false }, { preventAssignment: true }); // const DEBUG true; // Not replaced (assignment) // console.log(DEBUG); // Replaced with false其底层实现分两层见 plugin.rs通过is_variable_declaration_prefix判断匹配位置之前的代码是否以const/let/var加空白结尾plugin.rs检查匹配之后是否紧跟且不是/这类比较运算符若是则跳过。此外开启后构造匹配正则时还会附加前瞻断言(?!\s*[^])plugin.rs在正则层面提前排除赋值位置。测试用例 assignment 专门验证了这一行为。objectGuards自动展开typeof守卫类型boolean默认值false开启后对于形如process.env.NODE_ENV这样的对象属性链键插件会自动生成对应的typeof替换用于常见的“环境守卫”写法replacePlugin({ process.env.NODE_ENV: JSON.stringify(production) }, { objectGuards: true }); // Also replaces: // typeof process → object // typeof process.env → object实现位于 utils.rsexpand_typeof_replacements会先把键按.切分验证每一段都是合法的 JavaScript 标识符is_object_property_chainutils.rs然后对链上的每一级前缀生成typeof xxx→object的额外替换项。这样typeof process.env.NODE_ENV ! undefined这类代码在替换process.env.NODE_ENV之后依然可以正确求值。该函数带有完整的单元测试覆盖了空串、非法字符!、.连写、多级链a.b.c.d以及 Unicode 标识符等边界情况utils.rs集成测试见 simple_object_guards。sourcemap为替换生成源码映射类型boolean默认值false开启后插件在输出转换结果的同时会基于MagicString生成 high-resolutionHires::True的 source map。源码中transform与render_chunk两个 hook 都通过HookTransformOutputMap::from_if_enabled(self.sourcemap, ...)按需生成映射plugin.rs。这对于需要把产物源码映射回原始文件进行调试的工程是有价值的补充。工作原理解析为什么替换是安全的长键优先防止部分替换keys 会按长度降序排序keys.sort_by_key(|key| Reverse(key.len()))见 plugin.rs确保更长的键先参与匹配避免长键中的前缀键抢先替换导致残片// Input code: const apiV2 API_URL_V2; const api API_URL; replacePlugin({ API_URL: https://api.example.com, API_URL_V2: https://api.example.com/v2, }); // Without length sorting (❌ wrong): // const apiV2 https://api.example.com_V2; // Incorrect! // const api https://api.example.com; // With length sorting (✅ correct): // const apiV2 https://api.example.com/v2; // API_URL_V2 matched first // const api https://api.example.com; // Then API_URL matched多个键会以regex::escape转义后通过|连接成一个整体正则plugin.rs因此即使键中包含.、$等正则特殊字符也能被安全匹配——测试用例 special_characters 覆盖了这类场景。词边界避免子串误伤默认的\b边界保证了替换只发生在完整单词处。看下面的例子// Input code: const currentEnv env; const environment getEnvironment(); const config process.env.NODE_ENV; replacePlugin({ env: production }); // Output: // const currentEnv production; ✅ env as standalone word // const environment getEnvironment(); ✅ env is part of environment // const config process.env.NODE_ENV; ✅ env after . (property access)其中第三行process.env.NODE_ENV不被替换是因为默认右边界\b(?!\.)在词边界之后还断言了“下一个字符不是点号”在 Rust 优化路径下这一(?!\.)断言由look_around_assert在匹配后进行补充检查见 plugin.rs。双阶段执行transform 与 render_chunk插件注册的 hook 为Transform | RenderChunkplugin.rs即transform 阶段对每个模块的原始代码执行替换plugin.rs这是主要生效阶段render_chunk 阶段对最终产出的 chunk 再做一轮替换plugin.rs可用于处理打包过程中由其他代码生成的、或模块化包装引入的内容。两个阶段都基于MagicString::update原位更新匹配区间plugin.rs这保证了除替换点之外的所有代码在输出中逐字节保留。典型实战示例结合测试用例验证仓库内置的 form 测试套件 覆盖了十类场景可以作为写配置时的对照参考测试目录验证点replace_strings基础字符串替换replace_nothing无可替换内容时输出保持不变assignmentpreventAssignment跳过声明赋值delimiters/special_delimiters默认与自定义边界的行为差异match_variables变量名的匹配与替换process_checkprocess相关键与属性访问的共存simple_object_guardsobjectGuards的typeof展开special_characters键中含特殊字符时的转义处理ternary_operator三元表达式中的替换typescript_declareTypeScript 声明中的行为每个目录下都包含input.js/input.ts与artifacts.snap快照断言例如 simple_object_guards/input.js 使用[foo, foo.qux, foo]作为输入来验证对象属性链与字符串字面量的区分。从 rollup/plugin-replace 迁移官方文档提供了如下特性对比可作为迁移决策依据Featurerollup/plugin-replacerolldownAPIreplace({ values: {...} })replacePlugin({...}, options)Function values✅() value❌ Static values onlyFile filtering✅ include/exclude❌ All filesPerformanceJavaScriptRust (faster)迁移时最需要注意的是两点函数值不支持rollup/plugin-replace允许values中的值为函数() value惰性求值而 rolldown 版本只接受静态值。替代方案是在调用前先在 JS 侧求值——这也是文档给出的迁移示例的核心差异无 include/exclude 文件过滤rolldown 版本会对所有模块执行替换。如果原本依赖文件过滤来规避误替换迁移后需要改用更精确的键或自定义 delimiters 来控制匹配范围。文档给出的迁移示例// Before (rollup/plugin-replace) replace({ values: { __VERSION__: () getVersion() }, include: [src/**/*.js], }); // After (rolldown) replacePlugin({ __VERSION__: JSON.stringify(getVersion()), });注意 rolldown 版本在 JS 侧会执行String(value)转换replace-plugin.ts因此数字、布尔值均可直接写入 values无需手动包裹JSON.stringify但字符串值仍建议显式加引号否则替换结果会成为裸标识符。总结与适用边界replacePlugin是 Rolldown 内置插件体系中的一个轻量但设计严谨的成员它在 Rust 侧通过「长键优先 词边界 属性访问断言」三重机制保证替换的确定性通过MagicString保证非替换代码的逐字节保真并通过transform/render_chunk双 hook 覆盖模块级与 chunk 级两轮处理。适合用它处理环境变量注入、构建元信息替换、特征开关feature flag改写等场景。不适合用它处理需要动态求值、需要按文件路径过滤、或依赖 AST 语义理解的复杂改写——这类需求应优先考虑基于 Rust 的 AST 插件或常规 JS 插件。完整配置字段与绑定层校验可进一步参考 binding_replace_plugin_config.rs官方文档见 docs/builtin-plugins/replace.md。【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表