По сути, Webpack – это связыватель модулей для вашего JavaScript. Тем не менее, с момента выхода, Webpack 2 превратился в менеджера всего вашего фронтенд-кода (либо намеренно, либо по воле сообщества).
Такой таск-ранер как Gulp Gulp может обрабатывать множество различных препроцессоров и транспайлеров, но во всех случаях ему понадобится ввод (input) чтобы вписать его в скомпилированный вывод (output). Однако, он делает это от случая к случаю, независимо от того, насколько велика система. Для разработчиков это настоящая проблема: найти, где таск-раннер остановился и найти подходящий способ собрать все подвижные элементы в одном месте.
Webpack старается облегчить долю разработчика с помощью одного важного вопроса: что, если бы какой-то из процессов сам по себе обрабатывал бы зависимости? Что, если бы мы могли просто набирать код так, чтобы он сам себя контролировал, основываясь лишь на том, что нужно получить в итоге?
Если вы были частью веб-сообщества в течение последних нескольких лет, вы уже знаете, предпочтительный способ решения проблемы: построить это с помощью JavaScript. Таким образом, Webpack помогает облегчить процесс создания, проводя зависимости через JavaScript. Но настоящая его изюминка в том, что он не только управляет кодом, а в том, что это управление на 100% подходит к JavaScript (с функциями Node). Webpack дает возможность писать валидный JavaScript, который в целом лучше совместим с системой.
Другими словами код вы пишете для не для Webpack, а для своего проекта, а Webpack (конечно, с вашими настройками) уже продолжает работу сам.
Вкратце, если вы когда-нибудь испытывали трудности с:
- Загрузкой зависимостей в случайном порядке
- Включение неиспользованных CSS или JS в продакшен
- Случайной двукратной (или трехкратной) загрузкой библиотек
- Масштабированием как в CSS, так и в JavaScript
- Поиском хорошей системы для использования модулей Node/Bower в своем JavaScript, или если вам приходилось надеяться на странную конфигурацию с целью должного использования этих модулей
- Необходимостью оптимизировать загрузку ассетов и страхом что-то при этом накосячить
..то вы могли бы извлечь выгоду из WebPack. Он обрабатывает все вышесказанное без усилий, позволяя JavaScript беспокоиться о ваших зависимостях и порядке загрузки вместо мозга разработчика. WebPack даже может работать на стороне сервера, то есть вы можете построить прогрессивные сайты используя Webpack.
Первые шаги
Мы будем использовать Yarn (brew install yarn
) в этом учебнике вместо npm
, но вы можете поступать как хотите: они делают одно и тоже. Из нашей папки проекта мы, чтобы добавить Webpack для обеих наших глобальных пакетов и нашего локального проекта, выполним следующее в окне терминала:
npm i -g webpack webpack-dev-server@2
yarn add --dev webpack webpack-dev-server@2
Обратите внимание: в нашем примере для простоты мы устанавливаем его глобально, а не используем NPM-скрипты, как рекомендуется в таких случаях. Оба способа подходят, а доки покажут разницу между ними.
Затем мы объявляем конфигурацию WebPack 2 с помощью файла 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, который в данной статье является корнем всего проекта.
Помните, что WebPack "знает", что происходит в вашем проекте? Он знает это из вашего кода (не волнуйтесь, он подписал соглашение о неразглашении :) ). WebPack в основном выполняет следующие действия:
- Начинает из папки
context
folder, …
- … ищет имена файлов
entry
…
- … и считывает содержимое. Каждую зависимость
import
(ES6) или require()
()(Node), которую он находит при обработке кода, он сохраняет для финального билда. Затем он ищет зависимости those, и зависимости тех зависимостей до тех пор, пока не доберется до самого конца «дерева», собирая лишь то, что нужно для проекта.
- Теперь, 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"
то мы запустим
webpack -p
Флаг p
одначает режим “production” (продакшен) и уменьшает файл на выходе.
Мы получим dist/app.bundle.js
, который сохранил и записал текущие дату и время в консоли. Обратите внимание, что WebPack автоматически знал, на что ссылается 'moment'
(хотя у вас в каталоге был файл moment.js и Webpack по умолчанию поставит ему более высокий приоритет, чем Node-модулю moment
).
Работа с несколькими файлами
Вы можете указать любое количество входных/выходных точек, изменяя только объект entry
.
Несколько файлов, собранных вместе
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-файлов, чтобы разбить ваше приложение на части. Здесь мы группируем три файла: dist/home.bundle.js
, dist/events.bundle.js
и dist/contact.bundle.js
.
Автоматический сбор бандлов
Если вы разбиваете приложение на несколько бандлов, а это полезно в случае, если в вашем приложении много JS, которые вам не нужно загружать в самом начале, есть вероятность того, что вы можете дублировать код через эти файлы (обычно через библиотеки вендоров), потому что каждая зависимость разрешается отдельно от других. К счастью, WebPack имеет встроенный плагин CommonsChunk, который прекрасно с этим справляется:
module.exports = {
// …
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'commons',
filename: 'commons.js',
minChunks: 2,
}),
],
// …
};
Теперь, через ваши output
файлы, если у вас есть модули, которые загружаются 2
или более раза (задается в minChunks
) они будут собираться в файл commons.js
который вы можете кэшировать на стороне клиента. Это, конечно, выльется в дополнительный запрос заголовка, но клиент не будет загружать одни и те же библиотеки по нескольку раз. Таким образом, существует множество сценариев, когда мы экономим таким образом время.
Ручной сбор бандлов
Если вам больше нравится делать все самостоятельно, то можно собирать их и вручную:
module.exports = {
entry: {
index: './index.js',
vendor: ['react', 'react-dom', 'rxjs'],
},
// …
}
Здесь вы явно говорите Webpack 2 экспортировать бандл vendor
, содержащий react
, react-dom
и rxjs
Node-модули, вместо того, чтобы полагаться на CommonsChunkPlugin.
Разработка
У 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', // Новое
},
devServer: {
contentBase: path.resolve(__dirname, './src'), // Новое
},
};
Теперь сервер работает на localhost:8080
. Обратите внимание, что /assets
в теге script совпадает с output.publicPath
который прибавляет префиксы ко всем URL ассетов, поэтому вы можете загружать ассеты откуда вам надо (полезно при использовании CDN).
Webpack не будет загружать изменения в JavaScript, если вы будете делать их без необходимости перезагрузки браузера. Однако изменения в файле webpack.config.js
потребуют перезагрузки сервера, чтобы увидеть эффект.
Повсеместно доступные методы
Нужно использовать некоторые ваши функции из глобального пространства имен? Просто установите output.library
в webpack.config.js
:
module.exports = {
output: {
library: 'myClassName',
}
};
... и он присоединит вашу сборку к window.myClassName
Таким образом, используя такое имя, вы сможете вызывать методы, доступные на данный момент (вы можете узнать больше об этой настройке в документации).
Загрузчики
До сих пор, мы говорили только о работе с JavaScript. Важно начать с JavaScript, потому что это единственный язык, который понимает Webpack.. По идее, через JavaScript мы можем работать с файлами любого типа. Будем работать с загрузчиками (Loaders).
Загрузчики могут ссылаться на препроцессор (например, Sass) или транспайлер (например, Babel). В NPM они обычно называются *-loader
(например, sass-loader
) или babel-loader
.
Babel + ES6
Если мы хотим использовать ES6 с помощью Babel в нашем проекте, мы должны сначала установить соответствующий загрузчик локально:
yarn add --dev babel-loader babel-core babel-preset-es2015
… а затем добавить его в webpack.config.js
, чтобы WebPack мог его использовать.
module.exports = {
// …
module: {
rules: [
{
test: /\.js$/,
use: [{
loader: 'babel-loader',
options: { presets: ['es2015'] }
exclude: [/node_modules/],
}],
},
// Загрузчики для других типов файлов могут размещаться здесь
],
},
// …
};
Примечание для пользователей Webpack 1.x: основная концепция загрузчиков (Loaders) осталась такой же, но синтаксис улучшился.
Они ищут /\.js$/
- любые файлы, которые заканчиваются на .js
, и должны быть загружены через Babel. Webpack полагается на проверки регулярных выражений, чтобы дать вам полный контроль, не ограничивая вас только расширениями файлов или предлагая организовать ваш код определенным образом.
Если загрузчик искажает файлы, либо, напротив обрабатывает те файлы, которые не должен, вы можете указать опцию exclude
, чтобы пропустить определенные файлы. Здесь мы исключили нашу папку node_modules
обрабатываемых Babel’ом, они нам не нужны. Но мы также могли бы применить это к любому из наших собственных файлов проекта, например, если бы у нас была папка my_legacy_code
. Это вас не спасет от загрузки этих файлов; скорее, вы просто даете Webpack'у знать, что можно их импортировать и не обрабатывать. Вы можете использовать include
, а также сделать противоположное исключение (но, как правило, этот вариант не нужен).
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$/,
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, то при работе с HTML и JavaScript это будет хорошо заметно.
CSS + Модули Node
Вы можете использовать Webpack чтобы воспользоваться преимуществами импорта модулей Node с использованием Node’овского префикса ~
. Если мы запустим yarn add normalize.css
, то сможем использовать:
@import "~normalize.css";
… и сполна воспользоваться преимуществами NPM, управляющей стилями без их копирования. Далее, использование Webpack для сбора CSS имеет очевидные преимущества над стандартным импортом CSS, уберегая клиентов от лишних запросов заголовков и медленной загрузки.
Обновление: этот и последующие разделы были обновлены для точности, чтобы не путать использование CSS-модулей с простым импортом Node-модулей.
CSS модули
Вы наверняка слышали о Модулях CSS. Лучше всего они работают тогда, когда вы создаете DOM на JavaScript, но вообще они магическим образом направляют ваши CSS-классы JavaScript-файлу, который их загрузил. Если вы планируете его использовать, CSS-модули идут в комплекте с css-загрузчиком (yarn add --dev css-loader
):
module.exports = {
// …
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: { modules: true }
},
],
},
// …
],
},
};
Для css-загрузчиков мы используем расширенный синтаксис объекта чтобы передать ему настройки. Можно также использовать строки, чтобы использовать стандартные настройки, как мы делаем с css-загрузчиком.
Стоит отметить, что на самом деле вы можете опускать ~
при импорте Node-модулей с поддержкой CSS-модулей (например: @import "normalize.css";
). Однако, вы можете столкнуться с ошибками билда при импорте своей CSS. Если вы получаете ошибки “can’t find ___”, попробуйте добавить объект resolve
в webpack.config.js, чтобы дать Webpack лучше понять необходимый вам порядок модулей.
module.exports = {
//…
resolve: {
modules: [path.resolve(__dirname, './src'), 'node_modules']
},
};
Мы сперва указали исходную директорию, а затем модули Node. Так, Webpack будет работать чуточку лучше, сначала проверяя исходную папку, а затем установленные модули Node (замените src
и node_modules
своим исходным местоположением и местоположением модулей Node, соответственно).
Sass
Нужно использовать Sass? Нет проблем. Установите:
yarn add --dev sass-loader node-sass
И добавьте еще одно правило:
module.exports = {
// …
module: {
rules: [
{
test: /\.(sass|scss)$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
]
}
// …
],
},
};
Затем, когда Javascript вызывает import
на .scss или .sass файле, sass-файле, Webpack вступит в дело. Запомните: порядок использования обратный, поэтому сперва мы загружаем 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@2.0.0-beta.5
… и добавим их в webpack.config.js:
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
// …
module: {
rules: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
loader: 'css-loader?importLoaders=1',
}),
},
// …
]
},
plugins: [
new ExtractTextPlugin({
filename: '[name].bundle.css',
allChunks: true,
}),
],
};
Последняя версия полученного плагина текста на данный момент поддерживает только синтаксис загрузчика 1.x, а следовательно и загрузчик.
Теперь при запуске webpack -p
вы также заметите файл app.bundle.css в вашем каталоге output. Теперь добавьте тег link к вашему html-файлу.
HTML
Как вы могли догадаться, есть также html-loader plugin для Webpack. Тем не менее, когда мы перейдем к загрузке HTML с JavaScript, мы столкнемся с множеством различных способов, и среди них даже сложно выделить какой-то один для дальнейшей работы. Обычно мы бы загрузили HTML для того, чтобы использовать с JavaScript-разметкой, такой, как JSX, Mustache или Handlebars, которые будут использоваться в больших системах, таких как React, Vue или Angular. Вы можете использовать пре-процессор, такой как Pug (ранее – Jade), либо просто буквально продвигать один и тот же HTML-код от исходной папки в конечную. Какой способ выбрать – дело ваше.
Здесь мы закончим наш учебник: вы можете загрузить загрузить разметку через Webpack, но к этому времени вы уже должны принимать свои собственные решения касательно своего проекта, которые никто кроме вас принимать не может и не должен. В принципе, использование вышеописанных примеров по ссылкам и поиску нужных загрузчиков в NPM должно быть достаточным для начала.
Размышляя о модулях
Для того, чтобы получить максимальную отдачу от WebPack 2, вам придется думать о модулях как маленьких, многоразовых, автономных процессах, , которые отлично справляются со своей единственной работой. Это означает, что надо взять что-то такое:
└── js/
└── application.js // 300KB спагетти-кода
… и превратить в такое:
└── js/
├── components/
│ ├── button.js
│ ├── calendar.js
│ ├── comment.js
│ ├── modal.js
│ ├── tab.js
│ ├── timer.js
│ ├── video.js
│ └── wysiwyg.js
│
└── index.js // ~ 1KB кода; импорт из ./components/
В результате чистый код с возможностью многократного использования. Каждый отдельный компонент зависит от import
'а его зависимостей и export
'а того, что он хочет сделать доступным для остальных модулей. Совместите его с Babel + ES6 и вы сможете использовать JavaScript-классы для большой модульности и масштабирования.