{"id":17818262,"url":"https://github.com/icebreaker-trash/hello-node-bundler","last_synced_at":"2025-03-18T04:30:53.374Z","repository":{"id":45525344,"uuid":"355190349","full_name":"icebreaker-trash/hello-node-bundler","owner":"icebreaker-trash","description":"使用 webpack,rollup,esbuild 来打包nodejs","archived":false,"fork":false,"pushed_at":"2023-12-15T05:47:35.000Z","size":373,"stargazers_count":12,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-28T06:58:39.099Z","etag":null,"topics":["cjs","esbuild","nodejs","nodejs-rollup","webpack"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/icebreaker-trash.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-04-06T13:01:47.000Z","updated_at":"2024-10-25T12:26:41.000Z","dependencies_parsed_at":"2024-10-27T17:19:18.390Z","dependency_job_id":"4e72f26a-4ff1-4842-8479-01354b50618b","html_url":"https://github.com/icebreaker-trash/hello-node-bundler","commit_stats":null,"previous_names":["icebreaker-trash/hello-node-bundler","sonofmagic/hello-node-bundler"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icebreaker-trash%2Fhello-node-bundler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icebreaker-trash%2Fhello-node-bundler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icebreaker-trash%2Fhello-node-bundler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icebreaker-trash%2Fhello-node-bundler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/icebreaker-trash","download_url":"https://codeload.github.com/icebreaker-trash/hello-node-bundler/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243902293,"owners_count":20366258,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cjs","esbuild","nodejs","nodejs-rollup","webpack"],"created_at":"2024-10-27T16:48:35.205Z","updated_at":"2025-03-18T04:30:53.365Z","avatar_url":"https://github.com/icebreaker-trash.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\n# Hello! Bundling for node , webpack , rollup and esbuild\n\n\u003c!-- [源码地址](https://github.com/sonofmagic/hello-node-bundler) --\u003e\n\n## 序言\n\n\u003e 古有孔子曰： 茴香豆 的 **茴** 字有**四**种写法\n\n本文主旨并不在于使用大量不同的打包工具，比较这玩意嘛，不在于多，而在于哪个可以使用更少的成本来达到我们的目的\n\n本文代码运行环境如下\n\n- nodejs 14.x (LTS)\n- [webpack](https://www.npmjs.com/package/webpack) 5.x\n- [rollup](https://www.npmjs.com/package/rollup) 2.x\n- [esbuild](https://www.npmjs.com/package/esbuild) 0.11.x\n\n打包的项目示例以 `express` 和 `koa` 为主\n\n## 正文\n\n先说一下，本篇文章打包的都是 `nodejs` 项目，不是前端页面\n\n前端页面打包成 dist 部署，很好理解, spa csr 嘛\n\n而 ssr 的 **多入口打包** 和 **服务端渲染客户端激活** 也容易理解\n\n那么为啥要打包 nodejs 项目呢？ 有必要吗 ？ 这取决与我们自身遇到的场景\n\n### 普通部署场景\n\n让我们从 nodejs 的部署开始讲起\n\n先来一张非常有名的图\n\n![heavy object](./assets/image/heavy.webp)\n\n相信无论是前端还是 nodejs 开发人员，看到这张图都会 会心一笑\n\n什么造成了这种原因? \n\n原因在于强大又门槛低的 `npm`:\n\n- npm 包自身依赖可以层层依赖,深度非常高\n- npm 包作者不按照规范，发布时传了很多垃圾进去 , 现有的 prune 算法也无法做有效的清理\n- 更不用说 `.bin/binary` , 还有一些包在安装完成的 npm hook 里去下载大文件了(说的就是你 `puppeteer` )\n\n这些都间接造成了 `node_modules` 又大又深,即使后来 npm 更新了，做了一个 flat 的结构，然而点开后，还是要滚很久（笑~）\n\n我们平常部署nodejs项目：\n- 纯 cjs runtime , 直接在线上环境拉代码，`yarn --production` , 然后直接 `node` (docker同理)\n- ts nodejs , 本地调试 `ts-node` , 线上需要 `yarn` 把 `devDependencies` 和 `dependencies` 都要安装进来，才可以 `tsc`\n\n那么，我们干脆用前端的思路去进阶一下:\n\n最基础的，就是使用 `webpack/gulp` 这类的去对 `ts nodejs` 做一层代码转化，同时整理相对应的资源\n\n然而，这种添加的 `fake compile time` 大部分，还是以自己的项目为主，很少有会去打包 `node_modules` 的\n\n这当然有考量，毕竟 `node_modules` 里的东西不可控， 除了 `js` ,里面还有很多其他语言的玩意和二进制文件\n\n前端还好，除了 `wasm` , 其他基本都是 js, nodejs 包就丰富多了，`cpp`,`py`,`rs`,`go` 等等，简直就是个动物园，一旦破坏了目录结构，代码在 子进程 , 文件流 等等的路径没有转化，对应文件没有处理，就很容易产生意料不到的错误。\n\n不过在部分 `node_modules` 可控的场景下，还是有必要对其进行打包\n\n### Serverless 场景\n\n通常我们部署使用的是 `layer` + `cloud function` 的方式\n\n通常，我们把 `node_modules` 打成 layer\n\n自个的业务代码做成 `cloud function` , 并做一个关联绑定\n\n在这样的场景下，就让我们的打包工具出厂，来帮助我们解决除开 `builtin-modules` 的第三方依赖了\n\n### webpack\n\n先上一个 webpack tree shaking 的[文档](https://webpack.js.org/guides/tree-shaking/#root)\n\n#### Webpack Tree shaking info\n\n\u003e Tree shaking is a term commonly used in the JavaScript context for dead-code elimination. It relies on the static structure of ES2015 module syntax, i.e. import and export. The name and concept have been popularized by the ES2015 module bundler rollup.\n\n关键句 It relies on the static structure of ES2015 module syntax\n\n这意味着 webpack 5.x 不对 cjs 模块做 tree shaking 了\n\n[简单的配置项在此](https://github.com/sonofmagic/hello-node-bundler/blob/main/scripts/webpack.config.js)\n\nwebpack 默认 nodejs inject:  \n![webpack inject](./assets/image/webpack.png)\n\n大体上和前端方面类似 (调试方面，我都开了`source-map`, 可以直接在 vscode 编译前的源码里加断点)\n\n\n### rollup\n\nRollup 里提到了一句\n\n\u003e Even though this algorithm is not restricted to ES modules, they make it much more efficient as they allow Rollup to treat all modules together as a big abstract syntax tree with shared bindings.\n\n然而在 [tree-shaking issues](https://github.com/rollup/rollup/issues?q=label%3A%22b%C2%B3+%F0%9F%8C%B3+tree-shaking%22+) 里，我也没有找到 针对 cjs 比较好的 `tree-shaking` 方案\n\nrollup 默认 nodejs inject:  \n![rollup inject](./assets/image/rollup.png)\n\n从图上可见，所有的包，经过处理之后，都有 `default` 或者在 `default` 中\n\n原因自然也是因为 `rollup` 主要的设计就是给 `esm` 用的\n\n不过我自个在对应的配置项，比较喜欢 cjs 来写，而不是官网示例的 esm\n\n这样可以方便使用 nodejs api 直接调试\n\n[配置项在此](https://github.com/sonofmagic/hello-node-bundler/blob/main/scripts/rollup.config.js)\n\n### esbuild\n\n之前在 `umi` 和 `vite` 已经体验过这位 `go` 大神了\n\nesbuild 默认 nodejs inject:  \n![esbuild inject](./assets/image/esbuild.png)\n\n[配置项在此](https://github.com/sonofmagic/hello-node-bundler/blob/main/scripts/esbuild.config.js)\n\n从图上看也是很有意思的：\n```js\nvar __esm = (fn, res) =\u003e{\n  return () =\u003e {\n    return (fn \u0026\u0026 (res = fn(fn = 0)), res);\n  } \n}\nvar __commonJS = (cb, mod) =\u003e{\n  return () =\u003e {\n    return (mod || cb((mod = {exports: {}}).exports, mod), mod.exports);\n  } \n} \n```\n从生成出来的代码来看, 通过这两个方法对引入的模块，进行标记加闭包处理\n\n- esm =\u003e init_[module-name]\n- cjs =\u003e require_[module-name]\n\n而产生的新的函数，则是真正的导入方法\n\n同时，在代码中，假如使用 ES6 的方式导入的话，还会对模块做一次 `__toModule` 的处理\n```js\nvar __toModule = (module2) =\u003e {\n  return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, \"default\", module2 \u0026\u0026 module2.__esModule \u0026\u0026 \"default\" in module2 ? {get: () =\u003e module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);\n};\n```\n\n相当于给包打扮打扮，说我就是 esm 包\n\n而使用 cjs 导入，就没有这一层的步骤\n\n值得一说的还有 `esbuild` 对 `import()` 的处理, 默认是做成 `inline` 的\n\n```js\n// esbuild\nvar dynamic_exports = {}\nif (condition) {\n  Promise.resolve().then(() =\u003e (init_dynamic(), dynamic_exports)).then(function(m) {\n    // do some thing\n  });\n}\n// rollup \nif (condition) {\n  Promise.resolve().then(function () { return require('./dynamic-[hash:8].js'); }).then(function (m) {\n    // do some thing\n  });\n}\n\n// webpack \nif (condition) {\n  __webpack_require__.e(/*! import() */ \"src_common_dynamic_js\").then(__webpack_require__.bind(__webpack_require__, /*! ./dynamic.js */ \"./src/common/dynamic.js\")).then(function (,) {\n    // do some thing\n  })\n}\n```\n\n当然这也毕竟，在 esbuild 的官方文档上的 [`Splitting`](https://esbuild.github.io/api/#splitting) 章节\n\n\u003e Code splitting is still a work in progress. It currently only works with the esm output format. There is also a known ordering issue with import statements across code splitting chunks. You can follow the tracking issue for updates about this feature.\n\n个人还是很看好这个项目的长期发展的。\n\n## Footer\n\n在这个示例里 `webpack` , `rollup` , `esbuild` 配置项都非常简单\n\n在这里为了阅读起来方便，也没有加 babel / ts 这些玩意和静态资源的处理\n\n本文也只是初步介绍一下，这三样工具打包nodejs的方式，有兴趣的可以多交流一下。\n\n附之前写的一篇 rollup 打包微信云开发的一篇文章：\n\n[抛砖引玉：一种改善微信云开发 , 开发者体验的思路](https://developers.weixin.qq.com/community/develop/article/doc/000eaa5bb9ca305142cb8a2d95b013)\n## 附录\n\n- [源码地址](https://github.com/sonofmagic/hello-node-bundler)\n- [可参考的common-shake](https://github.com/indutny/common-shake)\n\n\n\u003c!-- \u003e ps: 当前版本的 esbuild 对 dynamic imports 默认是做 inline 处理的 --\u003e\n\u003c!-- ## Q\u0026A\n\nQ: 为啥没有 gulp / grunt / browserify  \nA: 感觉这些都是时代的眼泪\n\n\nQ: 你个人推荐用什么打包 nodejs  \nA: 哪个喜欢用哪个，看具体需求吧，自个玩可以使用 esbuild --\u003e\n\n\n\n\n\n\u003c!-- 这直接导致，遇到一些 子进程的执行，或者文件流路径不对，会出错 --\u003e\n\n\n\n\u003c!-- devDependencies 和 dependencies 依赖问题，有\n\n```shell\nnpm i --production\n//or\nyarn --production\n``` --\u003e\n\n\u003c!-- 可以在普通安装之后，自动把之前的 devDependencies 依赖给干掉 , 但是还是治标不治本\n\n很多 commonjs 的包都有 SideEffect , 在引入的时候会执行代码，导致一些问题 --\u003e\n\u003c!-- \n### 打包\n\n打包 nodejs 有很多的缺点,最大的问题就在于，破坏了目录结构，\n这直接导致，遇到一些 子进程的执行，或者文件流路径不对，会出错\n\n好处当然有很多，比如你再也见不到 node_modules 里，有些人上传的 markdown 啊，还有一些乱七八糟的配置文件和 LICENSE 了\n\n而且代码上也可以加混淆，反正有 source-map 调试也一点问题也没有。 --\u003e\n\n\n\u003c!-- 所以我个人还是没有理解，一些朋友\n\n[webpack-common-shake?](https://github.com/indutny/webpack-common-shake)\n问题还是：\n\n- node_modules 里垃圾太多 --\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficebreaker-trash%2Fhello-node-bundler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ficebreaker-trash%2Fhello-node-bundler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficebreaker-trash%2Fhello-node-bundler/lists"}