Skip to content

Commit 22348da

Browse files
authored
Merge pull request #820 from Lemoncode/feature/vite-extended
Feature/vite extended
2 parents cc64bc7 + ef5b9db commit 22348da

12 files changed

Lines changed: 382 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
VITE_API_BASE=http://localhost:8080
2+
VITE_ENABLE_FEATURE_A=true
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
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!
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE html>
2+
<html
3+
lang="en"
4+
class="p-2 bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200"
5+
data-theme="dark"
6+
>
7+
<head>
8+
<meta charset="UTF-8" />
9+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
10+
<title>Vite App</title>
11+
</head>
12+
<body>
13+
<div id="root"></div>
14+
<script type="module" src="/src/index.tsx"></script>
15+
</body>
16+
</html>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "hello-vite",
3+
"private": true,
4+
"type": "module",
5+
"version": "0.0.0",
6+
"description": "Let's start with a very basic sample, just add an html plus a simple console log (E5). This is what you can find in the getting started tutorial.",
7+
"scripts": {
8+
"start": "vite --host",
9+
"build": "vite build",
10+
"preview": "vite preview"
11+
},
12+
"devDependencies": {
13+
"@tailwindcss/vite": "^4.1.11",
14+
"@types/react": "^19.1.8",
15+
"@types/react-dom": "^19.1.6",
16+
"@vitejs/plugin-react": "^4.6.0",
17+
"typescript": "^5.8.3",
18+
"vite": "^7.0.4",
19+
"vite-bundle-analyzer": "^1.1.0",
20+
"vite-plugin-checker": "^0.10.0"
21+
},
22+
"dependencies": {
23+
"react": "^19.1.0",
24+
"react-dom": "^19.1.0",
25+
"tailwindcss": "^4.1.11"
26+
}
27+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const config = {
2+
API_BASE: import.meta.env.VITE_API_BASE,
3+
IS_FEATURE_A_ENABLED: import.meta.env.VITE_ENABLE_FEATURE_A === "true",
4+
} as const;
5+
6+
export default config;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { FC, useEffect, useState } from "react";
2+
import config from "./env-config";
3+
4+
export const HelloComponent: FC = () => {
5+
const [counter, setCounter] = useState(0);
6+
7+
useEffect(() => {
8+
const timer = setInterval(() => {
9+
setCounter((prev) => prev + 1);
10+
}, 1_000);
11+
12+
return () => clearInterval(timer);
13+
}, []);
14+
15+
const applyOperation = async () => {
16+
const { operate } = await import("./math");
17+
setCounter((prevCounter) => operate(prevCounter));
18+
};
19+
20+
return (
21+
<>
22+
<h2>Hello from React</h2>
23+
<p>Api server is {config.API_BASE}</p>
24+
<p>Feature A is {config.IS_FEATURE_A_ENABLED ? "enabled" : "disabled"}</p>
25+
<p>Counter state: {counter}</p>
26+
<button
27+
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
28+
onClick={applyOperation}
29+
>
30+
Apply operation
31+
</button>
32+
<a
33+
href="#"
34+
className="m-2 block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow-sm hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700"
35+
>
36+
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
37+
Card title
38+
</h5>
39+
<p className="font-normal text-gray-700 dark:text-gray-400">
40+
Some quick example text to build on the card title and make up the
41+
bulk of the card's content.
42+
</p>
43+
</a>
44+
</>
45+
);
46+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createRoot } from "react-dom/client";
2+
import { HelloComponent } from "./hello";
3+
import "./styles.css";
4+
5+
const root = createRoot(document.getElementById("root"));
6+
7+
root.render(<HelloComponent />);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const randomBetween = (min: number, max: number) =>
2+
Math.floor(Math.random() * (max - min + 1)) + min;
3+
4+
export const operate = (n: number): number => {
5+
const base = Math.min(n, randomBetween(0, 50));
6+
const multiplier = randomBetween(1, 15);
7+
return base + multiplier;
8+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@import "tailwindcss";
2+
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference types="vite/client" />
2+
3+
// We'll add here our environment variables. Remember all have string values.
4+
interface ImportMetaEnv {
5+
readonly VITE_API_BASE: string;
6+
readonly VITE_ENABLE_FEATURE_A: string;
7+
}

0 commit comments

Comments
 (0)