Июнь 2017 редактирование: 3 только что выпущено! Синтаксис для 3.x идентичен 2.x, что означает, что эта статья все еще актуальна (если я что-то пропустил, прокомментируйте!). Подъем версии был сделан для большего количества закулисных вещей, но он действительно добавил простой в добавлении плагин Scope Hoisting, который может сократить размер вашего пакета, который стоит проверить! Перейдите в конец статьи, чтобы перейти к разделу Обновление до 3 (спойлер: он не отменяет ничего другого в этой статье).

Что такое веб-пакет?

В простейшем случае webpack - это сборщик модулей для вашего JavaScript. Однако с момента выпуска он превратился в менеджера всего вашего внешнего кода (намеренно или по воле сообщества).

Средство выполнения задач, такое как Gulp, может обрабатывать множество различных препроцессоров и транспиляторов, но во всех случаях оно принимает исходный ввод и преобразует его в скомпилированный вывод. Однако это делается в индивидуальном порядке, не заботясь о системе в целом. Это задача разработчика: продолжить работу с того места, где остановился исполнитель задач, и найти правильный способ объединения всех этих движущихся частей воедино в производственной среде.

webpack пытается немного облегчить нагрузку на разработчика, задав смелый вопрос: что, если бы в процессе разработки была часть процесса, которая сама обрабатывала зависимости? Что, если бы мы могли просто написать код таким образом, чтобы процесс сборки управлялся сам, основываясь только на том, что в конечном итоге было необходимо?

Если вы были частью веб-сообщества в течение последних нескольких лет, вы уже знаете предпочтительный метод решения проблемы: создайте его с помощью JavaScript. Итак, webpack пытается упростить процесс сборки, передавая зависимости через JavaScript. Но истинная сила его дизайна - это не просто часть управления кодом; Дело в том, что этот уровень управления представляет собой 100% действительный JavaScript (с функциями Node). webpack дает вам возможность писать корректный JavaScript, который лучше понимает систему в целом.

Другими словами: вы не пишете код для веб-пакета. Вы пишете код для своего проекта t. И webpack не отстает (конечно, с некоторыми конфигами).

Вкратце, если вы когда-либо сталкивались с одним из следующих вопросов:

  • Загрузка зависимостей не по порядку
  • Включение неиспользуемых CSS или JS в производство
  • Случайно двойная (или тройная) загрузка библиотек
  • Проблемы с областью видимости - как из CSS, так и из JavaScript
  • Найдите хорошую систему для использования пакетов NPM в вашем JavaScript или положитесь на сумасшедшую конфигурацию бэкэнда, чтобы полностью использовать NPM
  • Вам нужно лучше оптимизировать доставку активов, но вы боитесь, что что-то сломаете

… Тогда вы можете извлечь выгоду из webpack. Он легко справляется со всем вышеперечисленным, позволяя JavaScript беспокоиться о ваших зависимостях и порядке загрузки, а не вашему мозгу разработчика. Лучшая часть? webpack работает с опережением времени, поэтому вы все равно можете создавать прогрессивные веб-приложения или приложения, для работы которых даже не требуется JS.

Первые шаги

В этом уроке мы будем использовать Пряжа (brew install yarn) вместо npm, но это полностью зависит от вас; они делают то же самое. Из папки нашего проекта мы запустим следующее в окне терминала, чтобы добавить веб-пакет в наш локальный проект:

yarn add --dev webpack webpack-dev-server

Примечание: рекомендуется использовать сценарии NPM, но для простоты мы будем запускать команды вручную в этом сообщении в блоге. Обязательно воспользуйтесь преимуществами автоматизации сценариев NPM в долгосрочной перспективе!

Затем мы объявим конфигурацию веб-пакета с файлом webpack.config.js в корне каталога нашего проекта:

const path = require('path');
const webpack = require('webpack');
module.exports = {
  context: path.resolve(__dirname, 'src'),
  entry: {
    app: './app.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js',
  },
};

Примечание. __dirname относится к каталогу, в котором находится этот webpack.config.js, который в этом сообщении блога является корнем проекта.

Помните, что веб-пакет «знает», что происходит в вашем проекте? Он знает, читая ваш код (не волнуйтесь, он подписал NDA). webpack в основном делает следующее:

  1. Начиная с папки context,…
  2. … Ищет entry имена файлов…
  3. … И читает содержание. Каждую зависимость import (ES6) или require() (узел), которую он находит при синтаксическом анализе кода, он объединяет для окончательной сборки. Затем он ищет эти зависимости и зависимости этих зависимостей, пока не достигнет самого конца дерева, объединяя только то, что ему нужно, и ничего больше.
  4. Оттуда webpack объединяет все в папку output.path, называя ее с помощью шаблона именования output.filename ([name] заменяется ключом объекта из entry)

Итак, если наш src/app.js файл выглядел примерно так (при условии, что мы предварительно запустили yarn add moment):

import moment from 'moment';
var rightNow = moment().format('MMMM Do YYYY, h:mm:ss a');
console.log(rightNow);
// "October 23rd 2016, 9:30:24 pm"

С терминала запустите:

node_modules/.bin/webpack -p

Примечание. Флаг p относится к «производственному» режиму и уменьшает / сокращает вывод. Когда мы запускаем node_modules/.bin/, мы запускаем локальную версию webpack в этом проекте. Это предпочтительно, потому что со временем и в разных проектах у нас могут быть установлены разные версии веб-пакетов, и это гарантирует, что ничего не сломается.

И он выведет dist/app.bundle.js, который записал текущую дату и время на консоль. webpack автоматически узнал, что 'moment' относится к установленному нами пакету NPM.

Совет: 'moment' работает - именно эта строка - потому что внутри package.json main: запись указывает на основную библиотеку. Без этого нам пришлось бы явно объявить библиотеку, которую мы хотели, вот так: 'moment/moment' (мы можем оставить .js в конце). Иногда стоит проверить пакеты NPM, потому что они могут включать альтернативные библиотеки, которые могут сэкономить ваше время или размер файла, если вам не нужна вся библиотека.

Работа с несколькими файлами

Вы можете указать любое количество точек входа / выхода, которое пожелаете, изменив только объект entry.

Объединение нескольких файлов

const path = require('path');
const webpack = require('webpack');
module.exports = {
  context: path.resolve(__dirname, 'src'),
  entry: {
    app: ['./home.js', './events.js', './vendor.js'],
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js',
  },
};

Все будут объединены в один dist/app.bundle.js файл в порядке массива.

Несколько файлов, несколько выходов

const path = require('path');
const webpack = require('webpack');
module.exports = {
  context: path.resolve(__dirname, 'src'),
  entry: {
    home: './home.js',
    events: './events.js',
    contact: './contact.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js',
  },
};

В качестве альтернативы вы можете объединить несколько файлов JS, чтобы разбить части вашего приложения. Он будет объединен в 3 файла: dist/home.bundle.js, dist/events.bundle.js и dist/contact.bundle.js.

Кеширование поставщика

Если вы хотите разделить библиотеки поставщиков в их собственный пакет, чтобы пользователям не приходилось повторно загружать ваши сторонние зависимости каждый раз, когда вы делаете небольшое обновление приложения, вы можете легко сделать это благодаря встроенному в webpack блоку Commons. Плагин:

const webpack = require('webpack');
module.exports = {
  entry: {
    index: './index.js',
    vendor: ['react', 'react-dom'],
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: Infinity,
    }),
  ],
}

Примечание. Убедитесь, что vendor в CommonsChunkPlugin совпадает с указанным выше именем 'vendor' записи - это может быть что угодно, если оно соответствует ключу entry.

В этом случае вы явно указываете webpack использовать ваш vendor пакет как общий блок, содержащий ваши react и react-dom модули узлов для всего приложения. В более крупных приложениях, где оптимизация является ключевым моментом, это может дать лучшие результаты, если вы таким образом ограничите пакет поставщика.

Обратите внимание, что при этом вы должны загрузить vendor раньше app в свой шаблон. webpack часто выдает что-то вроде этого:

Asset               Size  Chunks                   Chunk Names 
vendor.bundle.js  230 kB       0 [emitted]         vendor
app.bundle.js     173 kB       1 [emitted]  [big]  index

Посмотрите, как в веб-паке есть 0 и кусок 1? Он сообщает вам порядок, в котором он ожидает их загрузки. Если вы пишете свой собственный HTML-шаблон, вам нужно будет включить теги <script> в правильном порядке, или вы можете использовать что-то вроде HTML-плагина для веб-пакетов, который сделает это за вас.

Примечание редактора: в предыдущей версии этой статьи был приведен второй пример автоматического извлечения дублирующихся модулей из пакетов с помощью подключаемого модуля Commons Chunk. Этот пример был удален, потому что он бесполезен для большинства новичков, и, если вы не знали, что делаете, он все равно замедлял работу вашего приложения. Если вы хотите узнать больше о (многих) скрытых функциях плагина Commons Chunk, просмотрите документацию webpack.

Разработка

На самом деле у webpack есть собственный сервер разработки, поэтому независимо от того, разрабатываете ли вы статический сайт или просто создаете прототип интерфейса, он идеально подходит для любого из них. Для этого просто добавьте объект devServer в webpack.config.js:

module.exports = {
  context: path.resolve(__dirname, 'src'),
  entry: {
    app: './app.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist', 'assets'),
    publicPath: '/assets',                          // New
  },
  devServer: {
    contentBase: path.resolve(__dirname, 'src'),    // New
  },
};

Теперь создайте файл src/index.html, содержащий:

<script src="/assets/app.bundle.js"></script>

… И с вашего терминала запустите:

node_modules/.bin/webpack-dev-server

Ваш сервер теперь работает на localhost:8080. Обратите внимание, как /assets в теге скрипта совпадает с output.publicPath - это префикс всех URL ресурсов, поэтому вы можете загружать ресурсы откуда угодно (полезно, если вы используете CDN).

webpack будет загружать любые изменения JavaScript по мере их внесения без необходимости обновлять браузер. Однако любые изменения в webpack.config.js файле потребуют перезапуска сервера, чтобы они вступили в силу.

Глобально доступные методы

Вам нужно использовать некоторые из ваших функций из глобального пространства имен? Просто установите output.library в webpack.config.js:

module.exports = {
  output: {
    library: 'myClassName',
  }
};

… И он прикрепит ваш пакет к экземпляру window.myClassName. Таким образом, используя эту область имени, вы можете вызывать методы, доступные для этой точки входа (вы можете узнать больше об этом параметре в документации).

Погрузчики

До сих пор мы рассматривали только работу с JavaScript. Важно начать с JavaScript, потому что это единственный язык, на котором говорит веб-пакет. Мы можем работать практически с любым типом файла, если передадим его в JavaScript. Мы делаем это с помощью загрузчиков.

Загрузчик может относиться к препроцессору, например Sass, или транспилятору, например Babel. В NPM их обычно называют *-loader, например sass-loader или babel-loader.

Вавилон + ES6

Если бы мы хотели использовать ES6 через Babel в нашем проекте, мы бы сначала установили соответствующие загрузчики локально:

yarn add --dev babel-loader babel-core babel-preset-env

… А затем добавьте его в webpack.config.js, чтобы webpack знал, где его использовать.

module.exports = {
  // …
  module: {
    rules: [
      {
        test: /\.js$/i,
        exclude: [/node_modules/],
        use: [{
          loader: 'babel-loader',
          options: { presets: ['env'] },
        }],
      },
    
      // Loaders for other file types can go here
    ],
  },
  // …
};

Примечание для пользователей webpack 1.x: основная концепция Загрузчиков осталась прежней, но синтаксис улучшился.

Это ищет /\.js$/ RegEx поиск любых файлов, заканчивающихся на .js, которые будут загружены через Babel. webpack полагается на тесты регулярных выражений, чтобы дать вам полный контроль, не ограничивая вас только расширениями файлов или предполагая, что ваш код должен быть организован определенным образом.

Если вы обнаружите, что загрузчик искажает файлы или иным образом обрабатывает то, чего он не должен, вы можете указать параметр exclude, чтобы пропустить определенные файлы. Здесь мы исключили нашу папку node_modules из обработки Babel - она ​​нам не нужна. Но мы также можем применить это к любому из наших собственных файлов проекта, например, если бы у нас была папка my_legacy_code. Это не мешает вам загружать эти файлы; скорее, вы просто даете знать webpack, что можно импортировать как есть, а не обрабатывать их.

CSS + загрузчик стилей

Если бы мы хотели загружать CSS только по мере необходимости, мы могли бы это сделать. Допустим, у нас есть файл index.js. Мы импортируем его оттуда:

import styles from './assets/stylesheets/application.css';

Мы получим следующую ошибку: You may need an appropriate loader to handle this file type. Помните, что webpack может понимать только JavaScript, поэтому нам нужно установить соответствующий загрузчик:

yarn add --dev css-loader style-loader

… А затем добавьте правило в webpack.config.js:

module.exports = {
  // …
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      // …
    ],
  },
};

Загрузчики обрабатываются в обратном порядке массива. Это означает, что css-loader будет работать раньше style-loader.

Вы можете заметить, что даже в производственных сборках это фактически связывает ваш CSS с вашим связанным JavaScript, и style-loader вручную записывает ваши стили в <head>. На первый взгляд это может показаться немного странным, но постепенно становится понятнее, чем больше вы думаете об этом. Вы сохранили запрос заголовка, что сэкономило драгоценное время на некоторых соединениях, и, если вы все равно загружаете DOM с помощью JavaScript, это, по сути, устраняет FOUC само по себе.

Вы также заметите, что - прямо из коробки - webpack автоматически разрешил все ваши @import запросы, упаковав эти файлы вместе в один (вместо того, чтобы полагаться на импорт CSS по умолчанию, который может привести к необоснованным запросам заголовков и медленной загрузке ресурсов).

Загрузка CSS из вашего JS - это просто потрясающе, потому что теперь вы можете модулировать свой CSS новыми мощными способами. Допустим, вы загрузили button.css только через button.js. Это будет означать, что если button.js никогда не используется на самом деле , его CSS не будет раздувать нашу производственную сборку. Если вы придерживаетесь компонентно-ориентированных методов CSS, таких как SMACSS или BEM, вы видите ценность более тесного сочетания CSS с разметкой + JavaScript.

CSS + узловые модули

Мы можем использовать webpack, чтобы воспользоваться преимуществами импорта модулей узлов с использованием префикса узла ~. Если бы мы запустили yarn add normalize.css, мы могли бы использовать:

@import "~normalize.css";

… И воспользуйтесь всеми преимуществами NPM, управляющего нашими сторонними стилями для нас - версией и всем остальным - без какого-либо копирования + вставки с нашей стороны. Кроме того, получение веб-пакета для объединения CSS имеет очевидные преимущества по сравнению с использованием импорта CSS по умолчанию, что избавляет клиента от ненужных запросов заголовков и медленной загрузки.

Обновление: этот и следующий раздел были обновлены для обеспечения точности, чтобы больше не было путаницы с использованием CSS-модулей для простого импорта узловых модулей. Спасибо Альберту Фернандесу за помощь!

Модули CSS

Возможно, вы слышали о Модулях CSS, которые убирают C из CSS. Обычно он работает лучше всего, только если вы строите DOM с помощью JavaScript, но по сути он волшебным образом переносит ваши классы CSS в файл JavaScript, который их загрузил (подробнее об этом здесь). Если вы планируете использовать его, модули CSS поставляются в комплекте с css-loader (yarn add --dev css-loader):

module.exports = {
  // …
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: { modules: true },
          },
        ],
      },
      // …
    ],
  },
};

Примечание. Для css-loader теперь мы используем расширенный синтаксис объекта, чтобы передать ему параметр. Вместо этого вы можете использовать строку как сокращение для использования параметров по умолчанию, как мы все еще делаем с style-loader.

Стоит отметить, что вы можете сбросить ~ при импорте узловых модулей с включенными модулями CSS (например: @import "normalize.css";). Однако теперь вы можете столкнуться с ошибками сборки, когда @import используете собственный CSS. Если вы получаете сообщение об ошибке «не удается найти ___», попробуйте добавить объект resolve в webpack.config.js, чтобы дать webpack лучшее представление о предполагаемом порядке модулей.

module.exports = {
  //…
  resolve: {
    modules: [path.resolve(__dirname, 'src'), 'node_modules']
  },
};

Сначала мы указали наш исходный каталог, а затем node_modules. Таким образом, webpack будет обрабатывать разрешение немного лучше, сначала просматривая наш исходный каталог, а затем установленные модули узлов, в этом порядке (замените "src" и "node_modules" на каталоги исходного кода и модуля узла соответственно).

Sass

Нужно использовать Sass? Без проблем. Установить:

yarn add --dev sass-loader node-sass

И добавим еще одно правило:

module.exports = {
  // …
  module: {
    rules: [
      {
        test: /\.(sass|scss)$/i,
        use: [
          'style-loader',
          'css-loader',
          'sass-loader',
        ]
      } 
      // …
    ],
  },
};

Затем, когда ваш Javascript вызывает import в .scss или .sass файле, webpack сделает свое дело. Помните: порядок use обратный, поэтому сначала мы загружаем Sass, затем наш синтаксический анализатор CSS и, наконец, загрузчик стилей, чтобы загрузить наш проанализированный CSS в <head> нашей страницы.

CSS в комплекте отдельно

Может быть, вы имеете дело с прогрессивным улучшением; возможно, вам нужен отдельный файл CSS по какой-то другой причине. Мы можем легко это сделать, заменив style-loader на extract-text-webpack-plugin в нашей конфигурации без необходимости изменять какой-либо код. Возьмем наш пример app.js файла:

import styles from './assets/stylesheets/application.css';

Давайте установим плагин локально…

yarn add --dev extract-text-webpack-plugin

… И добавьте к webpack.config.js:

const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
  // …
  module: {
    rules: [
      {
        test: /\.css$/i,
        use:  ExtractTextPlugin.extract({
          use: [{
            loader: 'css-loader',
            options: { importLoaders: 1 },
          }],
        }),
      },
    
      // …
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      filename: '[name].bundle.css',
      allChunks: true,
    }),
  ],
};

Теперь при запуске node_modules/.bin/webpack -p вы также заметите app.bundle.css файл в своем output каталоге. Просто добавьте тег <link> к этому файлу в вашем HTML, как обычно.

HTML

Как вы уже догадались, для webpack есть еще html-loader плагин. Однако, когда мы приступаем к загрузке HTML с помощью JavaScript, это примерно та точка, где мы разветвляемся на множество различных подходов, и я не могу придумать ни одного единственного примера, который бы настроил вас на то, что вы планируете делать. следующий. Как правило, вы загружаете HTML с целью использования разметки со вкусом JavaScript, такой как JSX, Mustache или Handlebars, для использования в более крупной системе, такой как React, Vue или Angular. . Или вы используете препроцессор, такой как Pug (ранее Jade). Или вы можете просто вставить тот же HTML-код из исходного каталога в каталог сборки. Я не предполагаю, что бы вы ни делали.

На этом я закончу урок: загрузка разметки с помощью webpack настоятельно рекомендуется и выполнима, но к этому моменту вы будете принимать собственные решения относительно своей архитектуры, которые ни я, ни webpack не можем сделать за вас. . Но использования приведенных выше примеров для справки и поиска подходящих загрузчиков в NPM должно быть достаточно, чтобы вы начали.

Мышление модулями

Чтобы получить максимальную отдачу от webpack, вам придется мыслить модулями - небольшими, многоразовыми, автономными процессами, которые хорошо выполняют одно и то же. Это значит взять что-то вроде этого:

└── js/
    └── application.js   // 300KB of spaghetti code

… И превратив его в это:

└── js/
    ├── components/
    │   ├── button.js
    │   ├── calendar.js
    │   ├── comment.js
    │   ├── modal.js
    │   ├── tab.js
    │   ├── timer.js
    │   ├── video.js
    │   └── wysiwyg.js
    │
    └── index.js  // ~ 1KB of code; imports from ./components/

Результат - чистый, многоразовый код. Каждый отдельный компонент зависит от import своих зависимостей и export того, что он хочет сделать общедоступным для других модулей. Соедините это с Babel + ES6, и вы сможете использовать классы JavaScript для большей модульности и не думай об этом, что просто работает.

Подробнее о модулях читайте в этой отличной статье Прити Кэсиредди.

Обновление до 3

Согласно Шону Т. Ларкину в сообщении в блоге о выпуске: Переход с webpack 2 на 3 не должен включать никаких усилий, кроме выполнения команд обновления в вашем терминале. Мы отметили это как серьезное изменение из-за внутренних критических изменений, которые могут повлиять на некоторые плагины .

В версии 3 была добавлена ​​изящная функция: поднятие области видимости. Это специальное имя для описания нового метода объединения всех ваших модулей в один манифест. В лучшем случае: он может уменьшить размер вашего пакета почти вдвое (источник). Худший сценарий: подъем области видимости не работает для вашего специального пакета и выводит почти такой же код, как и в версии 2.x.

Чтобы добавить его, просто добавьте в plugins следующую строку:

module.exports = {
  //…
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin(),
  ],
  //…
};

Подробнее о Подъеме прицела читайте здесь.

Дальнейшее чтение