webpack
Преимущества webpack:
- Webpack поддерживает модули AMD прямо из коробки (иначе говоря берем AMD-модули (в стиле require) и начинаем использовать)
- webpack поддерживает и модули AMD (в стиле requireJS), и модули CommonJS (в стиде nodejs) одновременно.
2.1 Простая сборка - Простой конфиг
Устанавливаем глобально: npm i -g webpack
Создаем файл webpack.config.js. В этом файле в module.exports
экспортируем объект конфига.
webpack.config.js:
jQuery or Javascript
module.exports = {
entry: "./home", // entry говорит о том,
//какой модуль собирать (в нашем случае это home.js)
output: {
filename: "build.js"
} // output говорит о том, куда выводить
}
Запускаем сборку, в консоли для проекта:
webpack
Установим простейший сервер статики:
npm I –g node-static
Запускаем его в нужной директории:
static
По выданному адресу будет показано содержимое директории
2.2 Простая сборка - Внешний доступ к модулям
При webpack сборке глобальных переменных нет. Но что делать, если мы хотим, например, из какого-нибудь другого скрипта обратиться к функциональности нашего модуля. Например, мы хотим вызвать функцию не на момент загрузки, а потом.
- Во-первых сделаем
exports
(exports.welcome = welcome;
) в файле home.js. - В
webpack.config
добавим опциюlibrary
(library: "home"
),exports
модуля будет помещен в переменнуюhome
.
2.3 Простая сборка - Пересборка при изменениях
Опция watch: true
в webpack.config следит за файлами и при их изменении перезапускает сборку. Например, EnvironmentPlugin
будет передавать в итоговый код наши переменные.
2.4 Простая сборка - Source maps (Отладка)
Debugger;
сработает непосредственно в сборке, что неудобно. Есть решение – source maps
. При помощи параметра devtool
(значения которого позволяют строить source map
по разному, http://webpack.github.io/docs/configuration.html#devtool) можно указать, что нужна source map. Теперь вы можете отслеживать изменения (debugger) для каждого файла (модуля).
devtool: "source-map" // Хороший вариант на production
2.5 Простая сборка - Окружение, NODE_ENV
В конфиге установлено watch: true
. Однако в реальной жизни необходимо запустить webpack минимум 2 раза: для разработки (например, с watch: true) и для "боя" (просто сборка).
Традиционный способ переключаться между development и production это использовать Переменные окружения.
Плагин это такая штука, которая может подключаться на разных этапах компиляции и что-то делать.
В nodejs традиционно используется NODE_ENV
. Информация о сборке может находиться и внутри самих скриптов. То есть наши модули могут содержат свои отладочные средства, которые включаются только в development и помогают нам в отладке. Как этого добится:
- Использовать плагин (понадобится плагин, для этого локально (для проекта) установим webpack:
npm i webpack
, так как мы собираемся импортировать модули из webpack) const webpack = require('webpack');
- В webpack есть достаточно много плагинов (http://webpack.github.io/docs/list-of-plugins.html). Нам потребуется EnvironmentPlugin. Передаем те ключи окружения, которые хочется сделать доступными клиенту.
- Чтобы передавать клиенту непосредственно переменную
NODE_ENV
, а не свойствоprocess.env
, понадобится другой плагин: DefinePlugin
jQuery or Javascript
const NODE_ENV = process.env.NODE_ENV || 'development';
plugins: [
new webpack.DefinePlugin({
NODE_ENV: JSON.stringify(NODE_ENV)
// JSON.stringify – превращает объекты в строку в формате JSON
}),
]
// ==
if(NODE_ENV == 'development') { …….
Преобразуется в:
if(true) {...
2.6 Babel.js
Мы настроим загрузчик Babel
для всех файлов JavaScript, чтобы транскомпилировать код на ES6 в код старой версии JavaScript, который смогут понять нынешние браузеры. http://frontender.info/packing-the-web-like-a-boss/#nazadvbudushtee – пример webpack с babel-loader
(package.json)
Общепринятый способ использовать кроссбраузерно современный js это babel. http://babeljs.io/ - получает современный js и превращает его в более старый/кроссбраузерный.
Для использования babel в нашем приложении воспользуемся концепцией loader
’ов, которую предоставляет webpack.
jQuery or Javascript
module: {
loaders: [{
test: /\.js$/,
loader: 'babel'
}]
}
Здесь говорится, что к файлам, которые оканчиваются на js нужно применять babel-loader. Loader это модуль. Устанавливаем npm i babel-loader
.
Попадаем в репозитория модуля: npm repo babel-loader
.
Как настраивать loaders: http://webpack.github.io/docs/configuration.html#module-loaders/. Свойство test
на соответствие расширений, свойство include
для проверки путей (префиксы путей), exlude
те регулярные выражения или префиксы путей к которым loader не применим, свойство loader
– наименование loader.
a. Resolving
Resolve (модули вообще, например, “./home
”) и resolveLoader (для loader’ов) это пара настроек, которые описывают как webpack ищет модули.
b. Минификация
Для этого будем использовать плагин, который есть в webpack – UglifyJsPlugin
.
jQuery or Javascript
if (NODE_ENV == 'production') { // добавляем плагин для минификации
module.exports.plugins.push(
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
drop_console: true,
unsafe: true
}
})
);
}
3.1 Несколько точек входа
На текущей момент home.js собирается в build.js и все в одной директории (в реальности все по другому). Обычно есть директория, которая содержит файлы для разработки – frontend; и директория, в которой собирается проект – public.
Создадим еще 1 точку входа. Зачастую на сайте существует несколько разделов, например, поиск, магазин, о нас и т.д. Поэтому нам не нужно, чтобы 1 js файл содержал весь функционал сайта. То есть для каждого раздела будет загружаться свой необходимый функционал.
Работаем с config:
jQuery or Javascript
entry: {
home: "./frontend/home",
about: "./frontend/about",
},
output: {
path: __dirname + '/public', //абсолютный путь к директории сборки
filename: "[name].js", // шаблон вместо [name] при сборке будут подставлены home.js и about.js
library: "[name]", // аналогично: глобальные переменные home н about
//publicPath (см. ниже)
},
Все модули находятся в директории frontend. __dirname Чтобы не дублировать используем параметр context
:
jQuery or Javascript
context: __dirname + '/frontend',
Соответственно поиск будет идти относительно нее.
jQuery or Javascript
entry: {
home: "./home",
about: ". /about",
Если некий файл является точкой входа, то require
(nodejs) его ниоткуда нельзя.
3.2 NoErrorsPlugin
При сборке, даже с ошибками webpack выдает файлы. NoErrorsPlugin – то есть сборки не будет, если будут ошибки.
3.3 CommonChunkPlugin
Выделим из наших точек входа общую часть. Ведь есть код, в нашем случае это welcome
, который нужен на всех страницах сайта. Для этого воспользуемся плагином:
jQuery or Javascript
webpack.optimize.CommonsChunkPlugin()
new webpack.optimize.CommonsChunkPlugin({
name: "common"
})
Где name
- имя нового кусочка сборки.
D:\ggl\webpack\project>webpack Hash: d3cfa8b8c434526d27d9 Version: webpack 1.12.14 Time: 3790ms Asset Size Chunks Chunk Name about.js 713 bytes 0 [emitted] about home.js 727 bytes 1 [emitted] home common.js 10.2 kB 2 [emitted] common
Далее при сборке появится 3 файла about.js, home.js, common.js. common.js содержит те модули, которые используются во всех точках входа. Соответственно, для всех точек входа нужно подключить общий код:
HTML
<script src="common.js"></script>
<script src="about.js"></script>
3.4 Информация о сборке
Чтобы посмотреть подробнее какой модуль куда пошел можно запустить:
webpack --display-modules
Asset Size Chunks Chu about.js 713 bytes 0 [emitted] abo home.js 727 bytes 1 [emitted] hom common.js 10.2 kB 2 [emitted] com [0] ./about.js 97 bytes {0} [built] [0] ./home.js 96 bytes {1} [built] [1] ./welcome.js 463 bytes {2} [built]
About.js пошел в 0 ({0}
) фрагмент сборки. home.js в {1}
.
еще детальнее:
webpack --display-modules -v
С помощью красивой графической утилиты:
webpack --json --profile >stats.json
этим мы создадим файл stats.json. Далее заходим на http://webpack.github.io/analyse/ и подставляем stats.json.
3.5 Настройки CommonsChunkPlugin
http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin
name
– имяfilename
- имя файла, в который запишется общий код. По умолчанию:
filename: “[name].js”
minChunks
– по умолчанию CommonsChunkPlugin смотрит все точки входа и выносит то что используется везде. Если 5 точек входа, а код используется в 6-ти, тоCommonsChunkPlugin
его не вынесет. Но, Мы можем указать, например,minChunks: 2
иCommonsChunkPlugin
вынесет те модули, который используется хотя бы в 2-х точках сборки.chunks
– можем указать явно из каких модулей стоит выносить:
jQuery or Javascript
new webpack.optimize.CommonsChunkPlugin({ name: "common", chunks: ['about', 'home'] })
common
– общий код из модулей 'about' и 'home'. Мы можем использовать (вызывать)CommonsChunkPlugin
сколько угодно раз.
И т.д.
3.6 Общий код в commons.js
Зачастую в общий файл (common.js) нужно добавить какие-то свои вызовы: например, подключение и инициализацию библиотек, иниц-ю формы входа на сайт, или что-то еще, то есть код, которой нужен везде. Сделаем это:
Создадим одноименный (name: "common"
в CommonsChunkPlugin
) файл common.js, добавим в него свой код, добавим его как точку входа. В него попадет и ваш код и общий код.
Можно явно указать какие модули следует добавить в общую сборку:
jQuery or Javascript
common: ["./common", ".welcome"]
// примечательно, что экспортирован будет только последний модуль (welcome)
// webpack включит в общую сборку и "./common" и ".welcome"
3.7 Мульти-компиляция
Мульти-компиляция
– мы рассматривали ситуацию, когда есть несколько точек входа, а все остальные параметры одинаковые. Но бывают ситуации, когда другие параметры разные, например, много сборок для различных языков, для браузеров. Для того чтобы делать несколько сборок, в которых параметры различаются, используют Мульти-компиляцию – module.exports
экспортирует не 1 объект, а массив объектов:
module.exports = [{ }, { }, { }]
Инструкция try-catch
jQuery or Javascript
try{
// код, который может привести к ошибке
} catch (error) {
// действия при возникновении ошибки
}
jQuery or Javascript
try{
someFunction();
} catch (error) {
console.log(error.message);
}
Если в разделе try
происходит ошибка, выполнение кода прекращается и возобновляется в блоке catch
, в который передается объект с информацией об ошибке.
Объект error
зависит от браузера, но в нем как минимум присутствует свойство message
(сообщение об ошибке), а также: свойство name
(тип ошибки); number
– внутренний номер шибки; и т.д.
4.1 Продвинутый require – Динамический require
Запускаем в папке public
: static
У нас есть приложение и оно хочет подключить модуль login
не сразу, а лишь при необходимости (например, при нажатии на кнопку loginButton
).
Есть два способа, чтобы выделить модуль и подключить его при необходимости:
1 способ – функция webpack: require.ensure
1 аргумент – массив с модулями, webpack сгенерирует из них отдельный фрагмент сборки, и при запуске приложения, если выполнение пройдет в
callback require.ensure
, модули подгрузятся динамически и в callback мы сможем гарантировано подключить необходимые модули:
jQuery or Javascript
document.getElementById('loginButton').onclick = function() {
// ======== Способ 1 (require.ensure) ==
require.ensure([‘./login’], function(require) {
let login = require('./login');
login();
}, 'auth');
};
publicPath
– указывает, как из интернета получить наши файлы (интернет путь к нашей сборке). Webpack обязан его знать, так как скрипты подгружаются динамически.
Чтобы научить webpack делать запросы (для chunk loading и HMR, “Hot Module Replacement” https://webpack.github.io/docs/hot-module-replacement.html, HMR – особенность, вводить модули во время выполнения) для webpack-dev-server
требуется обеспечить полный URL посредством output.publicPath
.
2 способ – AMD синтаксис
jQuery or Javascript
require([‘./login’], function(login){
login();
});
AMD синтаксис немного уступает require.ensure
.
В нашем случае, если вы кликните по кнопке #loginButton
подгрузится скрипт 1.auth.js
. В require.ensure
в []
можно не прописывать модули, так как webpack анализирует содержимое webpack и все require
, которые находятся в callback, автоматически помещаются в []
.
5.1 Внешние библиотеки – CDN и сборка: externals
Рассмотрим подключение на примере библиотеки lodash. В приложении мы можем просто использовать глобальную переменную, которую создает библиотека. Для lodash это подчеркивание _
.
Например, функция _pluck
– создает из массива новый массив, содержащий определенной свойство.
Javascript
let users = [
{id: "abcd", name: "Vasya"},
{id: "defa", name: "Petya"},
{id: "1234", name: "Masha"}
];
console.log( pluck(users, 'name') );
Для того, чтобы работал следующий require:
Javascript
let _ = require(‘lodash’);
Несмотря на то что библиотека подключается посредством cdn. Мы должны в webpack.config прописать объект externals
, в котором ключами служат названия модулей, в нашем случае это lodash
, а значением глобальная переменная это подчеркивание _
.
Javascript
externals: {
lodash: "_"
}
Иначе при сборке будет ошибка.
Следовательно, когда мы let _ = require(‘lodash’);
вместо lodash
будет глобальная переменная _
.
5.2 Внешние библиотеки – Локально: ProvidePlugin
Рассмотрим более частую ситуацию: когда библиотека не в CDN, а органично включена в проект. В частности, когда она ставится как:
npm install lodash
Далее можно let _ = require('lodash');
и библиотека подключится. Однако сборка будет большого размера, почти 0.5Мб.
jQuery or Javascript
//let _ = require('lodash/compact');
let compact = require('lodash/compact');
let users = [
{id: "abcd", name: "Vasya"},
{id: "defa", name: "Petya"},
{id: "1234", name: "Masha"},
false
];
console.log( compact(users) );
А теперь укоротим наш код при помощи ProvidePlugin
. Многие библиотеки экспортируют глобальные переменные, которые нужны почти везде (_, $, angular
, etc). Если мы не хотим в начале каждого модуля прописывать let compact = require('lodash/compact');
то делаем следующее в webpack.config.js:
В plugins указываем объект new webpack.ProvidePlugin({ …. })
, в котором ключами будут переменные, например, compact
, а значением будут модули, например, 'lodash/compact'
.
Если webpack видит переменную (compact
), которая объявлена, но не используется, тогда ProvidePlugin
подключает соответствующий модуль('lodash/compact'
).
Можно подключить и весь lodash:
jQuery or Javascript
plugins: [
new webpack.ProvidePlugin({
compact: 'lodash/compact',
_: 'lodash'
})
]
Предпочтительнее использовать require
, но тем не менее для некоторых глобальных библиотек, которые используются везде ProvidePlugin
может быть хорошей опцией.
5.3 Внешние библиотеки – Оптимизация: noParse, include
При подключении сторонних библиотек у сборки могут возникнуть проблемы с производительностью.
При использовании angular и babel время сборки может составить:
Time: 3980ms
Информация о сборке
Команда
webpack --profile --display-modules
Покажет все модули сборки и сколько времени webpack потратил на каждый.
Более детальная информация о подключении:
webpack --profile --display-modules --display-reasons
Тормозы могут происходить из-за babel, так как библиотека angular не нуждается в обработке babel.
Исправим это:
jQuery or Javascript
loaders: [{
test: /\.js$/,
exclude: /\/node_modules\//,
loader: ‘babel’
}]
Теперь все файлы за исключением (exclude) папки node_modules будут обрабатываться babel.
Директива noParse
говорит о том, что библиотеку не надо анализировать (в ней нет require и т.д.) и это убыстрит время сборки. В качестве значения может быть массим из рег. Выражений, строка, рег. Выражение и т.д.
Например:
noParse: /angular\/angular.js/
5.4 Внешние библиотеки – Старые скрипты: пути и imports/exports
Иногда возникают ситуации, когда есть старый скрипт и нам нужно внести его в сборку.
Используем resolve
Сокращаем запись пути по alias к файлу vendor/old/dist/old.js
:
В webpack.config.js:
jQuery or Javascript
resolve: {
root: __dirname + '/vendor',
alias: {
old: 'old/dist/old'
}
}
Использование:
var old = require('old');
В old.js лежит глобальный функция Work:
jQuery or Javascript
function Work() {
setTimeout(function() {
alert("work complete!");
}, workSettings.delay);
}
Чтобы работать с такими скриптами у webpack есть: exports-loader и imports-loader
imports-loader
– позволяет импортировать переменную внутрь файла.
exports-loader
– позволяет экспортировать из файла одну или несколько переменных.
Синтаксис:
jQuery or Javascript
module: {
loaders: [{
test: /old.js$/,
loader: "imports?workSettings=>{delay:500}!exports?Work"
}]
},
Импортируем переменную workSettings
5.5 expose и script-loader
Expose-loader позволяет указать переменную и webpack сделает так, чтобы export этого модуля (./file.js) попал в глобальную переменную с таким названием (XMODULE):
require(“expose?XMODULE!./file.js”)
script-loader
– позволяет подключить скрипт в глобальном контексте. Лучше не использовать.
6.1 Стили и файлы
Style-loader добавляет некоторый css в DOM через тег style
.
Интеграция в нашу сборку шаблонов, стилей, картинок и других ресурсов. В нашем случае компонентный подход реализован для меню. Все что касается меню находится в папке menu
. Задача webpack собрать main.js
, подтянуть все что необходимо для меню, скопировать в папку public
.
Импортируем css:
import './menu.css';
С точки зрения webpack все является модулями. для файла css webpack ищет Loader, при отсутствии оного webpack будет пытаться разобрать css как js-файл и возникнет ошибка.
jQuery or Javascript
{
test: /\.css$/,
loader: 'style!css!autoprefixer?browsers=last 2 versions'
}
Здесь подключаются 3 loader: style-loader, css-loader, autoprefixer-loader.
6.2 Стили и файлы - CSS и file-loader
css-loader играет важную в роль: в css могут быть и import и url, то есть css-loader все import и url заменят на require.
Для статически файлов используется file-loader
:
jQuery or Javascript
{
test: /\.(png|jpg|svg|ttf|eot|woff|woff2)$/,
loader: 'file?name=[path][name].[ext]'
}
file-loader
копирует статичные файлы в public (смотри конфиг) и возвращает путь к нему. Как и куда копируется определяется параметром name.
В node.js если пути не указываются, то подразумевается node_modules. Чтобы брать стили из модуля (то есть в папке node_modules, там где лежит модуль) можно подключить стили так: @import(“~flag-icon-css/css/flag-icon.css”);
используется тильда.
.......................
По материалам курса Ильи Кантора webpack
Комментарии к статье
Пиши хотя бы источник, что взял курсы от Кантора Ильи.
alex, глаза разуй неуч
Вопрос по пункту 6.2. Типичная ситуация: нужно в модуль подтянуть одну из картинок, находящуюся в папке проекта, путь из модуля ('../images/'+imgName+'.png'). Я установил file-loader, прописал лоадер в конфигурации как сказано выше, но почему-то выдает GET http://localhost:3000/images/10n.png 404 (Not Found) - путь к картинке отображается некорректно. Как правильно подключать?