До появления современных сборщиков JavaScript-проектов фронтенд-разработка строилась вокруг набора разрозненных утилит. HTML, CSS и JavaScript подключались вручную, зависимости копировались между папками, минификация выполнялась отдельными программами, а оптимизация изображений зачастую происходила вне основного процесса разработки.
Типичная структура проекта начала 2010-х годов выглядела примитивно:
project/
├── css/
├── js/
├── img/
├── vendor/
└── index.html
При увеличении размеров приложения возникали проблемы:
Разработчики начали искать способы автоматизации рутинных задач. Именно тогда появились инструменты task runner-класса.
Grunt стал одним из первых массово используемых инструментов автоматизации фронтенд-разработки. Основная идея заключалась в последовательном выполнении задач:
Вся логика описывалась в конфигурационном файле:
module.exports = function(grunt) {
grunt.initConfig({
uglify: {
build: {
src: 'src/app.js',
dest: 'dist/app.min.js'
}
}
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('default', ['uglify']);
};
Ключевая особенность Grunt заключалась в декларативной конфигурации. Разработчик описывал:
Каждая задача являлась отдельным плагином:
npm install grunt-contrib-uglify --save-dev
Экосистема быстро разрослась:
Несмотря на революционность подхода, Grunt имел серьёзные ограничения.
Grunt активно использовал промежуточные файлы:
src → temp → dist
После каждой операции данные записывались на диск, что значительно замедляло сборку.
Со временем Gruntfile.js превращался в монолит:
concat: {
options: {
separator: ';'
},
dist: {
src: [
'src/file1.js',
'src/file2.js',
'src/file3.js'
],
dest: 'dist/bundle.js'
}
}
При масштабировании проекта конфигурация становилась трудно поддерживаемой.
Grunt работал как task runner, а не как модульный сборщик.
Он не понимал:
requireimportПо сути, Grunt лишь выполнял операции над файлами.
Gulp появился как реакция на недостатки Grunt.
Основные цели:
Главная идея Gulp — работа через Node.js Streams.
Вместо промежуточных файлов данные передавались напрямую между обработчиками:
src → transform → minify → dest
Пример задачи:
const gulp = require('gulp');
const uglify = require('gulp-uglify');
gulp.task('scripts', function() {
return gulp.src('src/*.js')
.pipe(uglify())
.pipe(gulp.dest('dist'));
});
Конфигурация стала значительно короче:
gulp.task('styles', function() {
return gulp.src('src/scss/*.scss')
.pipe(sass())
.pipe(cssmin())
.pipe(gulp.dest('dist/css'));
});
Потоковая модель уменьшила количество операций записи на диск.
Gulp фактически являлся программируемым task runner’ом.
Можно было использовать:
Несмотря на прогресс, фундаментальные проблемы оставались.
Gulp всё ещё работал с файлами, а не с зависимостями приложения.
Например:
import React from 'react';
Для Gulp это был просто текст внутри файла.
Появление:
кардинально изменило требования к сборке.
Приложения стали:
Task runner-подход перестал справляться.
До Webpack важнейшую роль сыграл Browserify.
Он впервые популяризировал идею:
использовать CommonJS-модули в браузере.
Пример:
const math = require('./math');
console.log(math.sum(1, 2));
Browserify анализировал зависимости и собирал единый bundle.
Хотя Browserify решил проблему модульности JavaScript, он всё ещё был ограничен:
Постепенно стало очевидно, что нужен более универсальный инструмент.
Webpack изменил подход к фронтенд-сборке.
Ключевая концепция:
Всё является модулем.
Webpack начал воспринимать как модули:
Главное архитектурное отличие Webpack — построение графа зависимостей.
Пример:
import './styles.css';
import logo from './logo.png';
import App from './App';
Webpack анализирует:
index.js
├── styles.css
├── logo.png
└── App.js
Затем рекурсивно строит полный dependency graph.
Это стало фундаментальным отличием от Grunt и Gulp.
Grunt и Gulp работали на уровне файлов.
Webpack работает на уровне приложения.
Он понимает:
Одной из главных инноваций стала система loader’ов.
Пример:
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
};
Webpack способен преобразовывать любой ресурс.
Webpack идеально интегрировался с:
Babel
Пример:
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
}
Это позволило использовать:
Webpack идеально подошёл под архитектуру Single Page Application.
Он обеспечил:
Одной из важнейших возможностей стал HMR.
Изменение компонента:
export default function Button() {
return <button>Click</button>;
}
могло обновляться без полной перезагрузки страницы.
Это радикально ускорило разработку.
Подход:
task → task → task
Фокус:
Подход:
stream → transform → output
Фокус:
Подход:
dependency graph → bundle
Фокус:
npm использовался преимущественно на backend.
Frontend-разработка часто строилась на:
Webpack превратил npm в центральный механизм фронтенд-разработки.
Теперь установка зависимости выглядела так:
npm install lodash
А использование:
import _ from 'lodash';
Webpack автоматически включал библиотеку в bundle.
Одной из важнейших инноваций Webpack стала оптимизация неиспользуемого кода.
Пример:
import { map } from 'lodash';
Webpack способен исключить неиспользуемые части библиотеки.
Это существенно уменьшило размер bundle.
До Webpack приложение часто собиралось в один гигантский файл:
bundle.js
Webpack сделал возможным автоматическое разделение кода:
import('./AdminPanel');
Результат:
main.js
admin.chunk.js
vendors.js
Webpack внедрил множество оптимизаций:
Пример:
output: {
filename: '[name].[contenthash].js'
}
Изменение файла приводит к новому hash.
Браузер эффективно использует cache.
Webpack получил огромную plugin-экосистему.
Популярные плагины:
Webpack стал стандартом де-факто для:
Несмотря на доминирование, Webpack оказался очень сложным инструментом.
Типичный production-конфиг:
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader'
}
]
},
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
Для новичков конфигурация была крайне сложной.
При огромном количестве модулей сборка могла занимать десятки секунд и даже минуты.
После Webpack начали появляться новые решения:
Каждый новый инструмент пытался решить проблемы Webpack:
Несмотря на конкуренцию, Webpack остаётся одним из важнейших инструментов фронтенда.
Причины:
Эволюция инструментов сборки отражает изменение самой фронтенд-разработки.
Frontend как набор файлов.
Frontend как поток задач.
Frontend как граф взаимосвязанных модулей.
Webpack изменил не только инструменты, но и подход к архитектуре.
Появились практики:
Современные инструменты сборки фактически развивают идеи, впервые массово реализованные Webpack:
Даже инструменты нового поколения продолжают использовать концепции, которые стали популярными благодаря Webpack.