ℹ️ Although this blog usually centers on Angular, the techniques described here apply equally to other JavaScript frameworks — and even to plain JavaScript.
In today’s web landscape, performance is essential to delivering a smooth user experience. Applications are expected to load quickly, and as modern projects grow in complexity, dependencies accumulate, often leading to oversized bundles.
Bundles are the consolidated output of your application code, produced by merging multiple JavaScript, CSS, and other assets into fewer files. This consolidation cuts down on HTTP requests, which helps speed up page loads.
Tools such as esbuild are central to this workflow, providing rapid bundling and minification that strip out unused code and shrink the overall footprint.
Still, even with robust tooling like esbuild, developers need to stay mindful of dependency management. Adding third-party packages without forethought can quickly swell bundle size, resulting in slower start times and a poorer experience for users.
Strategies to Prevent Oversized Bundles
Angular’s CLI ships with a feature called build budgets, which flags when your bundles exceed predefined size limits.
Executing ng build will surface warnings or errors in the output whenever these thresholds are breached. When that happens, it’s a cue to investigate further.
At that stage, you might review the latest commit to spot any changes responsible for the spike, or you might examine the bundles themselves to identify the main contributors to the bloat.
A number of open-source tools exist to help with bundle analysis and optimization. Historically, the Webpack Bundle Analyzer was the go-to option for many developers. But since Angular adopted esbuild in version 17, different tools are needed for effective analysis. In the esbuild ecosystem, the commonly used options include: source-map-explorer, esbuild-visualizer, and EsBuild Bundle Size Analyzer.
I’ve had the chance to test each of these in a large enterprise environment spanning more than 100 SPAs. Although all three offered useful perspectives and worked reasonably well, I found that none of them fully satisfied my requirements.
They were missing several key capabilities that I needed for the particular problems I was tackling. Without those features, it was hard to answer fundamental questions about bundle contents, performance bottlenecks, and where optimizations could be applied across such a wide-ranging setup.
Gaps in Existing esbuild Analyzers
In a large enterprise context with many SPAs, it’s typical to have shared components and services organized into reusable libraries. These could be published to NPM or maintained as Nx libraries inside a monorepo.
Developers in that environment frequently have questions regarding bundle size and structure. Drawing on my experience, the recurring and most vital questions are:
What’s the size of our eager bundle?
Knowing how much code loads at startup is essential for improving initial rendering performance.How large is our library xyz, and what is driving its size?
For shared libraries, it’s important to understand their effect on the total bundle and whether extraneous dependencies or dead code are adding weight.Where does my icon or library get bundled?
Is it placed in the eager bundle, within a lazy-loaded route, or somewhere else entirely?What is in the eager bundle, and how can we trim it?
Dissecting the eager bundle to find chances for lazy loading, tree-shaking, or other tweaks can make a real difference in performance.
These are straightforward but essential questions that require clear, concrete responses. Regrettably, the current crop of tools did not offer the level of detail and context needed to solve these problems effectively.
Most notably, they were missing two crucial features that are indispensable for thorough bundle analysis in a complex enterprise setting:
No Explicit View of the Eager Bundle
The available tools display chunks produced during the build, but they fail to give a unified, coherent picture of what truly makes up the eager bundle.
And a quick glance at themain.jsfile size isn’t enough. The eager bundle frequently encompasses more than justmain.js. In practice, I’ve seen applications wheremain.jswas merely12 KBand held little beyond references to other chunks.
esbuild leverages heuristics to optimize the main bundle, so the actual eager bundle may be distributed over several files. Without a simple way to see all the pieces of the eager bundle, pinpointing optimization opportunities becomes a challenge.Insufficient Filtering Capabilities
Among the available tools, esbuild-visualizer is the sole option with basic filtering. But for our purposes, that functionality was too limited.
We required more advanced filters that let us locate specific libraries and trace precisely where they are referenced and whether they appear in the eager or lazy sections of the app.
For example, when trying to determine where a shared library or an icon set is being pulled in, the current tools turn that into a tedious and drawn-out process. Deep filtering with exact search would be a breakthrough for effectively understanding and refining large-scale applications.
In the end, since I couldn’t find a tool that matched my needs, I decided to build one myself. Meet the newest addition to the bundle visualization space: Hawkeye! 🚀
Hawkeye — A Closer Look at the Interface
Before getting into the setup details, it’s worth taking a moment to see what the Hawkeye interface actually looks like in practice.
To showcase its functionality, I’ve generated a stats.json file from a mid-sized application with a reasonable level of complexity. While it isn’t particularly elaborate, this app provides a solid demonstration of Hawkeye’s capabilities and how it presents bundle information. Let’s jump in!

Right away, a few key figures stand out: the Eager bundle sits at 2.2 MB, while the Lazy bundle comes in at 6.9 MB. What’s particularly interesting is that the main.js file itself is only 523.6 KB. Hawkeye automatically aggregates all the pieces that belong to the main bundle, offering a comprehensive view that goes well beyond the raw size of main.js.
The interface also sorts chunks by size, starting with the largest. For instance, chunk-JDQX3ANA.js is the biggest at 1.4 MB. On the left-hand side, checkboxes let you filter and focus on specific chunks, making targeted exploration straightforward. Meanwhile, the ruler in the top-right corner aligns all chunks into a single row, enabling direct side-by-side comparisons of their sizes — a handy way to spot oversized chunks at a glance.
Hawkeye brings additional utilities as well, including the sorting arrow in the top-right, which toggles chunks between ascending and descending order by size. These features simplify the process of navigating and making sense of complex bundle datasets.
Now, let’s put Hawkeye to practical use and see what insights we can extract! 🚀
As highlighted earlier, chunk-JDQX3ANA.js stands out as the largest chunk at 1.4 MB. Let’s investigate what’s making it so hefty. The most direct approach is to simply click on the chunk. This opens a detailed breakdown, with Hawkeye dissecting the chunk to reveal which files, libraries, or assets are contributing to its overall size.
This single action can quickly expose potential issues — oversized dependencies, duplicated code, or unused assets — allowing you to zero in on optimization opportunities without much effort. Let’s dig deeper!

In this instance, it’s immediately clear that a custom icon library from my company accounts for a substantial portion of this chunk’s size. From here, we can examine each individual icon to see exactly which ones are being pulled in.

Armed with this information, we can take concrete steps — such as lazy-loading icons that aren’t used in the eager module or refining tree-shaking to strip out unnecessary code.
Of course, there may be scenarios where those icons are genuinely required in the eager bundle. Even in that case, having this level of clarity helps pinpoint where to focus efforts and ensures decisions about bundle optimization are grounded in data. This kind of visibility proves invaluable when working with large-scale applications and shared resources.
Let’s consider another typical scenario. Suppose you maintain a UI component library and want to confirm that it’s tree-shakable. Specifically, you’d like to know which components from your library land in the main bundle and how much space they occupy there.
With Hawkeye, this kind of analysis is easy. Just enter component in the search field located in the top-right corner of the interface. The view updates instantly, flagging every instance where your UI component library appears in the bundle. You now have the ability to:
Check tree-shakability: Confirm whether only the components being used in the app are actually included in the bundle.
Measure bundle impact: See the size of each component and how it affects the main bundle or lazy-loaded chunks.
Refine usage: If unused portions of the library are present, investigate why tree-shaking didn’t eliminate them and consider fixes like adjusting exports or build settings.

In the middle or right panel of the Hawkeye interface, we can see that our components amount to 62.6 KB. However, at this point, it’s still unclear which bundle they belong to.
This is where one of Hawkeye’s handy features comes into play: click on the components. A popup appears with detailed information about the components and where they sit in the bundle hierarchy. At the top of the popup, a breadcrumb navigation displays the hierarchy and lets you move up through the bundle structure.

Following the breadcrumb, it becomes evident that the UI components are part of the main-HR7VBWDD.js file. This tells us they’re included in the eager bundle, helping us determine whether they truly belong there or if strategies like lazy loading could trim the eager bundle size.
This functionality not only clarifies placement but also makes it simple to explore and grasp the full context of your bundle structure.
Setting Up Hawkeye
Ready to try Hawkeye? The good news is that Hawkeye is completely free, so you can begin analyzing your bundles right away.
There are two primary methods for using Hawkeye:
Web App: Go to the web app and upload your stats.json file. Hawkeye processes the file and gives you a detailed, interactive view for analyzing your bundles.
Command Line / NPM Script: For a more workflow-integrated approach, install Hawkeye via npm. Running the script launches Hawkeye automatically in your browser, allowing you to visualize and explore bundles directly from your development environment.
Using Hawkeye from the Command Line
Hawkeye supports command-line usage, which makes analyzing bundles from your terminal straightforward.
npx @angular-experts/hawkeye pathToTheEsBuildMetaJsonFile
Despite the package name, don’t assume this is Angular-only — the tool works smoothly with React, Vue, or even vanilla JavaScript! All you need is an esbuild meta file, and you’re set to go. 🚀
That said, Angular holds a special place for us! ❤️ That’s why we’ve built in some extra features specifically for Angular developers. 🚀
Hawkeye in Angular Projects
For command-line tools, it’s standard practice to set up an npm script that preconfigures the necessary command. This setup can sometimes be fiddly, so we decided to handle it for you.
That’s where our init command comes in. This command automatically creates the npm script for you. With a single command, you can set everything up without needing to remember syntax or check the docs. Just run the following from your project’s root directory:
npx @angular-experts/hawkeye init
Once the setup command runs, you’ll go through a simple wizard. Follow the prompts and answer questions about your project configuration. Based on your responses, Hawkeye configures the appropriate script for your workspace.
The script builds your Angular application with the esbuild meta file (
stats.json) and named chunks. It then runsnpx @angular-experts/hawkeywith the path to the meta file, after which Hawkeye opens in your browser, ready for bundle visualization and analysis. 🚀
After setup completes, you’re all set to analyze. Simply run the generated script.
Using Hawkeye in the Browser
If you’d rather not use an npm script or the command line, that’s fine! Just visit hawkeyapp.dev and upload your stats.json file. Hawkeye will immediately visualize your bundle and deliver the insights you need. 🚀
To create the
stats.jsonfile, runng buildwith the--statsJsonoption set totrue. We also recommend enabling the--named-chunksflag set totruefor better clarity.
That’s all there is to it! Give Hawkeye a shot — it’s free and designed to make bundle optimization simpler.
If you’ve tried it, I’d love to hear how it went in the comments. And if Hawkeye proved useful, please pass it along to your fellow developers — sharing not only helps others find the tool but also gives the project more visibility, helping us improve and expand it further. 🚀
Do you enjoy the theme of the code preview? Explore our brand new theme plugin
Skol - the ultimate IDE theme
Northern lights feeling straight to your IDE. A simple but powerful dark theme that looks great and relaxes your eyes.
Build smarter UIs with Angular + AI
Angular + AI Video Course

A hands-on course showing how to integrate AI into Angular apps using Hash Brown to build intelligent, reactive UIs.
Learn streaming chat, tool calling, generative UI, structured outputs, and more — step by step.
Prepare yourself for the future of Angular and become an Angular Signals expert today!
Angular Signals Mastercalss eBook

Discover why Angular Signals are essential, explore their versatile API, and unlock the secrets of their inner workings.
Elevate your development skills and prepare yourself for the future of Angular. Get ahead today!
Do you enjoy the content and want to master Angular's brand new Signal Forms?
Angular Signal Forms: Hands-On Masterclass

Master Angular's brand new Signal-Forms through 12 progressive chapters with theory and hands-on labs.
Learn form basics, validators, custom controls, subforms, migration strategies, and more!
Understanding the Bundle Breakdown
Once Hawkeye finishes analyzing your esbuild output, it presents a comprehensive view of your bundle's composition. The tool breaks down the total size by chunk, module, and even individual dependencies, giving you a granular understanding of where every kilobyte goes.
For each chunk, Hawkeye identifies which of your source files and which third-party packages are contributing to the final output. This makes it straightforward to spot cases where a large utility library is being pulled into a critical path, or where a duplicated module is inflating a chunk that should be minimal.
Spotting Redundancies and Duplication
One of the most common performance issues in modern front-end builds is the same code ending up in multiple places. Hawkeye's analysis makes these patterns obvious by flagging modules that appear across several chunks. When you see the same source file listed under two different bundles, you can investigate whether code-splitting boundaries are set correctly or whether a shared module should be extracted into a common chunk.
The tool also highlights when multiple versions of the same package are present. This often happens when different dependencies lock in conflicting semver ranges, and esbuild has no choice but to include both variants. Hawkeye shows you exactly which packages are responsible for each version, so you can decide whether a resolve.alias or a dependency upgrade is the right fix.
Monitoring Over Time
Performance work is rarely a one-time effort. Hawkeye supports this reality by allowing you to establish a baseline snapshot of your bundle. Once that baseline is saved, subsequent analyses are compared against it, and any significant increase in size is surfaced immediately in the output.
This comparison mode is particularly useful in CI. By adding a Hawkeye step to your pipeline, you can fail a build when a newly added dependency pushes the bundle past a predefined budget. The JSON output format for this mode integrates cleanly with reporting tools, so you can track the trajectory of your bundle size across releases.
Visualizing Dependency Relationships
Beyond raw numbers, Hawkeye provides a way to inspect how modules relate to one another. The tool can generate a visual graph that shows which files depend on which, which external packages are imported where, and how the connection graph could be restructured to improve load order.
This view makes it easier to reason about refactoring opportunities. If you are considering lazy-loading a feature module or moving an import to a route handler, the dependency graph helps you estimate the impact on the overall bundle before you write any code.
Configuration and Workflow
Hawkeye is designed to slot into your existing build tooling with minimal friction. The CLI accepts a path to your esbuild metafile output, or you can pipe data to it directly. If you are already generating a metafile by setting metafile: true in your esbuild config, you are ready to use Hawkeye without changing your build process.
For projects that use Angular's ng build, Hawkeye works with the esbuild information output that Angular produces, so you can apply the same analysis to your Angular bundles without an additional build step. No matter which path you choose, the analysis runs fast enough that you can safely add it to filesystem watchers and compare outputs as you develop.
The Takeaway
Hawkeye fills a real gap in the bundler ecosystem. While esbuild gives you speed, it gives you very little visibility into what it is producing. By concentrating on the analysis side, Hawkeye gives you the detailed information you need to make informed decisions about how to trim your bundle, adjust your code-splitting strategy, or push back on a dependency that adds more weight than it is worth.
The insights you gain with Hawkeye translate directly into actions. Every chunk you see, every relationship you trace, and every baseline you compare against points to a concrete change that can make your application load faster. Pairing a fast bundler with a modern analyzer like Hawkeye means you no longer have to trade build speed for insight.
Give it a try on your next build and see where your bytes are really going.
Get notified about new blog posts
Sign up for Angular Experts Content Updates & News and you'll get notified whenever we release a new blog posts about Angular, Ngrx, RxJs or other interesting Frontend topics!
We will never share your email with anyone else and you can unsubscribe at any time!
Emails may include additional promotional content, for more details see our Privacy policy.
Responses & comments
Do not hesitate to ask questions and share your own experience and perspective with the topic
You might also like
Check out following blog posts from Angular Experts to learn even more about related topics like Modern Angular!
Angular Signal Forms: Custom Controls Without ControlValueAccessor
Build reusable Angular custom controls with FormValueControl, model(), touch events, and schema-driven validation—without writing a ControlValueAccessor.
- By Kevin Kreuzer
- @nivekcode
- Intermediate content
- Aug 12, 2026
- 7 min read
Angular Signal Forms: The Missing Create/Edit Pattern
Learn a practical Angular Signal Forms pattern for create and edit flows, with route-based mode, edit data loading, linkedSignal prefilling, submit branching, and validation context.
- By Kevin Kreuzer
- @nivekcode
- Intermediate content
- Aug 1, 2026
- 6 min read
Angular Signal Forms Essentials
Understand the core concepts behind modern Angular Forms. Learn how to create Signal Forms, wire them up in templates, use built-in and custom validators, handle cross-field validation, submit forms, and more.
- By Kevin Kreuzer
- @nivekcode
- Beginner content
- Feb 14, 2026
- 12 min read
Empower your team with our extensive experience
Angular Experts have spent many years consulting with enterprises and startups alike, leading workshops and tutorials, and maintaining rich open source resources. We take great pride in our experience in modern front-end and would be thrilled to help your business boom.
