通过管道:前端打包器的探索

2025-06-07

通过管道:前端打包器的探索

上周,我花了很多时间埋头研究 Parcel、Rollup 和 esbuild 的文档。我一直在寻找答案。主要是因为我受够了 Webpack 缓慢且难以配置和管理。我也听了一些其他打包工具的介绍,甚至也尝试过一些,这让我知道应该存在一条更理想的解决方案。

我的捆绑需求

让我先列出我的具体需求,因为它非常具体,以至于没有一个打包器能够提供我所需的一切。现在是时候在我即将发布的反馈之前先说一下:在使用 parcel、rollup 和 esbuild 时,如果你的用例符合它们的默认模型,或者甚至符合它们提供的少量命令行参数选项,它们都是非常棒的、易于快速启动的打包器。

  • 我正在构建和捆绑.js用于.scssWordpress 主题开发的文件。
  • 我需要它来处理多个主要入口点(四个最终变成两个):
    • js/main.js– 是 JavaScript 的前端站点入口点
    • css/style.scss– 是 CSS 样式的前端站点入口点
    • js/editor.js– 是 Gutenberg 特有的 javascript(不常用,但在需要时很方便!)
    • css/editor.scss– 您猜对了,另一组特定于 Gutenberg 的 CSS,用于偶尔的视觉块和块编辑器调整。
  • 这四个入口文件应该捆绑到四个构建文件中:build/main.min.jsbuild/main.min.cssbuild/editor.min.jsbuild/editor.min.css
  • 在开发过程中,我希望任何导入的依赖项.js.scss文件更改都能触发重建,并且我希望所有这些文件以及.php文件更改都能在浏览器中触发实时重新加载。
  • 我需要能够通过以下方式安装、导入和捆绑第三方库npm
  • 我需要它比 webpack 更快(开发构建时间大约 5-10 秒)
  • 最后,我需要所有常见的好东西:最小化、源映射、转译等。

好的,以上就是我所寻找的内容,希望能够帮助 Googlebot 引导你到这里。从现在开始,我会更加简洁,直接跳到我测试过的每个打包工具的优缺点和学习心得。

Parcel 1.x(以及一点点 2.x)

优点:

  • 我从包裹开始,我已经在其他(更现代的堆栈)项目中使用过它,它确实经常可以工作,只需很少的配置甚至不需要配置。
  • 它非常快,并且具有一组非常令人愉快的命令行和节点 API 配置选项,并且有详细的文档。
  • 一开始感觉有点尴尬,但最后我还是接受了

缺点:

  • 1.x 不再被积极开发,取而代之的是 2.x(撰写本文时处于测试阶段),虽然这里有一个良好的生态系统,但少数路径最终都会找到一个旧的 Github 问题,例如“这在 parcel 1.x 中目前不可能实现,但将在 parcel 2.0 中可用”
  • CSS 中引用的资源(例如字体和图片)会自动打包到构建目录中。我目前还没有找到解决办法。
  • 当我发现更改 css 文件会导致 js 文件停止工作直到我重新保存它们时,我结束了 1.x 之旅,至于发生了什么仍然是一个谜。
  • 此外,在使用多个入口点时,即使尝试关闭它,编辑器构建文件也总是在文件名中获得哈希值,而前端文件则运行完美。
  • 我尝试了 2.x 一段时间,希望能找到解决方案,但尽管它承诺功能更强大、更具扩展性,但我发现许多新功能很难使用,而且文档很难理解。
  • 我的 2.x 之旅就此结束,因为我开始在推特上向它的创建者寻求指导,却发现我尝试做的事情根本无法实现。(我本来想创建一个插件来消除编辑器文件名中的哈希问题,但结果发现如果不发布到 npm 之类的平台就无法使用插件,而我对此毫无兴趣。)

经验教训:

  • 我确信我会在其他项目中使用 Parcel,特别是凭借parcel-bundler我所获得的节点 API 知识,我认为它是一个值得利用的很棒的 AP​​I。
  • 一开始感觉有点别扭,但最终我还是妥协了,把 scss 导入到各自的 js 文件中,因为这个功能在各个打包工具里似乎很常见。这样一来,我的入口点就从四个减少到了两个,现在我只需要处理 JavaScript 入口点了。
  • 这是我继续之前的包裹构建脚本:
const Bundler = require("parcel-bundler");

//entry files
const entries = ["./js/main.js", "./js/editor.js"];

const baseOptions = {
  outDir: "./build",
  minify: true,
  sourceMaps: true,
  watch: process.argv.includes("--watch"),
  publicUrl: "./",
  logLevel: 4,
  contentHash: false,
};

(async () => {
  const bundler = new Bundler(entries, { ...baseOptions });
  return await bundler.bundle();
})();
Enter fullscreen mode Exit fullscreen mode

Rollup.js

我感觉有点沮丧,但仍然相信我可能会在汇总中找到答案!

优点

  • 与 parcel 类似,rollup 有一个很好的命令行和节点 API 接口,我能够快速启动并运行。
  • 核心功能有点有限,但它通过许多易于安装和使用的可用插件弥补了这一缺陷。
  • 我能够让 rollup 完成我想要做的一切,但是构建时间开始增加。

缺点

  • 我最大的缺点是花了太长时间才把所有我想要的东西都弄好。我花了太长时间在 Github Issue 和 Google 上搜索。
  • 最后,我决定放弃 rollup,因为我没有看到它比 webpack 的捆绑速度有太大的提升,而且它比我使用 parcel 慢了几秒钟。

学习内容

  • 我会用我自己的rollup.config.js话来说话:
// rollup.config.js
import resolve from "rollup-plugin-node-resolve";
import commonJS from "rollup-plugin-commonjs";
import scss from "rollup-plugin-scss";
import { babel } from "@rollup/plugin-babel";
import { terser } from "rollup-plugin-terser";
import livereload from "rollup-plugin-livereload";
import fg from "fast-glob";

//custom watching plugin to get rollup to rebuild when other files get changed
const watcher = {
  name: "watch-external",
  async buildStart() {
    const files = await fg(["assets/js/**/*.js", "assets/css/**/*.scss", "**/*.php"]);
    for (let file of await files) {
      this.addWatchFile(file);
    }
  },
};

//setup the plugins for both builds
const plugins = [
  watcher, //my custom file watcher
  resolve(), //related to resolving node_module references, I think.
  commonJS({ //this allowed things like: import jquery from 'jquery' to work
    include: "node_modules/**",
  }),
  babel({ babelHelpers: "bundled" }), //transpile!
  scss({ outputStyle: "compressed" }), //minified scss
  terser(), //for minifcation
  livereload(), //for livereload
];

//notice, two entry points here, handled like a champ!
export default [
  {
    input: "js/main.js", //the style.scss is imported in this file
    output: {
      file: "build/main.min.js",
      format: "iife", //immediately invoked function expression - browser solution
      inlineDynamicImports: true,
    },
    plugins: plugins,
  },
  {
    input: "js/editor.js", //the editor scss is imported in this file, also
    output: {
      file: "build/editor.min.js",
      format: "iife",
      inlineDynamicImports: true,
    },
    plugins: plugins,
  },
];
Enter fullscreen mode Exit fullscreen mode

esbuild

虽然 rollup 运行良好,但我一直听说新兴的 esbuild 及其荒谬的构建时间,所以是时候深入研究了。

优点

  • 再次,这里有良好的文档、良好的命令行和节点体验。
  • 我的天哪,这玩意儿简直太牛了!150毫秒,伙计们!从8-10秒到150毫秒!我真以为它坏了,结果就在那里,我的构建文件完好无损。

缺点

  • 由于有多个入口点,我不得不放弃能够为构建文件指定具体名称。我曾在 Rollup 中找到了解决方法,但在 esbuild 中似乎运气不佳。所以,build/main.min.js我只能用build/main.js. 来处理了。
  • esbuild 还很年轻,虽然运行良好,但周围的生态系统感觉有些混乱。我有一种预感,如果我之前没有经历过 Parcel 和 Rollup 的考验,我可能会在第一眼看到 esbuild 就被吓跑。

学习内容

  • esbuild 刚刚(大约 12 天前)获得了一个watchFilesflag,它可以工作,但它似乎不允许你查看入口点之外的文件
  • 我能够拼凑chokidarbrowserSync创建我想要的监视和实时重新加载解决方案。
  • 这是我的最终 esbuild 构建脚本:
//esbuilder.js
const esbuild = require("esbuild");
const sassPlugin = require("esbuild-plugin-sass");
const chokidar = require("chokidar");
const browserSync = require("browser-sync").create();

const build = async () => {
  console.log("Building...");
  const service = await esbuild.startService();
  try {

    const timerStart = Date.now();

    // Build code
    await service.build({
      entryPoints: ["js/main.js", "js/editor.js"],
      format: "iife",
      bundle: true,
      minify: true,
      outdir: "build/",
      plugins: [sassPlugin()],
      sourcemap: true,
      //this stops esbuild from trying to resolve these things in css, may need to add more types
      external: ["*.svg", "*.woff", "*.css", "*.jpg", "*.png"],
    });

    const timerEnd = Date.now();
    console.log(`Done! Built in ${timerEnd - timerStart}ms.`);

  } catch (error) {
    console.log(error);
  } finally {
    service.stop();
  }
};

//watch it?
if (process.argv.includes("--watch")) {
  //chokidar will watch theme files for changes to trigger rebuild
  const watcher = chokidar.watch(["js/**/*.js", "css/**/*.scss", "**/*.php"]);
  console.log("Watching files... \n");

  //first build
  build();
  //build on changes
  watcher.on("change", () => {
    build();
  });

  //browserSync will trigger livereload when build files are updated
  browserSync.init({
    //TODO: make these values passed in by `npm run dev`
    port: 3334,
    proxy: "localhost:3333",
    files: ["assets/build/*"],
  });
} else {
  //no watch flag, just build it and be done
  build();
}
Enter fullscreen mode Exit fullscreen mode

奖金!星辰

Estrella 在某种程度上是对 esbuild 的包装。虽然我发现 esbuild 相当容易使用,但 estrella 的简易性更是达到了顶峰。我喜欢使用 estrella,而且我认为如果你对 esbuild 感兴趣但又觉得它有点难用,那么它可能就是你需要的起点!

结论

结论是,我很高兴了解所有这些打包器,它们运行良好,而且在不太特殊的情况下,它们都可以作为打包器选择。最后,esbuild 的速度非常快,它确实改变了开发体验,也许我们以前没有抱怨过,但既然我们知道了可能性,将来可能会开始抱怨。

文章来源:https://dev.to/walpolea/through-the-pipeline-an-exploration-of-front-end-bundlers-ea1
PREV
缩短持续交付周期的五种方法 了解持续部署 加快持续部署周期的五种方法
NEXT
JS 中的柯里化🤠 用 Ja​​vaScript 烹饪?什么?🤷‍♂️ 挑战下一关