#engineering /

The Evolution of Frontend Build Tools: A Decade from Gulp to Vite

Reviewing the decade-long evolution of frontend build tools: from Grunt/Gulp task runners to Webpack module bundling to Vite/esbuild lightning-fast builds, understanding the technical choices of each era.

Goal

The evolution of frontend build tools is essentially the history of increasing frontend engineering complexity and developers' pursuit of faster build speeds. Understanding this history helps us better comprehend the logic behind current technical choices and face future changes more calmly. This article systematically reviews the build tool evolution from Gulp to Vite.

Background

Three Phases of Frontend Engineering

Phase 1 (2012-2015): Task Runner Era
  Grunt → Gulp
  Core: Automate repetitive tasks (compile, minify, concatenate)

Phase 2 (2015-2020): Module Bundler Era
  Browserify → Webpack
  Core: Handle module dependencies, code splitting, asset management

Phase 3 (2020-present): Lightning-Fast Build Era
  esbuild → Vite → Turbopack
  Core: Leverage native languages and dev servers for millisecond builds

Phase 1: Task Runners

Grunt: Configuration-Driven

Grunt was one of the earliest build tools, driven by configuration files:

// Gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
sass: {
dist: {
files: {
'dist/css/style.css': 'src/scss/style.scss'
}
}
},
cssmin: {
dist: {
files: {
'dist/css/style.min.css': 'dist/css/style.css'
}
}
},
uglify: {
dist: {
files: {
'dist/js/app.min.js': 'dist/js/app.js'
}
}
}
});
grunt.loadNpmTasks('grunt-sass');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('default', ['sass', 'cssmin', 'uglify']);
};

Issues: Verbose configuration, not intuitive, requires full task chain execution on every modification.

Gulp: Code-Driven

Gulp replaced configuration with code, based on stream processing:

// gulpfile.js
const { src, dest, series, parallel, watch } = require('gulp');
const sass = require('gulp-sass')(require('sass'));
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const uglify = require('gulp-uglify');
// Compile SCSS
function styles() {
return src('src/scss/**/*.scss')
.pipe(sass().on('error', sass.logError))
.pipe(postcss([autoprefixer(), cssnano()]))
.pipe(dest('dist/css'));
}
// Minify JS
function scripts() {
return src('src/js/**/*.js')
.pipe(uglify())
.pipe(dest('dist/js'));
}
// Watch file changes
function watchFiles() {
watch('src/scss/**/*.scss', styles);
watch('src/js/**/*.js', scripts);
}
// Export tasks
exports.styles = styles;
exports.scripts = scripts;
exports.build = parallel(styles, scripts);
exports.dev = series(parallel(styles, scripts), watchFiles);

Improvement: Code as configuration, stream processing more intuitive, supports parallel execution.

Phase 2: Module Bundlers

Browserify: CommonJS to Browser

Browserify enables Node.js CommonJS modules to run in the browser:

# Bundle CommonJS modules into browser-usable bundle
browserify src/main.js -o dist/bundle.js
// Use Node.js-style require
const _ = require('lodash');
const moment = require('moment');
console.log(_.capitalize('hello'));
console.log(moment().format());

Limitations: No ES Modules support, no code splitting, limited performance.

Webpack: The Reign

Webpack unified frontend asset management through its "everything is a module" philosophy:

// webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js',
vendor: './src/vendor.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true,
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader',
],
},
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset/resource',
},
],
},
plugins: [
new MiniCssExtractPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
optimization: {
splitChunks: {
chunks: 'all',
},
},
};

Webpack Core Concepts

Entry: Starting point of bundling
  ↓
Output: Result of bundling
  ↓
Loaders: Process non-JS files
  ↓
Plugins: Extend build functionality
  ↓
Mode: development / production

Webpack 5 Improvements

// Webpack 5 new features
module.exports = {
// 1. Module Federation
plugins: [
new ModuleFederationPlugin({
name: 'app1',
remotes: {
app2: 'app2@http://localhost:3001/remoteEntry.js',
},
}),
],
// 2. Asset Modules
module: {
rules: [
{
test: /\.(png|jpg)$/,
type: 'asset', // Automatically choose inline or resource
parser: {
dataUrlCondition: {
maxSize: 8 * 1024, // Inline under 8KB
},
},
},
],
},
// 3. Persistent caching
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
};

Pain points: Complex configuration, slow dev server startup, limited HMR efficiency.

Phase 3: Lightning-Fast Builds

esbuild: The Power of Go

esbuild is written in Go, leveraging multi-core parallelism and zero-parse overhead to achieve 10-100x speed improvements:

// esbuild configuration
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
minify: true,
sourcemap: true,
target: 'es2020',
outdir: 'dist',
loader: {
'.tsx': 'tsx',
'.ts': 'ts',
'.jsx': 'jsx',
},
});

Performance comparison (1000 files):

  • Webpack 5: ~25 seconds
  • esbuild: ~0.2 seconds

Vite: Next-Generation Build Tool

Vite was created by Evan You, the author of Vue. Its core philosophy is "no bundling during development":

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
},
},
},
},
});

Vite's Core Advantages

1. Lightning-Fast Dev Server Startup

Traditional approach (Webpack):
  Parse all modules → Build dependency graph → Compile all code → Start server
  Time: 3-10 seconds

Vite approach:
  Only process requested modules → Start server
  Time: 300-500 milliseconds

2. Instant HMR

// Vite's HMR only updates changed modules
// Webpack needs to rebuild the entire module graph
// Modify a component
// Webpack: 1-2 seconds
// Vite: 50-100 milliseconds

3. On-Demand Compilation

// Browser requests /src/App.tsx
// Vite only compiles App.tsx and its dependencies
// Other modules are compiled when needed

Vite Ecosystem

// React projects
import react from '@vitejs/plugin-react';
// Vue projects
import vue from '@vitejs/plugin-vue';
// TypeScript
// Vite natively supports TypeScript, no extra configuration needed
// CSS preprocessors
// Built-in support for Sass, Less, Stylus
// Static assets
import logo from './logo.png'; // Auto-import
import worker from './worker.js?worker'; // Worker thread

Turbopack: Webpack's Successor

Turbopack is a next-generation bundler from Vercel, written in Rust:

# Use Turbopack
npx next dev --turbo

Design goals:

  • 700x faster than Webpack
  • 10x faster than Vite
  • Gradual migration, compatible with Webpack configuration

Tool Selection Guide

Scenarios and Recommendations

| Scenario | Recommended Tool | Reason | |----------|------------------|--------| | New project (React/Vue) | Vite | Lightning-fast dev experience, mature ecosystem | | Next.js project | Turbopack | Official Next.js support | | Large enterprise project | Webpack 5 | Flexible configuration, rich ecosystem | | Library/component development | tsup (esbuild) | Lightning-fast builds, multi-format output | | Micro-frontends | Webpack 5 (Module Federation) | Mature module federation solution | | Static sites | Vite / Astro | Simple and efficient |

Performance Comparison

Test results in a project with 1000 files:

| Tool | Cold Start | HMR | Full Build | |------|------------|-----|------------| | Webpack 5 | 8.2s | 1.2s | 25.3s | | Vite 5 | 0.4s | 0.05s | 12.1s | | Turbopack | 0.3s | 0.02s | 8.5s | | esbuild | N/A | N/A | 0.2s |

Conclusion

The evolution of frontend build tools is essentially the continuous pursuit of developer experience and build performance:

  1. Gulp Era: Solved automation problems but didn't understand modules
  2. Webpack Era: Solved modularization problems but had complex configuration and slow speed
  3. Vite Era: Leveraged native languages and on-demand compilation for lightning-fast experience

When choosing build tools, don't just look at "newest" or "fastest" -- consider the team's tech stack, project scale, and ecosystem maturity. For most new projects, Vite is the current best choice; for Next.js projects, Turbopack is the future direction.

Build tools will continue to evolve, but the core philosophy won't change: letting developers focus on business code rather than build configuration.

Like this post? Tweet to share it with others or open an issue to discuss with me!