Poprzednia dekada przyniosła programistom rozkwit narządzi typu "bundler". W okolicach roku 2010 można było jeszcze spotkać, że pliki strony czy appek webowych były przygotowywane ręcznie przez programistów na produkcje. W roku 2021 żaden chyba profesjonalny developer nie robi manualnie takich procesów jak minimalizacja js, css, konwertowanie js w es6 na es5 czy innych transformacji związanych w tworzeniu tak zwanego "bundle" produkcyjnego.
Narzędzia takie jak grunt, gulp przyczyniły się do rozwoju obecnie chyba najbardziej wykorzystywanego "bundlera" czyli webpack'a. Webpack i familia to naprawdę potężna i dobrze działająca machina. Na pewno dobry wybór nawet i do corporate level app, z dobrą dokumentacją oraz bardzo dużym ekosystemem.
Webpack jako "stary wół" jest jednak czasem dość powolny, następne iteracje webpacka wydają się nie wpływać na jego performance, a wręcz przeciwnie jego performance spada tak jak to sugeruje strona główna esbuild.
Enter the esbuild

Natrafiłem na esbuild'a już jakiś czas temu, nowej generacji "bundler" o "niesamowitych" osiągach. Jako że w tamtym czasie był dopiero na początku swojej drogi, nie poświeciłem mu zbytnio dużo czasu. Pracując ostatnio nad optymalizacją buildu (webpack), w konfiguracji loadera plików ts podmieniłem ts-loader na esbuild-loader, 40%-50% szybszy build time okazał się dużym zaskoczeniem.
Zachęcony wynikami, postanowiłem sprawdzić czy da rade porzucić webpacka na esbuild i jak rożni się praca pomiędzy młodym zawodnikiem (esbuild) a wysłużonym weteranem (webpack v5).
Na cel objąłem full stackowy setup node.js (express) + react.
Pierwsze kroki przygotowania bundle poszły dość bezproblemowo, dość ogarnięta dokumentacja esbuilda pomaga. Esbuild integruje się z TS bez zbędnych pluginów, works out of the box wraz z pobieraniem aliasów z tsconfig.json (np @components)
const { build } = require('esbuild');
const fs = require('fs-extra');
const postCss = require('esbuild-plugin-postcss2').default;
const autoprefixer = require('autoprefixer');
const tailwindcss = require('tailwindcss');
const generateBuild = async () => {
await fs.rmdirSync('./build/public/static', { recursive: true });
await build({
// Bundles JavaScript.
bundle: true,
sourcemap: true,
loader: { '.svg': 'dataurl', '.png': 'dataurl' },
// Defines env variables for bundled JavaScript; here `process.env.NODE_ENV`
// is propagated with a fallback.
define: {
'process.env.NODE_ENV': JSON.stringify(
process.env.NODE_ENV || 'development',
),
},
// Bundles JavaScript from (see `outfile`).
entryPoints: ['./src/client/index.tsx'],
// Uses incremental compilation (see `chokidar.on`).
incremental: true,
// Removes whitespace, etc. depending on `NODE_ENV=...`.
minify: true,
// Bundles JavaScript to (see `entryPoints`).
outdir: './build/static/',
plugins: [
postCss({
plugins: [autoprefixer, tailwindcss],
}),
],
}).catch(() => process.exit(1));
process.exit(0);
};
generateBuild();
(async () => {
// `esbuild` bundler for JavaScript / TypeScript.
await build({
// Bundles JavaScript.
bundle: true,
sourcemap: false,
// set platform for node, default is browser
platform: 'node',
loader: { '.svg': 'dataurl', '.png': 'dataurl' },
// Defines env variables for bundled JavaScript; here `process.env.NODE_ENV`
// is propagated with a fallback.
define: {
'process.env.NODE_ENV': JSON.stringify(
process.env.NODE_ENV || 'Production',
),
},
// Bundles JavaScript from (see `outfile`).
entryPoints: ['./src/server/index.ts'],
// Uses incremental compilation (see `chokidar.on`).
incremental: true,
// Removes whitespace, etc. depending on `NODE_ENV=...`.
minify: true,
// Bundles JavaScript to (see `entryPoints`).
outdir: './build/',
}).catch(() => process.exit(1));
process.exit(0);
})();
Webpack-dev-server ?
Ale praca "na projekcie" to nie sam build, o ile nodemon w sprawie serwera daje sobie spokojnie radę, to pytanie co z HMR, Live relead dla Reacta ? Esbuild może jest i szybki, ale żaden z devów nie chce klikać refresh co chwilę, dodatkowo gubiąc stan komponentów.
Niestety brakuje esbuildowi rozsądnego rozwiązania czy pluginu z community w temacie live developmentu, na pewno brakuje tutaj jakieś wskazówki, "oficjalnej" konkretnej ścieżki jak można by było osiągnąć hot refreash, ja w końcu zdecydowałem się na bibliotekę "servor", rozgrzewa się na pewno trochę dłużej niż webpack-dev-server ale zdaje egzamin w sprawie live reloadu.
esbuild jest na razie bardzo eksperymentalnym projektem, z bardzo ograniczonym ekosystemem pluginów. Brakuje mu dużo stabilności i dojrzałości, jest jednak nadzieja na nowej generacji bundler, który jeszcze bardziej przyczyni się do poprawy wydajności pracy programisty w przyszłości.
Do tego czasu, na razie proponuje jednak spróbować esbuild-loader w waszych ustawieniach webpacka.
Przykładowe repo z ustawieniem esbuild only