返回

Rollup 实践系列 01:从零开始手写一个 rollup

前端

rollup 与其思想

rollup 作为一款前端模块打包工具,在构建和优化 JavaScript 应用程序中发挥着重要作用。它通过将多个 JavaScript 模块组合成一个或多个更优化的 JavaScript 文件,实现模块化开发和性能优化。

rollup 的思想是以树状结构来解析和分析模块间的依赖关系,并据此决定需要打包的文件,最终生成一个捆绑包(Bundle)。其中,打包过程中运用一种被称为 Tree-Shaking 的优化技术,专门去除未被使用的代码,从而有效缩小文件体积,提升运行性能。

需求:自己的 rollup

手写 rollup,目的是深入了解 rollup 打包工具的工作原理与模块打包流程,为自定义的构建配置以及打包优化积累经验。

rollup 初探

思路

  • 首先需要收集项目的源码文件以及其依赖文件。
  • 然后根据依赖关系构建一个有向图。
  • 根据有向图进行拓扑排序,得到一个有序的文件列表。
  • 最后根据有序的文件列表进行打包。

实现步骤

1. 收集文件

通过 fs 模块遍历项目目录,收集项目的所有源码文件以及其依赖文件。

const fs = require("fs");

function collectFiles(dir) {
  const files = [];
  const dirs = [];
  const filesInDir = fs.readdirSync(dir);
  for (const file of filesInDir) {
    const path = `${dir}/${file}`;
    const stat = fs.statSync(path);
    if (stat.isFile()) {
      files.push(path);
    } else if (stat.isDirectory()) {
      dirs.push(path);
    }
  }
  for (const d of dirs) {
    files.push(...collectFiles(d));
  }
  return files;
}

2. 构建有向图

根据收集到的文件,通过分析每个文件的依赖关系,构建一个有向图。

function buildGraph(files) {
  const graph = {};
  for (const file of files) {
    const dependencies = [];
    const code = fs.readFileSync(file, "utf-8");
    const matches = code.match(/require\("(.+)"\)/g);
    if (matches) {
      for (const match of matches) {
        const dependency = match.slice(9, -2);
        dependencies.push(dependency);
      }
    }
    graph[file] = dependencies;
  }
  return graph;
}

3. 拓扑排序

对有向图进行拓扑排序,得到一个有序的文件列表。

function topologicalSort(graph) {
  const sorted = [];
  const visited = {};
  for (const node in graph) {
    if (!visited[node]) {
      visit(node);
    }
  }

  function visit(node) {
    visited[node] = true;
    for (const neighbor of graph[node]) {
      if (!visited[neighbor]) {
        visit(neighbor);
      }
    }
    sorted.push(node);
  }

  return sorted.reverse();
}

4. 打包

根据有序的文件列表,进行打包。

function bundle(files) {
  const output = [];
  for (const file of files) {
    output.push(`// ${file}`);
    output.push(fs.readFileSync(file, "utf-8"));
  }
  return output.join("\n");
}

5. 输出结果

将打包后的结果输出到指定文件。

const outputFile = "bundle.js";
fs.writeFileSync(outputFile, bundle(sortedFiles));

总结

手写 rollup,不仅加深了对 rollup 打包工具的工作原理以及模块打包流程的理解,也为自定义的构建配置以及打包优化积累了经验。