Миграция с Webpack 4 на Webpack 5

Переход с Webpack 4 на пятую версию затрагивает не только конфигурацию, но и внутреннюю модель работы сборщика. Webpack 5 был переработан с акцентом на:

  • производительность;
  • долговременное кэширование;
  • оптимизацию tree shaking;
  • модульную федерацию;
  • отказ от автоматических polyfill Node.js;
  • улучшенную работу с asset-модулями;
  • детерминированные идентификаторы;
  • persistent cache.

Основная сложность миграции заключается в том, что многие изменения являются не синтаксическими, а концептуальными.


Минимальные требования

Webpack 5 требует:

  • Node.js >= 10.13.0;
  • обновлённые loader и plugin;
  • совместимые версии Babel, TypeScript и PostCSS-экосистемы.

Типичная установка:

npm install webpack@5 webpack-cli@4 --save-dev

Для dev server:

npm install webpack-dev-server@4 --save-dev

Обновление webpack-cli

В Webpack 4 CLI часто устанавливался неявно или использовались устаревшие команды.

Webpack 5 требует отдельный пакет:

npm install webpack-cli --save-dev

Старые команды:

webpack-dev-server

Заменяются на:

webpack serve

Пример package.json:

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

Изменения режима mode

Webpack 5 строже относится к параметру mode.

Конфигурация:

module.exports = {
  mode: 'production'
};

Без mode Webpack выводит предупреждение.

Доступные режимы:

  • development
  • production
  • none

Persistent Cache

Одно из главных нововведений Webpack 5 — файловый кэш сборки.

В Webpack 4 кэш в основном находился в памяти процесса. После перезапуска сборка начиналась заново.

Webpack 5 умеет сохранять кэш на диск:

module.exports = {
  cache: {
    type: 'filesystem'
  }
};

Преимущества:

  • ускорение повторных сборок;
  • ускорение cold start;
  • снижение нагрузки на loader;
  • ускорение rebuild.

Дополнительная настройка:

module.exports = {
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    }
  }
};

Удаление автоматических polyfill Node.js

Это наиболее болезненное изменение при миграции.

Поведение Webpack 4

Webpack 4 автоматически подключал polyfill для:

  • path
  • crypto
  • stream
  • buffer
  • process
  • util

Даже браузерные проекты могли случайно использовать Node.js API.


Поведение Webpack 5

Автоматические polyfill удалены.

Ошибка:

Module not found: Error: Can't resolve 'crypto'

означает, что пакет зависит от Node.js API.


Ручное подключение polyfill

Теперь polyfill подключаются явно.

Установка:

npm install crypto-browserify stream-browserify buffer process --save

Конфигурация:

const webpack = require('webpack');

module.exports = {
  resolve: {
    fallback: {
      crypto: require.resolve('crypto-browserify'),
      stream: require.resolve('stream-browserify')
    }
  },

  plugins: [
    new webpack.ProvidePlugin({
      Buffer: ['buffer', 'Buffer'],
      process: 'process/browser'
    })
  ]
};

Отключение polyfill

Если модуль не нужен в браузере:

module.exports = {
  resolve: {
    fallback: {
      fs: false,
      path: false
    }
  }
};

Это особенно важно для backend-oriented библиотек.


Asset Modules вместо file-loader/url-loader/raw-loader

Webpack 5 встроил обработку ресурсов напрямую в ядро.

Webpack 4

module.exports = {
  module: {
    rules: [
      {
        test: /\.png$/,
        use: ['file-loader']
      }
    ]
  }
};

Webpack 5

module.exports = {
  module: {
    rules: [
      {
        test: /\.png$/,
        type: 'asset/resource'
      }
    ]
  }
};

Типы asset-модулей

asset/resource

Создаёт отдельный файл.

{
  test: /\.png$/,
  type: 'asset/resource'
}

asset/inline

Встраивает Base64 в bundle.

{
  test: /\.svg$/,
  type: 'asset/inline'
}

asset/source

Импортирует содержимое как строку.

{
  test: /\.txt$/,
  type: 'asset/source'
}

asset

Автоматический выбор между inline и resource.

{
  test: /\.(png|jpg)$/,
  type: 'asset',
  parser: {
    dataUrlCondition: {
      maxSize: 8 * 1024
    }
  }
}

Изменения в optimization.splitChunks

Webpack 5 улучшил механизм разделения чанков.

Webpack 4

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

Webpack 5

Базовая конфигурация сохраняется, но изменились:

  • алгоритмы группировки;
  • deterministic ids;
  • tree shaking;
  • работа с runtime.

Runtime Chunk

Рекомендуется выделять runtime отдельно:

optimization: {
  runtimeChunk: 'single'
}

Это улучшает долгосрочное кэширование.


Deterministic IDs

Webpack 4 часто генерировал нестабильные идентификаторы модулей.

Webpack 5 использует deterministic ids.

optimization: {
  moduleIds: 'deterministic',
  chunkIds: 'deterministic'
}

Преимущества:

  • стабильные hash;
  • уменьшение invalidation кэша;
  • более предсказуемые сборки.

Изменения в CleanWebpackPlugin

Ранее часто использовалось:

new CleanWebpackPlugin()

Webpack 5 поддерживает встроенную очистку:

module.exports = {
  output: {
    clean: true
  }
};

Это позволяет удалить сторонний plugin.


Изменения в Dev Server

Webpack 4

devServer: {
  contentBase: './dist'
}

Webpack 5

contentBase удалён.

Используется:

devServer: {
  static: './dist'
}

Изменение hot reload

Старый способ:

devServer: {
  hot: true,
  hotOnly: true
}

Новый вариант:

devServer: {
  hot: true
}

hotOnly удалён.


Изменения в Node Configuration

Webpack 4:

node: {
  fs: 'empty'
}

Webpack 5:

resolve: {
  fallback: {
    fs: false
  }
}

Секция node практически больше не используется.


Изменения в target

Webpack 5 поддерживает новые target.

Пример:

module.exports = {
  target: 'web'
};

Другие варианты:

  • node
  • webworker
  • electron-renderer
  • es2020

Поддержка ECMAScript Modules

Webpack 5 значительно улучшил ESM.

package.json

{
  "type": "module"
}

Экспорт конфигурации через ESM

export default {
  mode: 'production'
};

Расширение fullySpecified

Webpack 5 строже относится к ESM-импортам.

Ошибка:

Can't resolve './utils'

может появляться из-за отсутствия расширения.

Решение:

resolve: {
  fullySpecified: false
}

Либо:

import './utils.js';

Tree Shaking стал агрессивнее

Webpack 5 лучше анализирует:

  • side effects;
  • re-export;
  • nested usage;
  • inner graph analysis.

sideEffects

Рекомендуется явно указывать:

{
  "sideEffects": false
}

или:

{
  "sideEffects": [
    "*.css"
  ]
}

Module Federation

Одно из крупнейших нововведений Webpack 5.

Позволяет динамически подключать модули между независимыми приложениями.

Пример:

const ModuleFederationPlugin =
  require('webpack').container.ModuleFederationPlugin;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',

      remotes: {
        shop: 'shop@http://localhost:3001/remoteEntry.js'
      }
    })
  ]
};

Изменения hash-функций

Webpack 5 изменил генерацию hash.

Новые варианты:

output: {
  filename: '[name].[contenthash].js'
}

contenthash предпочтительнее hash.


Изменения в Source Map

Рекомендуемые режимы:

Development

devtool: 'eval-cheap-module-source-map'

Production

devtool: 'source-map'

Некоторые legacy-варианты deprecated.


Изменения Loader API

Старые loader могут перестать работать.

Особенно проблемны:

  • loader с прямым доступом к internal API;
  • deprecated hooks;
  • custom parser plugin.

Обновление loader

Часто требуется обновление:

npm install babel-loader@latest css-loader@latest style-loader@latest --save-dev

Изменения Babel-конфигурации

Webpack 5 лучше работает с современными preset.

Пример:

module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: [
            '@babel/preset-env'
          ]
        }
      }
    }
  ]
}

Изменения MiniCssExtractPlugin

Старые версии plugin несовместимы.

Требуется:

npm install mini-css-extract-plugin@latest --save-dev

Изменения TerserWebpackPlugin

Webpack 5 уже включает terser.

Часто отдельная настройка больше не нужна.


Изменения Watch Mode

Webpack 5 оптимизировал file watching.

Новая конфигурация:

watchOptions: {
  ignored: /node_modules/
}

Lazy Compilation

Webpack 5 поддерживает ленивую компиляцию.

experiments: {
  lazyCompilation: true
}

Особенно полезно для больших dev-сборок.


Top-Level Await

Новая возможность:

const data = await fetch('/api/data');

Включение:

experiments: {
  topLevelAwait: true
}

Experiments API

Webpack 5 ввёл систему экспериментальных возможностей.

experiments: {
  outputModule: true
}

Output Module

Генерация ESM bundle:

output: {
  module: true
},

experiments: {
  outputModule: true
}

Изменения Public Path

Автоматический publicPath:

output: {
  publicPath: 'auto'
}

Webpack сам определяет путь загрузки chunk.


Webpack 4 → Webpack 5: типичная миграция

Старый конфиг

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',

  entry: './src/index.js',

  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },

  devServer: {
    contentBase: './dist',
    hot: true
  },

  module: {
    rules: [
      {
        test: /\.png$/,
        use: ['file-loader']
      }
    ]
  }
};

Новый конфиг

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',

  entry: './src/index.js',

  cache: {
    type: 'filesystem'
  },

  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    clean: true,
    publicPath: 'auto'
  },

  devServer: {
    static: './dist',
    hot: true
  },

  optimization: {
    moduleIds: 'deterministic',
    chunkIds: 'deterministic',
    runtimeChunk: 'single'
  },

  module: {
    rules: [
      {
        test: /\.png$/,
        type: 'asset/resource'
      }
    ]
  },

  plugins: [
    new HtmlWebpackPlugin()
  ]
};

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

Ошибка crypto/path/fs

Причина:

  • удалены автоматические polyfill.

Решение:

  • fallback;
  • browserify-polyfill;
  • замена библиотеки.

Конфликт CommonJS и ESM

Ошибка:

Must use import to load ES Module

Причина:

  • смешивание require и import.

Сломанные loader

Ошибка:

this.getOptions is not a function

Причина:

  • loader написан под Webpack 4.

Проблемы с hash

После миграции могут измениться:

  • filename;
  • chunk order;
  • cache invalidation.

Это связано с новым deterministic algorithm.


Стратегия безопасной миграции

Этап 1

Обновление зависимостей:

  • webpack;
  • webpack-cli;
  • webpack-dev-server;
  • loader;
  • plugin.

Этап 2

Удаление deprecated API:

  • contentBase;
  • file-loader;
  • url-loader;
  • node polyfill;
  • legacy plugin.

Этап 3

Включение новых возможностей:

  • filesystem cache;
  • deterministic ids;
  • runtime chunk;
  • asset modules.

Этап 4

Проверка production bundle:

  • размер;
  • tree shaking;
  • contenthash;
  • splitChunks;
  • source maps.

Производительность после миграции

Webpack 5 обычно даёт:

  • более быстрый cold build;
  • более быстрый incremental rebuild;
  • меньше invalidation;
  • лучшее tree shaking;
  • меньше duplicated chunk;
  • стабильнее caching.

На крупных проектах ускорение rebuild может достигать нескольких раз благодаря filesystem cache и новой системе графа модулей.