Skip to content

JS Packaging and bundling with ESBuild #8792

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft

Conversation

hotzenklotz
Copy link
Member

@hotzenklotz hotzenklotz commented Jul 21, 2025

Transpile, package and bundle WK with ESBuild instead of webpack. Esbuild removes a lot more clutter from our node_modules resulting in smaller packages, up to 40% overall. Examples:

Antd: 1.5MB --> 660kb
Threejs: 900+kb --> 513kb

Chrome Lighthouse Performance Audit also measures clear improvements:

  • First Paint: 2.5s --> 1.5s
  • Performance Score: 69 --> 80
  • For more see comment below
Screenshot 2025-07-21 at 16 35 20

(Explore this graph interactively at https://esbuild.github.io/analyze/ with this file:
metafile.json)

I tested with the following browsers without any issues:

  • Chrome 138.0.7204.158
  • Safari 18.5 (20621.2.5.11.8)
  • Firefox 141

URL of deployed dev instance (used for testing):

Steps to test:

  • Click through WK and try that all sites still loads without error
  • Test in different browser
  • I verified that all the following stuff is being loaded over the network:
    • JS bundles
    • WASM
    • web worker code
    • image, web fonts etc

TODOs:

  • Cleanup
  • Include WASM bundling

Issues:

  • fixes #

(Please delete unneeded items, merge only when none are left open)

  • Added changelog entry (create a $PR_NUMBER.md file in unreleased_changes or use ./tools/create-changelog-entry.py)
  • Added migration guide entry if applicable (edit the same file as for the changelog)
  • Updated documentation if applicable
  • Adapted wk-libs python client if relevant API parts change
  • Removed dev-only changes like prints and application.conf edits
  • Considered common edge cases
  • Needs datastore update after deployment

Copy link
Contributor

coderabbitai bot commented Jul 21, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

This update transitions the frontend build tooling from webpack to esbuild, introducing a new esbuild configuration script and related plugins. It modifies asset inclusion in the main HTML template, updates worker creation logic, removes an obsolete polyfill, adjusts proxying for the dev server, and updates dependencies to support the new build process.

Changes

Files/Group Change Summary
app/views/main.scala.html Commented out vendors~main.css and vendors~main.js includes; updated main.js to use type="module".
esbuild_config.js, process_shim.js Added new esbuild config script and Node.js process shim for browser environments.
frontend/javascripts/test/_ava_polyfill_provider.ts Deleted obsolete AbortController polyfill file.
frontend/javascripts/viewer/view/right-border-tabs/comment_tab/comment_tab_view.tsx Changed import of useLifecycle to use default import from specific path.
frontend/javascripts/viewer/workers/comlink_wrapper.ts Updated createWorker to accept worker script URLs instead of class types; changed instantiation logic accordingly.
package.json Added esbuild-related plugins as devDependencies; updated beautiful-react-hooks version.
tools/proxy/proxy.js Replaced webpack dev server process with esbuild dev server; updated proxy handler to rewrite URLs for esbuild.

Estimated code review effort

3 (~45 minutes)

Suggested labels

frontend

Poem

A rabbit hops through bundles new,
Esbuild’s the tool, webpack adieu!
Scripts and styles now fresh and lean,
Proxy paths are squeaky clean.
With shims and plugins in the mix,
Our frontend’s ready—code that clicks!
🐇✨

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch esbuild-bundling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@hotzenklotz
Copy link
Member Author

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Jul 21, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@hotzenklotz hotzenklotz self-assigned this Jul 21, 2025
@hotzenklotz hotzenklotz added frontend dependencies Pull requests that update a dependency file labels Jul 21, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🔭 Outside diff range comments (2)
package.json (1)

75-84: Update package.json build scripts to use esbuild

An esbuild_config.js file has been added, but the following scripts in package.json still invoke webpack. To complete the migration, update them to call your esbuild configuration instead:

• scripts.build
– Replace
"node --max-old-space-size=4096 node_modules/.bin/webpack --env production"
– With something like
"node esbuild_config.js --prod"

• scripts.build-dev
– Replace
"node_modules/.bin/webpack"
– With
"node esbuild_config.js"

• scripts.build-watch
– Replace
"node_modules/.bin/webpack -w"
– With
"node esbuild_config.js --watch"

Please apply these updates in this PR (or let me know if you plan to ship them in a follow-up).

frontend/javascripts/viewer/workers/comlink_wrapper.ts (1)

52-57: Fix undefined reference to WorkerClass.

The fallback code references WorkerClass which is no longer a parameter. This will cause a runtime error in Node.js contexts.

Apply this fix:

  if (wrap == null) {
    // In a node context (e.g., when executing tests), we don't create web workers which is why
    // we can simply return the input function here.
    // @ts-ignore
-   return WorkerClass;
+   return workerUrl as any;
  }
🧹 Nitpick comments (4)
process_shim.js (1)

1-21: LGTM! Process shim correctly provides Node.js compatibility.

The implementation properly handles cross-environment compatibility by:

  • Using the standard process/browser package
  • Setting up global fallbacks for different JavaScript environments
  • Providing both named and default exports for flexibility

Consider consolidating the global assignment logic for cleaner code:

-// Make it available globally
-if (typeof globalThis !== 'undefined') {
-  globalThis.process = process;
-} else if (typeof window !== 'undefined') {
-  window.process = process;
-} else if (typeof global !== 'undefined') {
-  global.process = process;
-}
+// Make it available globally
+const globalScope = globalThis ?? window ?? global;
+if (globalScope) {
+  globalScope.process = process;
+}
frontend/javascripts/viewer/workers/comlink_wrapper.ts (1)

39-45: Update outdated webpack reference in comment.

The comment mentions webpack but this code now works with esbuild's worker plugin.

Update the comment to reflect the current esbuild context:

-// Worker modules export bare functions, but webpack turns these into Worker classes which need to be
+// Worker modules export bare functions, but esbuild's worker plugin generates URLs which need to be
 // instantiated first.
-// To ensure that code always executes the necessary instantiation, we cheat a bit with the typing in the following code.
+// To ensure that code always uses the correct worker URL, we cheat a bit with the typing in the following code.
tools/proxy/proxy.js (1)

127-135: Consider renaming function to reflect esbuild usage.

The URL rewriting logic is correct and handles the path transformation properly. However, the function name toWebpackDev is now misleading since it handles esbuild requests.

Consider renaming for clarity:

-function toWebpackDev(req, res) {
+function toEsbuildDev(req, res) {

And update the route handler on line 150:

-app.all("/assets/bundle/*", toWebpackDev);
+app.all("/assets/bundle/*", toEsbuildDev);
esbuild_config.js (1)

108-157: Fix unused parameter flagged by static analysis.

The proto plugin logic is sound, but there's an unused parameter that should be addressed.

-    root.resolvePath = (origin, target) => path.resolve(protoRoot, target);
+    root.resolvePath = (_origin, target) => path.resolve(protoRoot, target);

This addresses the static analysis warning about the unused origin parameter.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f49365 and 4008d88.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (8)
  • app/views/main.scala.html (2 hunks)
  • esbuild_config.js (1 hunks)
  • frontend/javascripts/test/_ava_polyfill_provider.ts (0 hunks)
  • frontend/javascripts/viewer/view/right-border-tabs/comment_tab/comment_tab_view.tsx (1 hunks)
  • frontend/javascripts/viewer/workers/comlink_wrapper.ts (2 hunks)
  • package.json (3 hunks)
  • process_shim.js (1 hunks)
  • tools/proxy/proxy.js (2 hunks)
💤 Files with no reviewable changes (1)
  • frontend/javascripts/test/_ava_polyfill_provider.ts
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: MichaelBuessemeyer
PR: scalableminds/webknossos#8221
File: CHANGELOG.unreleased.md:14-14
Timestamp: 2024-11-25T14:52:23.201Z
Learning: For the `webknossos` project, changelog entries in `CHANGELOG.unreleased.md` should be concise and avoid unnecessary detailed explanations.
frontend/javascripts/viewer/view/right-border-tabs/comment_tab/comment_tab_view.tsx (1)

Learnt from: knollengewaechs
PR: #8321
File: frontend/javascripts/components/slider.tsx:0-0
Timestamp: 2025-02-05T20:16:45.712Z
Learning: When tracking focus state that needs to be accessed by native event listeners (e.g., wheel events), use useRef instead of useState to ensure the event handlers always have access to the current value. This is because event listeners added via addEventListener would capture the initial state value if useState were used.

🪛 Biome (1.9.4)
esbuild_config.js

[error] 141-141: This parameter is unused.

Unused variables usually are result of incomplete refactoring, typos and other source of bugs.
Unsafe fix: If this is intentional, prepend origin with an underscore.

(lint/correctness/noUnusedVariables)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (12)
package.json (2)

13-13: LGTM! The esbuild plugins support the migration goals.

The added plugins provide essential functionality for the esbuild migration:

  • Worker plugin for web worker handling
  • Copy plugin for asset management
  • Less CSS preprocessing
  • Node.js polyfills for browser compatibility

Also applies to: 46-48


142-142: LGTM! Version update aligns with import style change.

The beautiful-react-hooks version update from ^3.11.1 to ^3.12 supports the import style change seen in the comment tab view file.

frontend/javascripts/viewer/view/right-border-tabs/comment_tab/comment_tab_view.tsx (1)

12-12: LGTM! Import style change aligns with dependency update.

The change from named import to direct path import (beautiful-react-hooks/useLifecycle) is consistent with the package.json update to version ^3.12 and supports better tree-shaking.

app/views/main.scala.html (2)

48-48: LGTM! Adding type="module" supports ES module usage.

The addition of type="module" to the main.js script tag correctly enables ES module loading, which aligns with modern JavaScript bundling practices.


30-35: Remove obsolete vendor asset links

Verified that the esbuild configuration bundles all dependencies into the main bundle (with splitting: true for dynamic imports) and inlines CSS imports by default—no standalone vendors~main.css or vendors~main.js is produced. You can safely delete the commented-out vendor links in app/views/main.scala.html:

• Lines 30–35: remove the <link …vendors~main.css…/> block
• Line 47: remove the corresponding <script …vendors~main.js…/> if still present

frontend/javascripts/viewer/workers/comlink_wrapper.ts (1)

50-51: LGTM! URL-based worker creation aligns with esbuild approach.

The change from class-based to URL-based worker instantiation correctly supports esbuild's worker plugin that generates worker bundle URLs.

Also applies to: 59-62

tools/proxy/proxy.js (1)

38-42: LGTM! Process spawn configuration looks correct.

The esbuild process configuration properly uses the watch flag and port argument, with appropriate environment setup.

esbuild_config.js (5)

1-24: LGTM! Dependencies and configuration setup looks solid.

The path resolution, target browsers, and plugin imports are well-structured. The commented out plugins suggest careful consideration of what's needed.


26-106: Excellent custom worker plugin implementation.

The worker plugin handles the complex task of bundling workers separately with proper error handling and logging. The approach of using virtual modules for worker URLs is elegant.

A few observations:

  • The async worker building in onEnd is well-implemented
  • Error handling propagates build failures correctly
  • IIFE format for workers is appropriate for browser compatibility

159-227: Comprehensive build configuration with good practices.

The build options are well-configured with appropriate settings for both development and production modes. The external dependencies, aliases, and loader configurations look correct.

Key strengths:

  • Proper source map configuration for dev/prod
  • Appropriate chunk naming with hashes
  • Good external dependency handling
  • Comprehensive file loaders

228-264: Excellent development server and build mode handling.

The watch mode implementation with development server and graceful shutdown is well-implemented. Production mode properly generates metafiles for analysis.

The development server logging and graceful shutdown handling are particularly well done.


268-280: Clean CLI argument parsing.

The command-line interface is simple and effective for the supported flags.

@hotzenklotz hotzenklotz changed the title WIP Packaging and bundling with ESBuild JS Packaging and bundling with ESBuild Jul 23, 2025
@hotzenklotz
Copy link
Member Author

Lighthouse Performance Audit on https://master.webknossos.xyz;

Screenshot 2025-07-24 at 10 59 26 Screenshot 2025-07-24 at 10 59 40

Lighthouse Performance Audit on https://esbuildbundling.webknossos.xyz/

Screenshot 2025-07-24 at 11 03 30 Screenshot 2025-07-24 at 11 04 01

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dependencies Pull requests that update a dependency file frontend
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant