[Перевод] 10 особенностей Webpack

Webpack считается лучшим инструментом для сборки приложений на React и Redux. Полагаю, многие из тех, кто сегодня использует Angular 2 и другие фреймворки, не обходят вниманием и Webpack. И поскольку начинать работу с данным инструментом всегда непросто, я решил посвятить этой теме несколько публикаций в надежде облегчить старт другим разработчикам и заодно продемонстрировать некоторые особенности Webpack.

85002a7c23c042c3a89ba1700240b218.jpg

Когда я впервые увидел его файл конфигурации, он показался мне крайне странным и путанным. Но спустя некоторое время я понял, что всё дело в уникальном синтаксисе Webpack и несколько иной философии, которая может поначалу немного сбить с толку. Но, с другой стороны, именно эта новая философия и делает Webpack таким популярным.

Философия Webpack

Можно выделить 2 основных принципа философии Webpack:

  1. Что угодно может быть модулем. Модулями могут быть как JS-файлы, так и CSS-файлы, HTML-файлы или изображения. То есть можно использовать и require («myJSfile.js»), и require («myCSSfile.css»). Таким образом, вы можете разбивать любой артефакт на меньшие удобоуправляемые части, использовать их повторно и так далее.
  2. Загружайте только то, что вам нужно и когда вам нужно. Обычно сборщики модулей берут все модули и генерируют из них один большой файл bundle.js. Но во многих приложениях размер такого файла может достигать 10–15 MB –, а это слишком много. Потому Webpack оснащен рядом функций, позволяющих делить код и генерировать множество bundle-файлов, а также асинхронно загружать необходимые части приложения тогда, когда это нужно.

А теперь давайте наконец перейдем к особенностям Webpack.

1. Development и production

Прежде всего нужно понять, что Webpack имеет множество функций, часть которых ориентирована на development, другая — на production, а третья — на то и на другое.

6a0541be495043fcb4c4cbd7f801dd43.png

Пример Webpack-файлов для development и production

Большинство проектов используют так много функций, что у них, как правило, есть 2 больших файла конфигурации Webpack.

Для создания бандлов вам, скорее всего, потребуется писать скрипты в package.json, примерно так:

 "scripts”: {
  //npm run build to build production bundles
  "build”: "webpack --config webpack.config.prod.js”,
  //npm run dev to generate development bundles and run dev. server
  "dev”: "webpack-dev-server”
 }

2. webpack CLI и webpack-dev-server

Важно отметить что Webpack, как сборщик модулей, предоставляет 2 интерфейса:

  1. Инструмент webpack CLI — интерфейс по умолчанию (установлен как часть самого Webpack).
  2. Инструмент webpack-dev-server — сервер Node.js (нужно устанавливать отдельно).

Webpack CLI (подходит для production-сборок)

Этот инструмент берет опции через инструмент CLI, а также через файл конфигурации (по умолчанию — webpack.config.js) и передает их в Webpack для сборки.

И хотя вы можете начать изучение Webpack, используя CLI-инструмент, он по большей части пригодится вам только для последующей генерации production-сборок.

Пример использования:

OPTION 1: 
//Install it globally
npm install webpack --g
//Use it at the terminal 
$ webpack //<--Generates bundle using webpack.config.js

OPTION 2 :
//Install it locally & add it to package.json
npm install webpack --save
//Add it to package.json's script 
"scripts”: {
 "build”: "webpack --config webpack.config.prod.js -p”,
 ...
 }
//Use it by running the following:
"npm run build"

Webpack-dev-server (подходит для development-сборок)

Это Express Node.js сервер, который работает на порту 8080. Этот сервер вызывает Webpack изнутри, что дает дополнительные возможности вроде перезагрузки браузера (Live Reloading) и/или замены только что измененного модуля (Hot Module Replacement, или HMR).

Пример использования:

OPTION 1:
//Install it globally
npm install webpack-dev-server --save
//Use it at the terminal
$ webpack-dev-server --inline --hot
OPTION 2:
// Add it to package.json's script 

"scripts”: {
 "start”: "webpack-dev-server --inline --hot”,
 ...
 }
// Use it by running 
$ npm start
Open browser at:
http://localhost:8080

Webpack и опции инструмента webpack-dev-server

Стоит отметить, что некоторые опции, такие как inline и hot, используются только для инструмента webpack-dev-server, в то время как, скажем, hide-modules подходят только для CLI.

Опции webpack-dev-server CLI и опции config

Стоит также отметить, что существует 2 способа передачи опций в webpack-dev-server:

  1. через объект devServer файла webpack.config.js;
  2. через опции CLI.

//Via CLI
webpack-dev-server --hot --inline
//Via webpack.config.js
devServer: {
 inline: true,
 hot:true
 }

Я обнаружил, что devServer config (hot: true и inline: true) иногда не работает. Поэтому я предпочитаю передавать опции как CLI-опции внутри package.json, вот так:
//package.json
{
scripts: 
   {"start”: "webpack-dev-server --hot --inline”}
}

Примечание: убедитесь, что не передаете hot: true и -hot вместе.

Опции hot и inline для webpack-dev-server

Опция inline добавляет Live Reloading для всей страницы. Опция hot включает Hot Module Reloading — горячую перезагрузку модуля, которая перезагружает только измененный компонент (а не всю страницу). Если передать обе опции, то при изменении источника webpack-dev-server запустит прежде всего HMR и, только если это не сработает, перезагрузит всю страницу.

//When the source changes, all 3 options generates new bundle but,
 
//1. doesn't reload the browser page
$ webpack-dev-server
//2. reloads the entire browser page
$ webpack-dev-server --inline
//3. reloads just the module(HMR), or the entire page if HMR fails
$ webpack-dev-server  --inline --hot

3. entry–  строка, массив и объект

Entry передает в Webpack данные о том, где находится корневой модуль или точка входа. Это может быть строка, массив или объект — причем разные типы используются для разных целей.

Если у вас всего одна точка входа (как в большинстве приложений), вы можете выбрать любой формат, и результат будет тот же.

c09054ddd61947348db72504d6d942a1.png
Разные типы entry с одинаковым результатом

entry  —  массив

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

Например, если вам понадобится googleAnalytics.js в вашем HTML, можно сделать так, чтобы Webpack добавил этот файл в конец bundle.js:

f4302704225440b3909475d176f92908.png

entry  — объект

Предположим, у вас настоящее многостраничное приложение, не SPA с мультипросмотром, а несколько HTML-файлов (index.html и profile.html). С помощью Webpack вы можете сразу сгенерировать множество бандлов, используя объект entry.

Файл конфигурации на примере ниже будет генерировать 2 JS-файла: indexEntry.js и profileEntry.js, которые можно использовать в index.html и profile.html соответственно.

2798957fa18f43a998ba09b29c746d64.png

Пример использования:

//profile.html

//index.html


Примечание: название файла происходит от ключей объекта entry

entry  —  комбинация

Вы также можете использовать entry-массивы внутри entry-объекта. К примеру, следующий файл конфигурации сгенерирует 3 файла: index.js, profile.js и vendor.js, содержащий 3 vendor-файла.

8f567bfa361545c3b995fc19d82b0e96.png

4. output  —  path и publicPath

output сообщает Webpack, где и как хранить результирующие файлы. У output есть 2 свойства, path и publicPath, поначалу это может немного смутить.

Свойство path сообщает Webpack, где хранить результат, тогда как свойство publicPath используется в нескольких плагинах Webpack для обновления URL внутри CSS- и HTML-файлов во время генерации production-сборок.

3b8e3e64a15d4b91adf1fdd3411effff.png
Использование свойства publicPath для development и production

Предположим, в вашем CSS-файле содержится URL для загрузки ./test.png на localhost. Но на production файл test.png может быть расположен на CDN, тогда как ваш сервер Node.js может работать на Heroku. Значит, работая на production, вам придется вручную обновлять URL во всех файлах, чтобы они указывали на CDN.

Но вместо этого можно применить свойство publicPath, а также целый ряд сопряженных плагинов, чтобы автоматически обновлять все URL при генерации production-сборок.

6288e9bafc84486e99d553af79a8a790.png
Пример publicPath production

// Development: Both Server and the image are on localhost
.image { 
  background-image: url(‘./test.png’);
 }
// Production: Server is on Heroku but the image is on a CDN
.image { 
  background-image: url(‘https://someCDN/test.png’);
 }

5. Загрузчики и цепочки загрузчиков

Загрузчики — это дополнительные узловые модули, которые помогают загружать или импортировать файлы разных типов в совместимых с браузерами форматах — JS, CSS и т. д. Последующие загрузчики также позволяют импортировать такие файлы в JS, используя require или import в ES6.

Например, вы можете использовать babel-loader для конвертации JS-файла, написанного на ES6, в совместимый с браузером ES5:

module: {
 loaders: [{
  test: /\.js$/, ←Test for ".js" file, if it passes, use the loader
  exclude: /node_modules/, ←Exclude node_modules folder
  loader: ‘babel’ ←use babel (short for ‘babel-loader’)
 }]

Цепочки загрузчиков (работают справа налево)

Несколько загрузчиков для одного типа файлов можно объединить в цепочку. Формирование цепочек осуществляется справа налево, а загрузчики отделяются восклицательным знаком:»!».

Предположим, у нас есть CSS-файл myCssFile.css, и мы хотим выгрузить его содержимое в тег внутри HTML. Это можно сделать, используя 2 загрузчика: css-loader и style-loader.

module: {
 loaders: [{
  test: /\.css$/,
  loader: ‘style!css’ <--(short for style-loader!css-loader)
 }]

Вот как это работает:

7cfcd3af13f24875901bc62f998a4f1e.png

  1. Webpack осуществляет поиск взаимосвязей CSS-файлов внутри модулей. Иными словами, Webpack проверяет, имеет ли JS-файл require (myCssFile.css). Если обнаруживается взаимосвязь, Webpack сначала передает этот файл в css-loader.
  2. css-loader загружает в JSON все CSS-файлы и их собственные взаимосвязи (т. е. import otherCSS). Затем Webpack передает результат в style-loader.
  3. style-loader берет JSON, добавляет его в стилевой тег  —  — и вставляет этот тег в файл index.html.

6. Настройка загрузчиков

Загрузчики можно настраивать так, чтобы они работали по-разному в зависимости от параметров передачи.

В следующем примере url-loader настроен таким образом, чтобы использовать DataURL для изображений размером менее 1024 байт и URL для изображений размером более 1024 байт. Это можно осуществить, передав параметр limit одним из двух способов:

82c6e31f6e824d27bdf7a8351c091334.png

7. Файл .babelrc

babel-loader использует настройку presets, чтобы правильно конвертировать ES6 в ES5 и парсить React JSX в JS. Настройки можно передать через параметр query, как показано ниже:

module: {
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /(node_modules|bower_components)/,
      loader: 'babel',
      query: {
        presets: ['react', 'es2015']
      }
    }
  ]
}

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

Это должно выглядеть примерно так:

//webpack.config.js 
module: {
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /(node_modules|bower_components)/,
      loader: 'babel'
    }
  ]
}

//.bablerc
{
 "presets”: ["react”, "es2015”]
}

8. Плагины

Плагины — это дополнительные узловые модули, которые работают с результирующим бандлом.

К примеру, uglifyJSPlugin берет bundle.js, а затем минимизирует и обфусцирует его содержимое, чтобы уменьшить размер файла.

Аналогичным образом extract-text-webpack-plugin внутренне использует css-loader и style-loader, чтобы собрать все CSS-файлы в одном месте. Этот плагин извлекает результат во внешний файл styles.css и добавляет ссылку на этот файл в index.html.

//webpack.config.js
//Take all the .css files, combine their contents and it extract them to a single "styles.css"
var ETP = require("extract-text-webpack-plugin");

module: {
 loaders: [
  {test: /\.css$/, loader:ETP.extract("style-loader","css-loader") }
  ]
},
plugins: [
    new ExtractTextPlugin("styles.css") //Extract to styles.css file
  ]
}

Примечание: если вы хотите просто встроить CSS как элемент стиля в HTML, это можно сделать без плагина extract-text-webpack-plugin, а за счет CSS и загрузчиков стилей, как показано ниже:
module: {
 loaders: [{
  test: /\.css$/,
  loader: ‘style!css’ <--(short for style-loader!css-loader)
 }]

9. Загрузчики и плагины

Как вы успели заметить, загрузчики работают на отдельном файловом уровне во время генерации бандла или перед ней.

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

10. Разрешение файловых расширений

Многие файлы конфигурации Webpack имеют свойство разрешения расширений с пустой строкой, как показано ниже. Пустая строка нужна, чтобы способствовать импорту без расширений вроде require (»./myJSFile») или импортировать myJSFile из ./myJSFile без файловых расширений.

{
 resolve: {
   extensions: [‘’, ‘.js’, ‘.jsx’]
 }
}

Вот и всё!

Комментарии (3)

  • 5 сентября 2016 в 16:33

    +1

    О, вменяемый гайд по вебпаку, ура.


    1. Файл .babelrc

    Я бы отметил, что сам по себе .babelrc поддерживает разные окружения. Удобно в dev использовать только то, что не поддерживает твой браузер, а в прод деплоить чистый ES5 для IE, Safari и прочих любителей клея, и без соурс-мапов.

  • 5 сентября 2016 в 16:44

    0

    Большинство проектов используют так много функций, что у них, как правило, есть 2 больших файла конфигурации Webpack.

    Есть такой модуль: webpack-merge — он по уму склеивает разные конфиги, так что большие конфиги бьются на маленькие, а из основного можно подтягивать нужные чанки в зависимости от переменных окружения и делать разные сборки (dev, prod, test например)

  • 5 сентября 2016 в 16:45

    0

    К пункту 4:
    file-loader позволяет передать свой собственный publicPath через параметры, отличный от того, что в output. Таким образом можно все картинки в production-режиме закинуть на отдельный сервер.
    Более того, в качестве publicPath можно даже функцию передать, чтобы динамически url генерить.
    Отсюда совет — читайте исходники лоадеров, в них много всего интересного.

© Habrahabr.ru