Webpack отвечает за построение графа зависимостей, упаковку модулей и оптимизацию сборки, тогда как Babel занимается преобразованием Javascript-кода. Основная задача Babel — транспиляция современного синтаксиса Javascript в код, поддерживаемый старыми браузерами или средами выполнения.
Связка Webpack + Babel решает сразу несколько задач:
Внутри Webpack Babel обычно используется через
babel-loader.
Пример базовой интеграции:
npm install --save-dev babel-loader @babel/core @babel/preset-env
Конфигурация:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
}
};
Babel состоит из нескольких уровней:
Пакет:
@babel/core
Выполняет:
Без ядра остальные компоненты работать не могут.
Пресет представляет собой набор готовых Babel-плагинов.
Например:
@babel/preset-env
включает множество трансформаций ES6+.
Пресеты позволяют не подключать плагины вручную по одному.
Плагин выполняет конкретную трансформацию:
Плагины подключаются точечно.
Babel поддерживает несколько вариантов конфигурации.
.babelrc{
"presets": ["@babel/preset-env"]
}
babel.config.jsonПодходит для крупных проектов и monorepo.
{
"presets": ["@babel/preset-env"]
}
package.json{
"babel": {
"presets": ["@babel/preset-env"]
}
}
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
Такой подход используется реже, поскольку отдельный файл Babel удобнее поддерживать.
@babel/preset-env@babel/preset-env — главный пресет Babel.
Он автоматически определяет:
Это позволяет не подключать десятки плагинов вручную.
Установка:
npm install --save-dev @babel/preset-env
Конфигурация:
{
"presets": ["@babel/preset-env"]
}
Секция targets определяет поддерживаемые окружения.
Пример:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"chrome": "90",
"firefox": "88"
}
}
]
]
}
{
"targets": {
"node": "18"
}
}
Babel умеет использовать настройки Browserslist.
Пример:
{
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
Webpack, Babel и Autoprefixer могут использовать один и тот же список браузеров.
Современный синтаксис и современные API — разные вещи.
Babel преобразует синтаксис:
const fn = () => {};
но не добавляет API вроде:
Promise
Map
Set
Array.from
Для этого используются полифилы.
Установка:
npm install core-js
useBuiltIns: "usage"{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}
Babel автоматически подключит только необходимые полифилы.
Например:
const arr = Array.from(document.querySelectorAll('div'));
будет дополнен импортом нужного полифила.
entry{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry",
"corejs": 3
}
]
]
}
В entry-файле:
import "core-js";
import "regenerator-runtime/runtime";
usage и
entry| Режим | Поведение |
|---|---|
| usage | Анализирует код и добавляет точечные полифилы |
| entry | Подключает набор полифилов через entry |
В большинстве современных проектов используется
usage.
@babel/preset-reactПозволяет Babel понимать JSX.
Установка:
npm install --save-dev @babel/preset-react
Конфигурация:
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
Исходный код:
const element = <h1>Hello</h1>;
После Babel:
const element = React.createElement("h1", null, "Hello");
Современный React поддерживает автоматический JSX runtime.
Конфигурация:
{
"presets": [
[
"@babel/preset-react",
{
"runtime": "automatic"
}
]
]
}
Теперь импорт React не требуется:
export default function App() {
return <div>Hello</div>;
}
@babel/preset-typescriptBabel умеет удалять TypeScript-аннотации.
Установка:
npm install --save-dev @babel/preset-typescript
Конфигурация:
{
"presets": [
"@babel/preset-env",
"@babel/preset-typescript"
]
}
Webpack:
{
test: /\.ts$/,
exclude: /node_modules/,
use: 'babel-loader'
}
Babel:
Но Babel не выполняет:
Для проверки типов обычно используется:
tsc --noEmit
или:
fork-ts-checker-webpack-plugin
Пример:
npm install --save-dev @babel/plugin-transform-runtime
Конфигурация:
{
"plugins": [
"@babel/plugin-transform-runtime"
]
}
Babel может вставлять вспомогательные функции в каждый файл:
function _extends() {}
function _classCallCheck() {}
При большом проекте это увеличивает размер бандла.
Установка:
npm install --save @babel/runtime
npm install --save-dev @babel/plugin-transform-runtime
Конфигурация:
{
"plugins": [
"@babel/plugin-transform-runtime"
]
}
Теперь helper-функции импортируются централизованно.
Некоторые возможности языка ещё не входят в официальный стандарт ECMAScript.
Babel позволяет использовать их заранее.
Установка:
npm install --save-dev @babel/plugin-proposal-class-properties
Пример:
class User {
name = 'Alex';
}
Установка:
npm install --save-dev @babel/plugin-proposal-decorators
Конфигурация:
{
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
Современный Babel уже включает поддержку через preset-env.
Пример:
const city = user?.address?.city;
Трансформируется в безопасные проверки.
const value = input ?? 'default';
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
},
resolve: {
extensions: ['.js', '.jsx']
}
};
Транспиляция больших проектов может занимать значительное время.
{
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
Webpack начнёт сохранять кеш Babel.
Повторные сборки существенно ускоряются.
Обычно кеш располагается:
node_modules/.cache/babel-loader
Большинство npm-пакетов уже поставляется в совместимом виде.
Поэтому:
exclude: /node_modules/
значительно ускоряет сборку.
Некоторые библиотеки публикуются в современном синтаксисе:
node_modules/some-esm-package
Тогда используется selective transpilation.
{
test: /\.js$/,
exclude: /node_modules\/(?!some-esm-package)/,
use: 'babel-loader'
}
Webpack и Babel умеют сохранять связь между исходным кодом и итоговым bundle.
Конфигурация:
module.exports = {
devtool: 'source-map'
};
Дополнительная настройка обычно не требуется.
Webpack автоматически объединяет source maps.
Babel сам по себе не минифицирует код.
Минификация обычно выполняется:
TerserWebpackPlugin
Плагин:
npm install --save-dev babel-plugin-transform-remove-console
Конфигурация:
{
"env": {
"production": {
"plugins": [
"transform-remove-console"
]
}
}
}
Babel позволяет использовать отдельные конфигурации.
Пример:
{
"presets": ["@babel/preset-env"],
"env": {
"development": {
"sourceMaps": true
},
"production": {
"comments": false
}
}
}
Webpack:
NODE_ENV=production webpack
Babel автоматически выберет production-конфигурацию.
Некоторые трансформации могут ломать tree shaking.
Особенно это касается преобразования ES Modules в CommonJS.
Правильная настройка:
{
"presets": [
[
"@babel/preset-env",
{
"modules": false
}
]
]
}
Теперь Webpack сможет выполнять tree shaking.
Webpack использует:
import('./module.js')
для code splitting.
Babel должен сохранить такой синтаксис.
Новые версии Babel корректно работают с dynamic import без дополнительных плагинов.
Webpack передаёт файл в babel-loader.
Далее Babel:
Плагины выполняются раньше пресетов.
Пресеты применяются снизу вверх.
Пример:
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
Порядок выполнения:
В monorepo может быть несколько пакетов:
packages/
frontend/
backend/
shared/
Для monorepo обычно используется корневая конфигурация:
module.exports = {
presets: ['@babel/preset-env']
};
module.exports = function(api) {
api.cache(true);
return {
presets: ['@babel/preset-env']
};
};
Позволяет кешировать результат конфигурации Babel.
Если tree shaking перестал работать, причиной часто становится:
{
"modules": "commonjs"
}
Webpack хуже оптимизирует CommonJS.
Ошибка:
import "core-js";
и одновременно:
"useBuiltIns": "usage"
может привести к дублированию.
Decorators чувствительны к порядку плагинов.
Обычно:
{
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"@babel/plugin-proposal-class-properties"
]
}
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 0.25%, not dead",
"useBuiltIns": "usage",
"corejs": 3,
"modules": false
}
],
[
"@babel/preset-react",
{
"runtime": "automatic"
}
]
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
module.exports = {
mode: 'production',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
}
]
}
};