Миграция с Webpack

На протяжении многих лет Webpack оставался стандартом де-факто для сборки JavaScript-приложений. Его гибкость, развитая экосистема и высокая настраиваемость сделали его основой огромного количества проектов. Однако вместе с преимуществами появились и недостатки:

  • сложная конфигурация;
  • большое количество зависимостей;
  • необходимость ручной настройки загрузчиков и плагинов;
  • высокий порог входа для новых участников команды;
  • усложнение поддержки конфигураций при росте проекта.

Parcel предлагает альтернативный подход. Основная идея заключается в автоматизации большинства задач, которые в Webpack требуют отдельной настройки.

Ключевые особенности Parcel:

  • конфигурация по принципу zero configuration;
  • автоматическое определение типов файлов;
  • встроенная поддержка популярных технологий;
  • быстрый запуск разработки;
  • автоматическая оптимизация production-сборок;
  • встроенный кэш;
  • параллельная обработка ресурсов.

Миграция особенно актуальна для проектов, где конфигурация Webpack постепенно разрослась и стала сложной в сопровождении.


Анализ существующей конфигурации Webpack

Перед переносом необходимо определить, какие возможности используются в текущем проекте.

Типичная конфигурация Webpack может выглядеть следующим образом:

module.exports = {
  entry: './src/index.js',

  output: {
    filename: 'bundle.js',
    path: __dirname + '/dist'
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
};

Во время анализа следует обратить внимание на:

Точки входа

entry: './src/index.js'

или

entry: {
  app: './src/app.js',
  admin: './src/admin.js'
}

Используемые загрузчики

Чаще всего встречаются:

  • babel-loader;
  • css-loader;
  • style-loader;
  • sass-loader;
  • file-loader;
  • url-loader;
  • ts-loader.

Плагины

Например:

plugins: [
  new HtmlWebpackPlugin(),
  new MiniCssExtractPlugin()
]

Алиасы

resolve: {
  alias: {
    '@': path.resolve(__dirname, 'src')
  }
}

Переменные окружения

new webpack.DefinePlugin({
  API_URL: JSON.stringify(process.env.API_URL)
})

Code Splitting

optimization: {
  splitChunks: {
    chunks: 'all'
  }
}

Полный список используемых возможностей позволит определить объём будущей миграции.


Установка Parcel

Для начала необходимо удалить зависимости, относящиеся к Webpack.

Пример списка пакетов:

npm uninstall webpack
npm uninstall webpack-cli
npm uninstall webpack-dev-server
npm uninstall babel-loader
npm uninstall css-loader
npm uninstall style-loader

После этого устанавливается Parcel:

npm install --save-dev parcel

Или:

yarn add --dev parcel

Замена команд сборки

Webpack обычно использует такие сценарии:

{
  "scripts": {
    "start": "webpack serve",
    "build": "webpack --mode production"
  }
}

После перехода на Parcel они изменяются следующим образом:

{
  "scripts": {
    "start": "parcel src/index.html",
    "build": "parcel build src/index.html"
  }
}

Parcel рассматривает HTML-файл как корневую точку входа приложения.


Переход от entry к HTML-входу

В Webpack чаще всего используется JavaScript-файл:

entry: './src/index.js'

В Parcel рекомендуется использовать HTML:

<!DOCTYPE html>
<html>
<head>
  <title>Application</title>
</head>
<body>
  <script type="module" src="./index.js"></script>
</body>
</html>

Запуск сборки:

parcel src/index.html

Parcel самостоятельно обнаружит все зависимости и включит их в итоговый бандл.


Миграция Babel

В Webpack настройка Babel обычно выглядит так:

{
  test: /\.js$/,
  loader: 'babel-loader'
}

И сопровождается файлом:

{
  "presets": ["@babel/preset-env"]
}

Parcel автоматически обнаруживает Babel-конфигурацию.

Достаточно оставить файл:

{
  "presets": ["@babel/preset-env"]
}

или

module.exports = {
  presets: ['@babel/preset-env']
};

Дополнительная настройка загрузчиков не требуется.


Перенос CSS

В Webpack подключение CSS обычно требует нескольких загрузчиков:

{
  test: /\.css$/,
  use: [
    'style-loader',
    'css-loader'
  ]
}

В Parcel достаточно импортировать файл:

import './styles.css';

После запуска сборки:

parcel src/index.html

стили автоматически попадут в итоговый результат.


Миграция Sass

В Webpack:

{
  test: /\.scss$/,
  use: [
    'style-loader',
    'css-loader',
    'sass-loader'
  ]
}

В Parcel достаточно установить Sass:

npm install --save-dev sass

Импорт:

import './styles.scss';

Parcel автоматически активирует соответствующий трансформер.


Работа с изображениями

В старых конфигурациях Webpack часто используются:

{
  test: /\.(png|jpg|svg)$/i,
  loader: 'file-loader'
}

или:

{
  test: /\.(png|jpg)$/i,
  loader: 'url-loader'
}

В Parcel изображения импортируются напрямую:

import logo from './logo.png';

image.src = logo;

или используются в HTML:

<img src="./logo.png" alt="Logo">

Parcel самостоятельно:

  • копирует файл;
  • генерирует хэш имени;
  • обновляет ссылки;
  • оптимизирует ресурсы при production-сборке.

Перенос TypeScript

Webpack-конфигурация:

{
  test: /\.ts$/,
  loader: 'ts-loader'
}

Parcel требует только установки TypeScript:

npm install --save-dev typescript

После этого можно создавать файлы:

const message: string = 'Hello';

Parcel автоматически распознает расширение:

index.ts

и выполнит компиляцию.


Замена HtmlWebpackPlugin

Webpack:

plugins: [
  new HtmlWebpackPlugin({
    template: './src/index.html'
  })
]

Parcel не требует отдельного плагина.

Достаточно использовать HTML-файл напрямую:

parcel src/index.html

Все необходимые скрипты будут подключены автоматически.


Миграция переменных окружения

В Webpack часто используется:

new webpack.DefinePlugin({
  API_URL: JSON.stringify(process.env.API_URL)
})

В Parcel переменные доступны напрямую:

API_URL=https://api.example.com

Использование:

console.log(process.env.API_URL);

или в современных версиях:

console.log(process.env.API_URL);

Parcel самостоятельно внедрит значения во время сборки.


Настройка алиасов

Webpack:

resolve: {
  alias: {
    '@': path.resolve(__dirname, 'src')
  }
}

Parcel использует настройки в файле package.json.

Пример:

{
  "alias": {
    "@": "./src"
  }
}

Импорт:

import Button from '@/components/Button';

Замена Webpack Dev Server

Webpack:

{
  "scripts": {
    "dev": "webpack serve"
  }
}

Parcel имеет встроенный сервер разработки:

parcel src/index.html

Поддерживаются:

  • горячая перезагрузка;
  • отслеживание изменений;
  • автоматическое обновление браузера;
  • диагностика ошибок.

Дополнительные пакеты устанавливать не требуется.


Перенос Code Splitting

Webpack:

optimization: {
  splitChunks: {
    chunks: 'all'
  }
}

В Parcel применяется динамический импорт:

const module = await import('./module.js');

При сборке Parcel автоматически создаст отдельный чанк.

Пример:

button.addEventListener('click', async () => {
  const editor = await import('./editor.js');
  editor.init();
});

В результате модуль будет загружаться только при необходимости.


Миграция Web Workers

Webpack:

new Worker(
  new URL('./worker.js', import.meta.url)
);

Parcel поддерживает аналогичный синтаксис:

const worker = new Worker(
  new URL('./worker.js', import.meta.url)
);

Дополнительная конфигурация отсутствует.


Работа с несколькими страницами

Webpack обычно требует настройки множества точек входа:

entry: {
  home: './src/home.js',
  admin: './src/admin.js'
}

Parcel позволяет использовать несколько HTML-файлов:

parcel build src/home.html src/admin.html

Структура:

src/
 ├─ home.html
 ├─ admin.html
 ├─ home.js
 └─ admin.js

Каждая страница получает собственный набор ресурсов.


Production-сборка

Webpack часто содержит большое количество настроек оптимизации:

optimization: {
  minimize: true
}

Parcel выполняет автоматически:

  • минификацию JavaScript;
  • минификацию CSS;
  • оптимизацию HTML;
  • tree shaking;
  • удаление неиспользуемого кода;
  • генерацию source map;
  • хэширование файлов.

Сборка:

parcel build src/index.html

Удаление устаревшей инфраструктуры

После успешного перехода обычно становятся не нужны:

webpack.config.js

а также многочисленные зависимости:

webpack
webpack-cli
webpack-dev-server
style-loader
css-loader
file-loader
url-loader
mini-css-extract-plugin
html-webpack-plugin

Размер файла package.json заметно сокращается, а процесс поддержки сборки становится проще.


Типичные проблемы при миграции

Использование специфичных Webpack API

Некоторые проекты содержат конструкции:

require.context(...)

Parcel их не поддерживает.

Необходимо заменить на стандартные механизмы ECMAScript:

import.meta.glob(...)

или явные импорты.

Зависимость от загрузчиков

Иногда бизнес-логика завязана на возможности конкретного loader.

Например:

import template from './template.html';

Следует проверить поддержку аналогичного поведения в Parcel либо заменить архитектурное решение.

Кастомные плагины Webpack

Если проект использует собственные плагины:

class CustomPlugin {
  apply(compiler) {}
}

прямого аналога может не существовать.

В подобных случаях используются:

  • трансформеры Parcel;
  • namers;
  • packagers;
  • reporters;
  • optimizers.

Монорепозитории

В больших монорепозиториях необходимо проверить:

  • корректность разрешения зависимостей;
  • работу workspace-пакетов;
  • публикацию внутренних библиотек;
  • обработку символьных ссылок.

Стратегии миграции

Полная миграция

Подходит для небольших проектов.

Этапы:

  1. Удаление Webpack.
  2. Установка Parcel.
  3. Перенос сценариев сборки.
  4. Проверка production-сборки.
  5. Тестирование приложения.

Пошаговая миграция

Подходит для крупных систем.

Схема:

Webpack
   ↓
Экспериментальная ветка
   ↓
Parcel
   ↓
Тестирование
   ↓
Основная ветка

Такой подход позволяет постепенно устранять несовместимости.

Параллельная поддержка

На определённом этапе могут существовать обе системы сборки:

{
  "scripts": {
    "webpack-build": "webpack",
    "parcel-build": "parcel build src/index.html"
  }
}

После полного тестирования старая конфигурация удаляется.


Сравнение итоговой структуры проекта

Структура проекта с Webpack:

project/
 ├─ src/
 ├─ webpack.config.js
 ├─ .babelrc
 ├─ package.json
 └─ node_modules/

После перехода на Parcel:

project/
 ├─ src/
 ├─ .babelrc
 ├─ package.json
 └─ node_modules/

В большинстве случаев исчезает необходимость поддерживать крупный конфигурационный файл, а процесс сборки становится более декларативным и ориентированным на структуру самого приложения, а не на описание инфраструктуры сборщика.