Comparison of size of
For some of the most used Javascript Framework.
For each framework is developed a small application that handles a counter and a list that can be dynamically appended and cleared by UI.
The well-known JS web frameworks benchmark conducted by Krausest focuses on CPU speed execution, Lighthouse mobile simulation, and memory allocation. While it is valuable to understand the performance of the framework we are working with or considering, an important factor to consider is the size of the final built bundle that contains minified JS code. Nowadays, most devices can easily handle complex websites. However, websites are often refreshed frequently, sometimes with every page change by the user. This raises the issue of the web application's size, as it affects page loading speed, file interpretation speed, user experience, and network traffic. It is desirable to keep the bundled website as small as possible to minimize network traffic and ensure the best possible user experience in terms of loading time. This project analyzes the sizes of the minified JS bundles for the same simple web application in some of the most commonly used JS frameworks.
React create-react-app |
React vite |
Angular | Preact Vite |
Svelte | Vue vite |
Solid | Qwik | Vanilla JS | |
---|---|---|---|---|---|---|---|---|---|
Size of built JS (KB) | 531 | 140 | 138 | 13.7 | 6.73 | 53.6 | 9.96 | 54.0 | 1.11 |
Size of dev folder (KB) | 292 511 | 102 757 | 481 411 | 70 761 | 103 418 | 107 088 | 96 100 | 168 932 | 3.6 |
Version | 18.2.0 | 18.2.0 | 14.2.3 | 10.13.1 | 3.55.1 | 3.2.45 | 1.6.10 | 0.20.1 | - |
Lower is better
Qwik is a little bit different because all js is not interpreted after the page is rendered, though the minimum bundle size remains large (42.5 KB).
Including node modules
Lower is better
In my opinion:
In any case, vanilla JS is to be avoided for any kind of projects.
Size of development folder: 91.6 MB
Contains:
Size of built app: 27.7 KB
Size of minified JS files: 9.96 KB
Components: functions that returns JSX or TSX
State: const [a, setA] = createSignal('val');
Primitives: createEffect(() => console.log(a + ' updated'));
and const aa: Type = createMemo(() => a + a;
npm i
npm run dev
npm run build
Size of development folder: 98.6 MB
Contains:
Size of built app: 9.78 KB
Size of minified JS files: 6.73 KB
Components: .svelte
file separating script, template and style
State: let a: Type = 'val';
Primitives: $: console.log(a + ' updated'));
Simplest and cleanest syntax so far
npm i
npm run dev
npm run build
Size of development folder: 102 MB
Contains:
Size of built app: 59,2 KB
Size of minified JS files: 53,6 KB
Components: .vue
file separating script, template and style. I used components API
State: let a: Type = 'val';
npm i
npm run dev
npm run build
Size of development folder: 459 MB
Contains:
Size of built app: 153 KB
Size of minified JS files: 138 KB
Components: generated folder containing 4 files, model and controler is a TypeScript class
State: a: Type = 'val';
as a class attribute (in ts file, need to access them with this
keyword)
Primitives: Not native to Angular, pretty verbose
npm i
ng s
ng build --configuration production
Size of development folder: 278 MB
Contains:
Size of built app: 540 KB
Size of minified JS files: 531 KB
Components: JSX or TSX files, function that returns JSX
State: const [a, setA] = useState<Type>('val');
as a class attribute
npm i
npm start
npm run build
npm i
npm run dev
npm run build
npm i
npm run dev
npm run build
npm i
npm start
npm run build
I used SSG (Static Site Generation) for this Qwik project
npm run qwik add
Size of development folder: 3.50 KB
Contains:
Size of built app: 3.50 KB
Size of JS: 1.11 KB
Components: import js files
State: need to work with the DOM
Primitives: need to code one's own framework
Here I put side to side all code to manage the list (creation, push, empty)
<input type="text" (change)="sendMessage($event)" />
<app-message *ngFor="let elem of list" [message]="elem"></app-message>
<button (click)="emptyList()">Empty</button>
import { Component } from '@angular/core';
import { OnChange } from 'property-watch-decorator';
export class AppComponent {
LS: string = "jsFrameworkComparison-angular-messages";
list: Message[] = [];
ngOnInit() {
this.list = JSON.parse(window.localStorage.getItem(this.LS) ?? "[]") as Message[];
}
sendMessage(event: any) {
this.list.push({ message: event.target.value, date: new Date() });
window.localStorage.setItem(this.LS, JSON.stringify(this.list));
}
emptyList() {
this.list = [];
window.localStorage.setItem(this.LS, "[]");
}
}
export type Message = {
message: string;
date: Date;
};
<script lang="ts">
import MessageComponent from "./components/MessageComponent.svelte";
const LS: string = "jsFrameworkComparison-svelte-messages";
let list: Message[] = [];
list = JSON.parse(window.localStorage.getItem(LS) ?? "[]") as Message[];
function sendMessage(event: any) {
list.push({ message: event.target.value, date: new Date() });
list = list;
window.localStorage.setItem(LS, JSON.stringify(list));
}
function emptyList() {
list = [];
window.localStorage.setItem(LS, "[]");
}
type Message = {
message: string;
date: Date;
};
</script>
<input type="text" on:change={(event) => sendMessage(event)} />
{#each list as elem}
<MessageComponent message={elem} />
{/each}
<button on:click={() => emptyList()}>Empty</button>
<script setup lang="ts">
import { ref, type Ref } from 'vue';
import MessageComponent from './components/MessageComponent.vue';
const LS: string = "jsFrameworkComparison-vue-messages";
let list: Ref<Message[]> = ref([]);
list.value = JSON.parse(window.localStorage.getItem(LS) ?? "[]") as Message[];
function sendMessage(event: any) {
list.value.push({ message: event.target.value, date: new Date() });
window.localStorage.setItem(LS, JSON.stringify(list.value));
}
function emptyList() {
list.value = [];
window.localStorage.setItem(LS, "[]");
}
type Message = {
message: string;
date: Date;
};
</script>
<template>
<input type="text" @change="(event) => sendMessage(event)" />
<MessageComponent v-for="elem in list" :message="elem" />
<button @click="() => emptyList()">Empty</button>
</template>
import { Component, createEffect, createSignal } from 'solid-js';
import MessageComponent from './components/MessageComponent';
const App: Component = () => {
const LS: string = 'jsFrameworkComparison-solid-messages';
const [list, setList] = createSignal<Message[]>([]);
setList(JSON.parse(window.localStorage.getItem(LS) ?? '[]') as Message[]);
const sendMessage = (event: any) => {
setList([...list(), { message: event.target.value, date: new Date() }]);
window.localStorage.setItem(LS, JSON.stringify(list()));
}
const emptyList = () => {
setList([]);
window.localStorage.setItem(LS, '[]');
}
return (
<div class={styles.content}>
<input type="text" onchange={(event) => sendMessage(event)} />
{list().map(elem => <MessageComponent message={elem} />)}
<button onclick={() => emptyList()}>Empty</button>
</div>
);
};
export type Message = {
message: string;
date: Date;
}
export default App;
import { useEffect, useState } from 'react';
import MessageComponent from './components/MessageComponent';
const LS: string = 'jsFrameworkComparison-react-messages';
function App() {
const [list, setList] = useState<Message[]>([]);
useEffect(() => {
setList(JSON.parse(window.localStorage.getItem(LS) ?? '[]') as Message[]);
}, []);
const sendMessage = (event: any) => {
setList([...list, { message: event.target.value, date: new Date() }]);
window.localStorage.setItem(LS, JSON.stringify(list));
}
const emptyList = () => {
setList([]);
window.localStorage.setItem(LS, '[]');
}
return (
<div className="content">
<input type="text" onBlur={(event) => sendMessage(event)} onKeyDown={(event) => event.key === 'Enter' ? sendMessage(event) : {}} />
{list.map((elem, i) => <MessageComponent key={i} message={elem} />)}
<button onClick={() => emptyList()}>Empty</button>
</div>
);
};
export type Message = {
message: string;
date: Date;
}
export default App;
import { component$, useSignal, useBrowserVisibleTask$, $, useStore } from '@builder.io/qwik';
import MessageComponent from './components/MessageComponent';
const title: string = 'Qwik app';
const LS: string = 'jsFrameworkComparison-qwik-messages';
export default component$(() => {
const list = useStore<{ value: Message[] }>({ value: [] });
const sendMessage = $((event: any) => {
list.value = [...list.value, {message: event.target.value, date: new Date()}];
window.localStorage.setItem(LS, JSON.stringify(list.value));
});
const emptyList = $(() => {
list.value = [];
window.localStorage.setItem(LS, '[]');
});
useBrowserVisibleTask$(() => {
list.value = JSON.parse(window.localStorage.getItem(LS) ?? '[]') as Message[];
});
return (
<div class="content">
<input type="text" onChange$={(event) => sendMessage(event)} />
<span>{list.value.length}</span>
{list.value.map((elem, i) => (<MessageComponent key={i} message={elem} />))}
<button onClick$={() => emptyList()}>Empty</button>
</div>
);
});
export type Message = {
message: string;
date: Date;
}
<input type="text" onchange="sendMessage(event)" />
<div id="MessageComponent"></div>
<button onclick="emptyList()">Empty</button>
MessageComponent = document.getElementById('MessageComponent');
const LS = "jsFrameworkComparison-vanilla-messages";
document.getElementById('title').innerHTML = title;
let list = [];
list = JSON.parse(window.localStorage.getItem(LS) ?? "[]");
list.forEach(m => renderMessage(m));
function sendMessage(event) {
const message = { message: event.target.value, date: new Date() };
list.push(message);
renderMessage(message);
window.localStorage.setItem(LS, JSON.stringify(list));
}
function renderMessage(message) {
MessageComponent.innerHTML += `<div class="elem">
<span class="message">${message.message}</span>
<span class="date">${message.date.toString()}</span>
</div>`;
}
function emptyList() {
list = [];
MessageComponent.innerHTML = '';
window.localStorage.setItem(LS, "[]");
}