Rollup插件能否访问链上前序插件生成的AST以避免重复解析?
Rollup 多插件单次解析AST优化方案
以下三种方案都可以实现文件仅解析一次,无需合并插件:
方案1:利用Rollup原生AST传递能力
Rollup 2.0及以上版本中,transform钩子的第三个参数meta会携带前序插件返回的AST,无需重复调用this.parse:
- 第一个执行的transform插件完成AST解析后,返回结果中携带
ast字段 - 后续所有transform插件优先读取
meta.ast,不存在时再 fallback 到自行解析
改造后的示例代码:
// rollup.config.js import {createFilter} from '@rollup/pluginutils'; import {simple} from 'acorn-walk'; import {attachComments} from 'astravel'; import {generate} from 'astring'; // 通用AST获取逻辑,可单独抽成工具函数供所有插件使用 function getAST(code, parse, meta) { // 优先用前序插件生成的AST if (meta?.ast) return meta.ast; const comments = []; const ast = parse(code, {onComment: comments}); attachComments(ast, comments); return ast; } export default { input: 'src/main.js', output: {file: 'bundle.js', format: 'cjs'}, plugins: [{ name: 'plugin1', transform(code, id, meta) { const ast = getAST(code, this.parse, meta); simple(ast, { Identifier(n) { if (n.name === 'wrong') n.name = 'right'; } }); return { code: generate(ast, {comments: true}), ast, // 必须返回ast供后续插件使用 map: null }; } }, { name: 'plugin2', transform(code, id, meta) { // 直接拿前序plugin1返回的AST,不用重新parse const ast = getAST(code, this.parse, meta); simple(ast, { CallExpression(n) { if (n.callee.type === 'Identifier' && n.callee.name === 'mylog') { n.callee = { type: 'MemberExpression', object: {type: 'Identifier', name: 'console', start: n.start, end: n.end}, property: {type: 'Identifier', name: 'log', start: n.start, end: n.end}, computed: false, start: n.start, end: n.end } } } }); return { code: generate(ast, {comments: true}), ast, // 仍需返回,方便后续其他插件使用 map: null }; } }] };
方案2:跨插件共享AST缓存
如果插件执行顺序不固定,或者需要兼容更低版本的Rollup,可以用全局缓存存储每个文件的解析结果,所有插件共用同一个缓存实例:
// 全局AST缓存,所有插件共享 const astCache = new Map(); // 插件中使用逻辑 transform(code, id) { let ast = astCache.get(id); if (!ast) { const comments = []; ast = this.parse(code, {onComment: comments}); attachComments(ast, comments); astCache.set(id, ast); } // 后续处理ast逻辑... // 注意:如果当前插件修改了ast,需要更新缓存 astCache.set(id, ast); return { code: generate(ast), ast, map: null } } // 在buildEnd钩子清空缓存避免内存泄漏 buildEnd() { astCache.clear(); }
方案3:利用moduleParsed钩子复用Rollup内部AST
Rollup本身会对所有模块做一次AST解析,解析完成后触发moduleParsed钩子,可以直接复用这个官方生成的AST,连第一次手动parse都可以省掉:
const astCache = new Map(); export default { plugins: [ { name: 'ast-cache-provider', moduleParsed(moduleInfo) { // moduleInfo.ast就是Rollup内部解析好的AST astCache.set(moduleInfo.id, moduleInfo.ast); }, buildEnd() { astCache.clear(); } }, // 后续所有插件都可以直接从astCache取对应id的AST使用 { name: 'plugin1', transform(code, id) { const ast = astCache.get(id); // 处理逻辑... } } ] }
注意事项
- 如果不同插件需要对AST做互不干扰的修改,可以在拿到AST后做一次浅拷贝,避免污染原始缓存
- 如果你用到了自定义acorn插件,需要确保Rollup的acorn配置和你parse时的配置一致,否则建议使用方案2自行缓存
- 修改AST后记得重新生成code返回,避免Rollup使用未修改的原始代码
内容的提问来源于stack exchange,提问作者Dominik Schreiber
相关产品推荐
相关产品推荐

