Skip to content

🐛 fix(build): make package tree-shakeable and Node-safe via unbundle - #167

Merged
Innei merged 1 commit into
masterfrom
fix/unbundle-tree-shaking
May 29, 2026
Merged

🐛 fix(build): make package tree-shakeable and Node-safe via unbundle#167
Innei merged 1 commit into
masterfrom
fix/unbundle-tree-shaking

Conversation

@Innei

@Innei Innei commented May 29, 2026

Copy link
Copy Markdown
Member

Problem

Importing a pure function from @lobehub/editor (e.g. isPureUrl, getHotkeyById, createDebugLogger, scrollIntoView, KeyEnum) dragged the entire editor + DOM globals into the bundle and crashed in Node/SSR with a top-level document reference.

Root cause

The browser build emitted a single mega-bundle es/index.js whose top-level held unconditional side-effect calls:

init_helper(); init_hotkey(); init_plugin(); init_debug();

These come from rolldown's lazy-init wrappers for cross-chunk modules. Because the pure exports lived in the same module as those calls, using any export forced es/index.js to be included, and the top-level init_*() calls cannot be tree-shaken — so the whole editor (and document access) loaded eagerly.

Measured from the published artifact (consumer tree-shake): isPureUrl20MB, getHotkeyById7MB, both referencing document. The source itself was perfectly tree-shakeable; the build artifact was not.

Fix

  • Enable unbundle: true for the browser build group so output mirrors the source module structure. Each export becomes its own file and index.js is a plain re-export barrel — restoring per-module tree-shaking exactly like the source.
  • Declare remark, remark-gfm, remark-math as runtime dependencies (remark moved from devDependencies). They are imported by the markdown plugin but were undeclared, so unbundle mode emitted their transitive graph (micromark / mdast-util / vfile / …) as ~864KB under es/node_modules. Declaring them keeps them external.

Verification (consumer tree-shake + Node import)

Export before after
isPureUrl 20MB · top-level document · Node crash 1KB · runs in Node
getHotkeyById 7MB · crash 10KB · runs in Node
createDebugLogger 7MB · crash 5.4KB · runs in Node
es/node_modules 864KB (micromark/mdast-util/vfile) 28KB (type-only stubs)
es/ total 3.9MB 2.6MB
  • es/index.js: 250KB mega-bundle → 12.5KB re-export barrel
  • headless + markdown round-trip still works in Node
  • type-check ✅ · lint:circular ✅ (no cycles)

Notes / follow-ups

  • Residual es/node_modules (28KB) is purely @types/mdast + @types/unist (.d.ts only, self-contained). Could be removed by declaring those @types/* as dependencies if desired.
  • Browser-side end-to-end (dumi docs build) not run in this change — recommend verifying before release.

The browser build emitted a single mega-bundle (es/index.js) whose
top-level contained unconditional side-effect calls (init_helper(),
init_hotkey(), init_plugin(), init_debug()). Because pure utility
exports (isPureUrl, getHotkeyById, createDebugLogger, scrollIntoView,
KeyEnum, ...) lived in the same module as those calls, any import from
'@lobehub/editor' forced the whole bundle to evaluate, dragging in the
entire editor and DOM globals. In a Node/SSR environment this referenced
`document` at import time and crashed — a pure function import pulled
7-20MB of code.

Enable `unbundle: true` for the browser build group so output mirrors the
source module structure. Each export becomes its own file and the barrel
is a plain re-export, restoring per-module tree-shaking. Importing a pure
function now resolves to a ~1KB standalone module with no top-level DOM.

Also declare remark, remark-gfm and remark-math as runtime dependencies
(remark moved from devDependencies). They are imported by the markdown
plugin but were previously undeclared, so unbundle mode emitted their
transitive graph (micromark/mdast-util/vfile/...) as ~864KB of files
under es/node_modules. Declaring them keeps them external.

Verified (consumer tree-shake + Node import):
- isPureUrl 20MB→1KB, getHotkeyById 7MB→10KB, all run in Node
- es/node_modules 864KB→28KB (type-only stubs), es/ 3.9MB→2.6MB
- headless + markdown round-trip still works in Node
@vercel

vercel Bot commented May 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lobe-editor Ready Ready Preview, Comment May 29, 2026 7:31am

Request Review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've reviewed this pull request using the Sourcery rules engine

@pkg-pr-new

pkg-pr-new Bot commented May 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@lobehub/editor@167

commit: e57e2be

@Innei
Innei merged commit 5f61f5b into master May 29, 2026
7 of 8 checks passed
@Innei
Innei deleted the fix/unbundle-tree-shaking branch May 29, 2026 12:35
github-actions Bot pushed a commit that referenced this pull request May 29, 2026
### [Version 4.15.2](v4.15.1...v4.15.2)
<sup>Released on **2026-05-29**</sup>

#### 🐛 Bug Fixes

- **build**: Make package tree-shakeable and Node-safe via unbundle.

<br/>

<details>
<summary><kbd>Improvements and Fixes</kbd></summary>

#### What's fixed

* **build**: Make package tree-shakeable and Node-safe via unbundle, closes [#167](#167) ([5f61f5b](5f61f5b))

</details>

<div align="right">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>
@lobehubbot

Copy link
Copy Markdown
Member

🎉 This PR is included in version 4.15.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants