rollup技术揭秘系列十二 handleGenerateWrite(可能是全网最系统性的rollup源码分析文章)

191 阅读4分钟

handleGenerateWrite 方法定义

执行完 "build" 之后就是 "generate" 阶段了。

先看下 handleGenerateWrite 的方法定义:

src/rollup/rollup.ts

async function handleGenerateWrite(
  isWrite: boolean,
  inputOptions: NormalizedInputOptions,
  unsetInputOptions: ReadonlySet<string>,
  rawOutputOptions: OutputOptions,
  graph: Graph
): Promise<RollupOutput> {
  const {
    options: outputOptions,
    outputPluginDriver,
    unsetOptions
  } = await getOutputOptionsAndPluginDriver(
    rawOutputOptions,
    graph.pluginDriver,
    inputOptions,
    unsetInputOptions
  );
  return catchUnfinishedHookActions(outputPluginDriver, async () => {
    const bundle = new Bundle(outputOptions, unsetOptions, inputOptions, outputPluginDriver, graph);
    const generated = await bundle.generate(isWrite);
    if (isWrite) {
      timeStart('WRITE', 1);
      if (!outputOptions.dir && !outputOptions.file) {
        return error(errorMissingFileOrDirOption());
      }
      await Promise.all(
        Object.values(generated).map(chunk =>
          graph.fileOperationQueue.run(() => writeOutputFile(chunk, outputOptions))
        )
      );
      await outputPluginDriver.hookParallel('writeBundle', [outputOptions, generated]);
      timeEnd('WRITE', 1);
    }
    return createOutput(generated);
  });
}

handleGenerateWrite 接收的参数:

  • isWrite 表示是否需要将打包后的代码写入文件
  • inputOptions 是 rollup 的 inputOptions 配置
  • unsetInputOptions 是一个空的 Set
  • rawOutputOptions 是我们传入的打包输出配置
  • graph 就是 rollup 的模块图,它保存了所有的源码信息和模块相关的信息

handleGenerateWrite 方法内部首先通过 const { options: outputOptions, outputPluginDriver, unsetOptions } = getOutputOptionsAndPluginDriver(rawOutputOptions, graph.pluginDriver, inputOptions, unsetInputOptions) 得到经过 merge 后的 outputOptions,outputPluginDriver, unsetOption。然后执行了 return catchUnfinishedHookActions() 操作。

catchUnfinishedHookActions(outputPluginDriver, callback) 方法内部调用了 Promise.race([callback(), emptyEventLoopPromise]) 并将结果返回。此处我们主要分析它的 callback 函数逻辑:

async () => {
  const bundle = new Bundle(outputOptions, unsetOptions, inputOptions, outputPluginDriver, graph);
  /**
   * bundle.generate(isWrite)会得到一个 outputBundleBase 对象。(类似 {index.js: {…}, acorn-bf6b1c54.js: {…}})
   */
  const generated = await bundle.generate(isWrite);
  if (isWrite) {
    if (!outputOptions.dir && !outputOptions.file) {
      return error(errorMissingFileOrDirOption());
    }
    // graph.fileOperationQueue.run() 会从队列中取出一个任务执行,它的作用主要是保证任务的执行顺序(先进先执行)
    await Promise.all(
      Object.values(generated).map(chunk =>
        graph.fileOperationQueue.run(() => writeOutputFile(chunk, outputOptions))
      )
    );
    //...
  }
  //createOutput(generated) 会得到一个 output[...chunks]
  return createOutput(generated);
};

callback 中第一步执行了 new Bundle 生成了 bundle 实例。然后执行 const generated = await bundle.generate(isWrite) 。这个 generated 就是最终的 outputBundle, 即以文件名为 key 以打包的区块为 value 组成的对象。它的接口定义如下:

interface OutputBundle {
  [fileName: string]: OutputAsset | OutputChunk;
}

isWrite 代表是否需要将打包后的 code 写入文件。并且 if 语句中第一步有判断, 如果既没有设置 outputOptions.dir 又没有设置 outputOptions.file 则会抛出一个错误提示。然后执行 Promise.all 方法将所有的 chunks 执行 writeOutputFile(chunk, outputOptions) 操作。

writeOutputFile 方法的作用很明确,即将 chunk 写入到文件中。它的代码定义在 src/rollup/rollup.ts 中:

async function writeOutputFile(
  outputFile: OutputAsset | OutputChunk,
  outputOptions: NormalizedOutputOptions
): Promise<unknown> {
  // 将 outputFile.fileName 参数解析为绝对路径
  const fileName = resolve(outputOptions.dir || dirname(outputOptions.file!), outputFile.fileName);
  // 创建文件
  await fs.mkdir(dirname(fileName), { recursive: true });
  // 写文件
  return fs.writeFile(fileName, outputFile.type === 'asset' ? outputFile.source : outputFile.code);
}

handleGenerateWrite 函数逻辑总结

  1. 首先通过 getOutputOptionsAndPluginDriver 方法获取 outputOptions, outputPluginDriver, unsetOptions
  2. 执行 new Bundle(outputOptions, unsetOptions, inputOptions, outputPluginDriver, graph).generate(isWrite) 得到 outputBundle 对象。
  3. 如果 isWrite 为 true 则循环 chunks 执行 writeOutputFile(chunk, outputOptions) 将最终的代码写入到指定文件中。
  4. 执行 createOutput(generated) 并最终将 output[...chunks] 返回。

下一节我们继续分析 bundle.generate 的详细过程。

rollup 揭秘相关文章