|
| 1 | +# Bundle Analyzer |
| 2 | + |
| 3 | +For the next example, let's install and use a bundle analyzer to inspect the internal content of our bundles. |
| 4 | + |
| 5 | +📌 We start from sample `10-code-splitting`. |
| 6 | + |
| 7 | +# Steps to build it |
| 8 | + |
| 9 | +## Prerequisites |
| 10 | + |
| 11 | +Install [Node.js and npm](https://nodejs.org/en/) (20.19.0 || >=22.12.0) if they are not already installed on your computer. |
| 12 | + |
| 13 | +> ⚠ Verify that you are running at least latest Node LTS version and npm. You can check your current version by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. |
| 14 | +
|
| 15 | +## Steps |
| 16 | + |
| 17 | +- We start from `10-code-splitting`. Just copy the project and install: |
| 18 | + |
| 19 | + ```bash |
| 20 | + npm install |
| 21 | + ``` |
| 22 | + |
| 23 | +- Before we proceed with this tool, here goes a brief introduction why they are so useful and widely used: |
| 24 | + |
| 25 | + > ℹ️ A bundle analyzer or visualizer is a tool that help us to understand the contents of bundles generated by our bundler. It visually displays how modules and dependecies are distributed within the bundle, showing its relationships and the size of each package, allowing us to: |
| 26 | + > |
| 27 | + > - Reduce the final bundle size. |
| 28 | + > - Detect unnecessary or too heavy dependencies. |
| 29 | + > - Improve load times and user experience. |
| 30 | + > |
| 31 | + > These tools are critical to manually increase application performance before we commit to production, and, therefore, they are usually applied over production bundles only. |
| 32 | + > |
| 33 | + > There are different libraries out there, like `vite-bundle-analyzer`, `rollup-plugin-visualizer`, etc. We will use the first one for this sample. |
| 34 | +
|
| 35 | +- These tools are usually plug and play, so their usage is as simple as installing it first: |
| 36 | + |
| 37 | + ```bash |
| 38 | + npm install vite-bundle-analyzer --save-dev |
| 39 | + ``` |
| 40 | + |
| 41 | +- And then, use it as a plugin in `vite.config.js`: |
| 42 | + |
| 43 | + _vite.config.js_ |
| 44 | + |
| 45 | + ```diff |
| 46 | + import { defineConfig } from "vite"; |
| 47 | + import checker from "vite-plugin-checker"; |
| 48 | + import react from "@vitejs/plugin-react"; |
| 49 | + import tailwindcss from "@tailwindcss/vite"; |
| 50 | + + import { analyzer } from "vite-bundle-analyzer"; |
| 51 | + |
| 52 | + export default defineConfig({ |
| 53 | + plugins: [ |
| 54 | + checker({ typescript: true }), |
| 55 | + tailwindcss(), |
| 56 | + react(), |
| 57 | + + analyzer(), |
| 58 | + ], |
| 59 | + }); |
| 60 | + ``` |
| 61 | + |
| 62 | +- Let's see what we get out-of-the-box: |
| 63 | + |
| 64 | + ```bash |
| 65 | + npm build |
| 66 | + ``` |
| 67 | + |
| 68 | + 🔎 Check how `localhost:8888` is automatically opened after the build is complete. This is the default behaviour of the package. |
| 69 | + |
| 70 | + This server renders a report showing an interactive tree map that represents what the the different bundles are made of. It shows every module proportionally to its size. |
| 71 | + |
| 72 | + You can also expand a search panel to look for specific modules and check its size under different assumptions (gzipped, brotli, etc). |
| 73 | + |
| 74 | +- We can also pass options to the analyzer like: |
| 75 | + |
| 76 | + ```diff |
| 77 | + react(), |
| 78 | + + analyzer({ |
| 79 | + + analyzerMode: "static", |
| 80 | + + openAnalyzer: false, |
| 81 | + + reportTitle: "Bundle Analysis", |
| 82 | + + fileName: "bundle-report.html", |
| 83 | + + }), |
| 84 | + ], |
| 85 | + ``` |
| 86 | + |
| 87 | + > ℹ️ These settings will make analyzer work in static mode instead of server mode. This way, an `html` document is generated with the desired name, we can open it manually, but no server is created. |
| 88 | +
|
| 89 | +- And build again: |
| 90 | + |
| 91 | + ```bash |
| 92 | + npm build |
| 93 | + ``` |
| 94 | + |
| 95 | +## Optional |
| 96 | + |
| 97 | +- Another very interesing analyzer is a rollup plugin to extract bundle statistics. It's called `rollup-plugin-bundle-stats`. Just install it: |
| 98 | + |
| 99 | + ```bash |
| 100 | + npm install rollup-plugin-bundle-stats --save-dev |
| 101 | + ``` |
| 102 | + |
| 103 | +- And now let's configure in vite config file like this: |
| 104 | + |
| 105 | + ```diff |
| 106 | + import { defineConfig } from "vite"; |
| 107 | + import checker from "vite-plugin-checker"; |
| 108 | + import react from "@vitejs/plugin-react"; |
| 109 | + import tailwindcss from "@tailwindcss/vite"; |
| 110 | + import { analyzer } from "vite-bundle-analyzer"; |
| 111 | + + import { bundleStats } from "rollup-plugin-bundle-stats"; |
| 112 | + |
| 113 | + export default defineConfig({ |
| 114 | + plugins: [ |
| 115 | + checker({ typescript: true }), |
| 116 | + tailwindcss(), |
| 117 | + react(), |
| 118 | + analyzer({ |
| 119 | + analyzerMode: "static", |
| 120 | + openAnalyzer: false, |
| 121 | + reportTitle: "Bundle Analysis", |
| 122 | + fileName: "bundle-report.html", |
| 123 | + }), |
| 124 | + + bundleStats(), |
| 125 | + ], |
| 126 | + }); |
| 127 | + ``` |
| 128 | + |
| 129 | +- Build it again: |
| 130 | + |
| 131 | + ```bash |
| 132 | + npm build |
| 133 | + ``` |
| 134 | + |
| 135 | + 🔎 Check new `bundle-stats.html` page and explore its powerfull features. |
| 136 | + |
| 137 | +- One of the advanced features of this tool is the ability to compare our current build against a baseline build. First of all, we must indicate which run is our baseline. A simple, quick way, is to set an env variable when building our baseline build: |
| 138 | + |
| 139 | + ⚡ If you are using Linux/Bash, you can run: |
| 140 | + |
| 141 | + ```bash |
| 142 | + BUNDLE_STATS_BASELINE=true npm run build |
| 143 | + ``` |
| 144 | + |
| 145 | + ⚠️ Under Window's powershell terminal, you could first set variable for the session and then build it, like: |
| 146 | + |
| 147 | + ```bash |
| 148 | + $env:BUNDLE_STATS_BASELINE="true" |
| 149 | + ``` |
| 150 | + |
| 151 | + ```bash |
| 152 | + npm run build |
| 153 | + ``` |
| 154 | + |
| 155 | + ⚠️ Close used terminal in windows to 'unset' env variable. |
| 156 | + |
| 157 | +- Now, let's do a simple exercise, let's copy paste `math.ts` module as `math2.ts`, import it dynamically from `hello.tsx` component by duplicating the button and the handler: |
| 158 | + |
| 159 | + _src/hello.tsx_ |
| 160 | + |
| 161 | + ```diff |
| 162 | + const applyOperation = async () => { |
| 163 | + const { operate } = await import("./math"); |
| 164 | + setCounter(prevCounter => operate(prevCounter)); |
| 165 | + }; |
| 166 | + |
| 167 | + + const applyOperation2 = async () => { |
| 168 | + + const { operate } = await import("./math2"); |
| 169 | + + setCounter(prevCounter => operate(prevCounter)); |
| 170 | + + }; |
| 171 | + |
| 172 | + return ( |
| 173 | + <> |
| 174 | + <h2>Hello from React</h2> |
| 175 | + <p>Api server is {config.API_BASE}</p> |
| 176 | + <p>Feature A is {config.IS_FEATURE_A_ENABLED ? "enabled" : "disabled"}</p> |
| 177 | + <p>Counter state: {counter}</p> |
| 178 | + <button |
| 179 | + className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" |
| 180 | + onClick={applyOperation} |
| 181 | + > |
| 182 | + Apply operation |
| 183 | + </button> |
| 184 | + + <button |
| 185 | + + className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" |
| 186 | + + onClick={applyOperation2} |
| 187 | + + > |
| 188 | + + Apply operation 2 |
| 189 | + + </button> |
| 190 | + ``` |
| 191 | + |
| 192 | +- Now install a new library called 'loglevel': |
| 193 | + |
| 194 | + ```bash |
| 195 | + npm install loglevel |
| 196 | + ``` |
| 197 | + |
| 198 | +- And let's use it in both math modules: |
| 199 | + |
| 200 | + _src/math.tsx_ |
| 201 | + |
| 202 | + ```diff |
| 203 | + + import log from "loglevel"; |
| 204 | + |
| 205 | + + log.warn("*** Executing lazy-loaded math chunk"); |
| 206 | + |
| 207 | + const randomBetween = (min: number, max: number) => |
| 208 | + ``` |
| 209 | + |
| 210 | + _src/math2.tsx_ |
| 211 | + |
| 212 | + ```diff |
| 213 | + + import log from "loglevel"; |
| 214 | + |
| 215 | + + log.warn("*** Executing lazy-loaded math2 chunk"); |
| 216 | + |
| 217 | + const randomBetween = (min: number, max: number) => |
| 218 | + ``` |
| 219 | + |
| 220 | +- Run again the build in a clean terminal: |
| 221 | + |
| 222 | + ```bash |
| 223 | + npm run build |
| 224 | + ``` |
| 225 | + |
| 226 | +- 🔎 Now check the stats again and see how our latest build is compared against the baseline, offering differences in a bunch of stats, mainly size, which allow us to compare any improvement or decline in optimization. |
| 227 | + |
| 228 | +- 🤯 It is specially worth mentioning that we won't see any stat related to duplicated code or duplicated modules. We would expect our library `loglevel` to be included in both chunks `math` and `math2`. However, vite is smart enough to avoid duplicates as much as possible by extracting this common library to a separate bundle. Amazing! |
0 commit comments