You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 05:06:04