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

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

Зачем нужен webpack

Разбивать код на модули — очень здравая мысль. В JavaScript для обеспечения модульности использовались сторонние библиотеки, такие как require.js. С приходом спецификации ES6 о них можно забыть, отныне модульность описана в стандарте языка.

Но есть одна небольшая проблема: поддержка браузерами. И есть временное решение этой проблемы: писать на ES6 можно уже сейчас, но придётся пользоваться сборщиками и транспайлером babel.js, который будет переводить ваш новый синтаксис в понятный всем браузерам старый. Такие дела.

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

Разумеется, он позаботится и о стилях: в этом поможет PostCSS (эта штука достойна отдельной заметки). Используйте любой подход (AMD, CommonJS, etc). Работайте с самыми новыми «фичами» благодаря babel и PostCSS, подключайте в связку react или angular. Теперь можно всё.

Как настроить webpack

Подготовительный этап

Создайте директорию для вашего проекта. Пусть это будет kanban-app. Её структура имеет вид:

.
├── app
│   ├── App.jsx
│   ├── app.css
│   ├── assets
│   ├── components
│   └── index.html
├── config
└── webpack.config.js

Содержимое index.html:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Kanban App</title>
</head>

<body>
  <div id="app"></div>
  <script src="/js/bundle.js"></script>
</body>

</html>

App.jsx

import React, { Component } from 'react';
import { render } from 'react-dom';
import './app.css';

class App extends Component {
  render() {
    return (
      <div className="app-wrapper">
        <h1 className="app-wrapper__heading">Hello</h1>
      </div>
    );
  }
}

render(<App />, document.getElementById('app'));

Что сделали? В примере будем использовать библиотеку React, поэтому импортируем её части, стили и создаём класс, который будет выводить своё содержимое в разметку. Страдающим от вида jsx могу лишь предложить придумать свой пример: конкретный код в данном случае не имеет значения.

В components должны лежать части приложения, в assets — статика (favicon’ки, robots.txt, humans.txt и прочее).

Добавим немного PostCSS. Все переменные задаются в root.

:root {
  --mainColor: #f2f2f2;
  --headingColor: #333;

  --lightTheme {
    background-color: #fff;
    border-color: #a3be8c;
  }
}

Используются переменные для задания параметров или применения сразу нескольких:

.app-wrapper {
  margin: 20px auto;
  padding: 0 20px;
  background-color: var(--mainColor); /* цвет #f2f2f2 */
  border: 2px solid #555;

  /* здесь сразу кучка правил для светлой темы */
  @apply --lightTheme;

  /* вложенность и BEM тоже можно */
  &__heading {
    padding: 0 20px;
    font-size: 23px;
    color: var(--headingColor);
  }
}

А ещё можно кастомные медиавыражения:

@custom-media --medium screen and (min-width: 960px);
@custom-media --large screen and (min-width: 1200px);

@media (--medium) {
  .app-wrapper {
    display: flex;
    flex-direction: row;
    justify-content: center;
  }
}

Подготовительный этап почти закончен. Для управления зависимостями добавить npm или yarn по вкусу. Подтягиваем зависимости:

$ yarn init # создаст package.json
$ yarn add react react-dom # глобальные зависимости проекта

$ yarn add --dev babel-core babel-loader babel-plugin-react-transform \
babel-preset-es2015 babel-preset-react babel-preset-stage-0 copy-webpack-plugin \
css-loader eslint eslint-plugin-react extract-text-webpack-plugin file-loader \
open-browser-webpack-plugin postcss-cssnext postcss-loader react-transform-hmr \
style-loader url-loader webpack webpack-dev-server webpack-merge

Да, зависимостей для разработки получилось довольно много. Что поделать, таков современный фронтенд: за удобство в разработке и сопровождении нужно чем-то платить. Здесь webpack и нужные нам плагины, babel со товарищи, postcss, линтеры: неплохой комплект.

Babel нуждается в точных инструкциях: в presets укажем то, что используем (ES6, ES7, react), в plugins настроим react-transform-hmr, чтобы на странице без полной её перезагрузки перерисовывались только те части, которые были изменены.

{
  "presets" : ["es2015", "stage-0", "react"],
  "env": {
    "development": {
      "plugins": [["react-transform", {
          "transforms": [{
              "transform": "react-transform-hmr",
              "imports": ["react"],
              "locals": ["module"]
            }]
        }]]
    }
  }
}

Этап настройки

Чтобы читатель вкусил взрослой жизни, предлагаю разнести конфигурацию на несколько отдельных файлов: для разработки и production. В package.json добавляем несколько скриптов. Первый делает сборку, второй — сборку для production, третий запускает сборку и webpack-dev-server, дальше линтер.

"scripts": {
  "build": "webpack -d",
  "build:prod": "webpack --config config/webpack.prod.config.js -p",
  "start": "npm run build && webpack-dev-server --debug --hot --devtool --watch --colors --inline --quiet --content-base build --port 8080 --host 0.0.0.0",
  "lint": "eslint ./app"
}

Теперь разберёмся с конфигурационными файлами.

/* В `webpack.config.js` будет лишь одна строка импорта */
module.exports = require('./config/webpack.dev.config.js');

/* config/webpack.common.config.js содержит основные настройки */

// импортировать модули и плагины
const path = require('path');
const cssnext = require('postcss-cssnext');
const CopyWebpackPlugin = require('copy-webpack-plugin');

// задать константы для директории проекта и сборки
const APP_DIR = path.resolve(__dirname, '../app');
const BUILD_DIR = path.resolve(__dirname, '../build');

const commonConfig = {

  context: APP_DIR,
  entry: path.join(APP_DIR, '/App.jsx'),

  output: {
    path: BUILD_DIR,
    publicPath: '/',
    filename: 'js/bundle.js',
  },

  devServer: {
    outputPath: BUILD_DIR,
  },

  resolve: {
    extensions: ['', '.js', '.jsx'],
  },

  module: {
    // указать загрузчики для расширений файлов
    loaders: [
      { test: /\.jsx?$/, include: APP_DIR, loader: 'babel' },
      { test: /\.json?$/, include: APP_DIR, loader: 'json' },
      { test: /\.css$/, loader: 'style!css!postcss' },
      { test: /\.(jpe?g|jpg|png|gif|svg)$/i, include: APP_DIR, loader: 'url?limit=1000&name=[path]/[name].[ext]' },
      { test: /\.(eot|svg|ttf|woff?2)$/i, include: APP_DIR, loader: 'file?name=[path]/[name].[ext]' },
    ],
  },

  // будем писать стили c postCSS:
  // cssnext как препроцессор
  // и autoprefixer с поддержкой последних двух версий браузеров
  postcss() {
    return {
      defaults: [cssnext],
      cleaner: [cssnext({ browsers: ['last 2 versions'] })],
    };
  },

  // копировать статику: html и assets в директорию BUILD
  plugins: [
    new CopyWebpackPlugin([{ context: APP_DIR, from: '**/*.html', to: BUILD_DIR }]),
    new CopyWebpackPlugin([{ context: APP_DIR, from: 'assets', to: BUILD_DIR }]),
  ],
};

// экспорт конфигурации
module.exports = commonConfig;

Двигаться в направлении разделения конфигов будем, используя установленный ранее webpack-merge. Так выглядит конфиг, который запускается в режиме разработки.

/* config/webpack.dev.config.js содержит основные настройки */
// экспорт только нужного
const webpackMerge = require('webpack-merge');
const OpenBrowserPlugin = require('open-browser-webpack-plugin');
const commonConfig = require('./webpack.common.config.js');

// здесь начинаются различия
module.exports = webpackMerge(commonConfig, {
  // подключаем source-map
  devtool: 'cheap-eval-source-map',

  // при разработке сразу открывать страницу index.html
  plugins: [
    new OpenBrowserPlugin({ url: 'http://localhost:8080/webpack-dev-server/' }),
  ],
});

Наконец, конфиг для сборки в production.

/* config/webpack.prod.config.js содержит основные настройки */
// экспорт только нужного
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const commonConfig = require('./webpack.common.config.js');
const ENV = process.env.NODE_ENV = process.env.ENV = 'production';

module.exports = webpackMerge(commonConfig, {
  // подключаем плагины всякой разной оптимизации
  plugins: [
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.OccurrenceOrderPlugin(true),
    new webpack.optimize.UglifyJsPlugin({
      mangle: true,
      output: { comments: false },
      compress: { warnings: false },
    }),
  ],
});

И что теперь?

Ничего. Пользуемся. Наша простейшая конфигурация запустится по команде npm run start. Все имеющиеся команды описаны в package.json. По поводу стилей хочется отметить, что при такой конфигурации менять их можно «на лету», они находятся в JavaScript (да-да, тот самый all in js) и webpack отслеживает их. Это достаточно удобно при разработке, но для production, если стиль «all in js» не устраивает, есть extract-text-webpack-plugin: компонентный подход остаётся, мы импортируем стили прямо в JS, но webpack вытаскивает их в отдельный css-файл/файлы. ⤧  Предыдущая запись Ключи от всех дверей