Давайте сначала разберемся, зачем нужен вебпак (webpack), и какие проблемы он пытается решить, а потом научимся работать с ним. Webpack позволяет избавиться от bower и gulp/grunt в приложении, и заменить их одним инструментом. Вместо bower'а для установки и управления клиентскими зависимостями, можно использовать стандартный Node Package Manager (npm) для установки и управления всеми фронтэнд-зависимостями. Вебпак также может выполнять большинство задач grunt/gulp'а.
Bower это пакетный менеджер для клиентской части. Его можно использовать для поиска, установки, удаления компонентов на JavaScript, HTML и CSS. GruntJS это JavaScript-утилита командной строки, помогающая разработчикам автоматизировать повторяющиеся задачи. Можно считать его JavaScript-альтернативой Make или Ant. Он занимается задачами вроде минификации, компиляции, юнит-тестирования, линтинга и пр.
Допустим, мы пишем простую страницу профиля пользователя в веб-приложении. Там используется jQuery и библиотеки underscore. Один из способов — включить оба файла в HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>User Profile</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" media="screen">
<link rel="stylesheet" href="/css/style.css" media="screen">
</head>
<body>
<div class="container">
<div class="page-header">
<h1 id="timeline"></h1>
</div>
<ul class="timeline">
</ul>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="js/profile.js"></script>
</body>
</html>
Это простой HTML с Бутстрапом. Мы подключили jQuery и underscore с помощью тега script.
Давайте рассмотрим файл profile.js
, который использует подключенные нами библиотеки. Наш код лежит внутри анонимного замыкания, которое хранит бизнес-логику приложения. Если не замыкать код в функцию, то переменные будут находиться в глобальном окружении, и это плохо.
(function(){
var user = {
name : "Shekhar Gulati",
messages : [
"hello",
"bye",
"good night"
]
};
$("#timeline").text(user.name+ " Timeline");
_.each(user.messages, function(msg){
var html = "<li><div class='timeline-heading'><h4 class='timeline-title'>"+msg+"</h4></div></li>";
$(".timeline").append(html);
});
}());
Код будет исполнен при вызове скрипта. Если открыть страницу в браузере, то профиль будет выглядеть так.
У этого кода две задачи:
- получить информацию о пользователе
- настроить таймлайн.
Известно, что смешивать понятия — плохая практика, так что нужно написать отдельные модули, отвечающие за определенные задачи. В файле profile.js
мы использовали анонимное замыкание для хранения всего кода. В JavaScript есть способы делать модули получше. Два популярных способа это CommonJS и AMD.
- Модуль CommonJS это грубо говоря кусок повторно используемого кода, который экспортирует определенные объекты, и они становятся доступными другим модулям с помощью
require
. - Asynchronous Module Definition (AMD) позволяет загружать модули асинхронно.
Если хотите узнать о модулях в JavaScript больше, то советую прочитать статью JavaScript Modules: A Beginner’s Guide.
А в этой статье мы будем писать модули на CommonJS. Давайте напишем модуль timeline с методами для установки хедера и таймлайна. В CommonJS можно импортировать зависимости с помощью функции require
. Таймлайн зависит от jquery
и underscore
.
var $ = require('jquery');
var _ = require('underscore');
function timeline(user){
this.setHeader = function(){
$("#timeline").text(user.name+ " Timeline");
}
this.setTimeline = function(){
_.each(user.messages, function(msg){
var html = "<li><div class='timeline-heading'><h4 class='timeline-title'>"+msg+"</h4></div></li>";
$(".timeline").append(html);
});
}
}
module.exports = timeline;
Этот код создает новый модуль timeline
. Есть две функции: setHeader
и setTimeline
. Мы используем специальный объект module
и добавляем ссылку на нее в module.exports
. Таким образом мы сообщаем модульной системе CommonJS, что хотим позволить другим функциям использовать модуль.
Теперь обновим profile.js
, он должен использовать модуль timeline
. Можно создать новый модуль, который будет загружать информацию о пользователе, но пока давайте ограничимся одним модулем.
var timeline = require('./timeline.js');
var user = {
name : "Shekhar Gulati",
messages : [
"hello",
"bye",
"good night"
]
};
var timelineModule = new timeline(user);
timelineModule.setHeader(user);
timelineModule.setTimeline(user);
Если загрузить index.html в браузере, то отобразится пустая страница. В консоли (в developer tools) можно обнаружить ошибку:
profile.js:1 Uncaught ReferenceError: require is not defined
Проблема в том, что браузеры не понимают модульную систему вроде CommonJS. Нужно предоставить браузеру формат, который он ожидает.
Бандлеры модулей идут на помощь
Веб-браузеры не понимают эти хорошо описанные модули. Нужно или добавить весь JavaScript-код в один файл и импортировать его, или нужно добавить все файлы вручную на страницу с помощью тега script
. Используем бандлер модулей (module bundler) для решения этой проблемы. Бандлер модулей комбинируют разные модули и их зависимости в один файл в правильном порядке. Он может парсить код, написанный с использованием разных модульных систем, и комбинировать в один формат, понятный браузеру. Два популярных бандлера модулей это:
- browserify: пакует npm-модули, чтобы потом использовать их в браузере. В случае с browserify приходится дополнительно подключать Grunt или Gulp для линтинга, запуска тестов и пр. Это значит, что нужно тратить время на работу с несколькими инструментами и интеграцией.
- webpack: система сборки, которая предоставляет не только бандлинг (компоновку) модулей, но и может выполнять задачи, которыми занимаются Gulp/Grunt. К тому же, вебпак не ограничивается JavsScript-файлами, он может работать с другой статикой вроде CSS, картинок, html-компонентов и др. Вебпак также поддерживает очень полезную фичу —
code splitting
(разбиение кода). Большое приложение можно разбить на куски, которые загружаются по мере необходимости.
Что такое вебпак?
Официальное определение звучит так:
webpack берет модули с зависимостями и генерирует статические ресурсы, которые представляют эти модули
Это определение теперь имеет смысл, когда понятна решаемая проблема. Вебпак берет набор ресурсов и трансформирует их в наборы (бандлы).
Трансформацией ресурсов занимаются загрузчики, которые являются сердцем вебпака.
Вебпак в действии
Для установки вебпака нужен node. Можно скачать node с официального сайта.
Теперь можно установить вебпак глобально:
$ npm install -g webpack
Создайте новый модуль командой npm init
. Она создаст файл package.json
. Установите зависимости с помощью npm.
$ npm install -S jquery
$ npm install -S underscore
В дополнение, нужно установить вебпак как зависимость.
$ npm install -S webpack
Замените index.html
следующим кодом. Как видите, мы удалили все теги script для jquery и underscore. Также, вместо импорта js/profile.js
импортируется dist/bundle.js
.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>User Profile</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" media="screen">
<link rel="stylesheet" href="/css/style.css" media="screen" title="no title">
</head>
<body>
<div class="container">
<div class="page-header">
<h1 id="timeline"></h1>
</div>
<ul class="timeline">
</ul>
</div>
<script src="dist/bundle.js" charset="utf-8"></script>
</body>
</html>
Давайте создадим бандл.
$ webpack js/profile.js dist/bundle.js
Hash: 6d83c7db8ae0939be3d0
Version: webpack 1.13.2
Time: 350ms
Asset Size Chunks Chunk Names
bundle.js 329 kB 0 [emitted] main
[0] ./js/profile.js 252 bytes {0} [built]
[1] ./js/timeline.js 427 bytes {0} [built]
+ 2 hidden modules
Теперь страница работает нормально.
Можно сделать так, чтобы вебпак следил за изменениями и генерировал бандл автоматически. Для этого нужно запустить его с таким флагом:
$ webpack -w js/profile.js dist/bundle.js
Теперь вебпак не будет завершаться сам. При изменении файлов будет генерироваться новый бандл. Нужно лишь перезагрузить страницу в браузере. Давайте изменим profile.js
:
var user = {
name : "Shekhar Gulati!!!",
messages : [
"hello",
"bye",
"good night"
]
};
Файл bundle.js
, сгенерированный вебпаком, содержит много кода, относящегося к самому вебпаку, а ваш код там будет в измененном виде. Будет очень неудобно отлаживать приложение в браузере, в инструментах разработчика, например. Чтобы упростить себе жизнь, можно запустить вебпак с флагом devtools.
$ webpack -w --devtool source-map js/profile.js dist/bundle.js
Вебпак сгенерирует source map для файла bundle.js. Source map связывает минимизированный и собранный в один файл код с исходным, несобранным кодом. Для тестирования можно добавить строчку debugger
в profile.js
var timeline = require('./timeline.js');
var user = {
name : "Shekhar Gulati",
messages : [
"hello",
"bye",
"good night"
]
};
debugger;
var timelineModule = new timeline(user);
timelineModule.setHeader(user);
timelineModule.setTimeline(user);
Перезагрузите страницу, и приложение остановится на этой строке.
Добавление CSS
В HTML выше видно, что мы загружаем /css/style.css
. Вебпак умеет работать не только с JavaScript, но и с другой статикой, в том числе CSS. Удалите строку с /css/style.css
из index.html
. Мы будем подключать стили в profile.js
таким образом:
require('../css/style.css');
var timeline = require('./timeline.js');
var user = {
name : "Shekhar Gulati",
messages : [
"hello",
"bye",
"good night"
]
};
var timelineModule = new timeline(user);
timelineModule.setHeader(user);
timelineModule.setTimeline(user);
вебпак перезагрузит изменения, и мы увидим сообщение об ошибке в консоли:
ERROR in ./css/style.css
Module parse failed: /Users/shekhargulati/dev/52-technologies-in-2016/36-webpack/code/css/style.css Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
Проблема в том, что вебпак не понимает CSS по умолчанию. Нужно установить пару загрузчиков для этого. Вот команда для установки необходимых загрузчиков:
$ npm install style-loader css-loader --save-dev
Вебпак использует загрузчики для трансформации текста в нужный формат. Теперь нужно обновить require
:
require('style!css!../css/style.css');
Синтаксис style!css!
означает, что сначала нужно применить трансформацию css
для конвертации текста из style.css
в CSS, а потом применить стиль к странице с помощью трансформации style
.
Запустите вебпак снова.
$ webpack -w --devtool source-map js/profile.js dist/bundle.js
Конфигурация
Чтобы не указывать все опции в командной строке, можно создать конфигурационный файл webpack.config.js
в корне приложения:
module.exports = {
context: __dirname,
devtool: "source-map",
entry: "./js/profile.js",
output: {
path: __dirname + "/dist",
filename: "bundle.js"
}
}
Теперь можно запускать вебпак простой командой webpack -w
.
Когда мы добавили style!css!
в profile.js
, мы смешали продакшен-код с конфигурацией вебпака. Можно перенести эту опцию в файл конфигурации.
После изменений конфигурации нужно перезапускать вебпак.
var webpack = require('webpack');
module.exports = {
context: __dirname,
devtool: "source-map",
entry: "./js/profile.js",
output: {
path: __dirname + "/dist",
filename: "bundle.js"
},
module:{
loaders: [
{test : /.css$/, loader: 'style!css!'}
]
}
}
Самая интересная секция тут это декларация модулей. Тут мы указали, что если файл заканчивается на .css, то нужно применять трансформацию style!css!
.
Горячая перезагрузка
Для горячей перезагрузки (hot reloading) нужен webpack-dev-server
. Установите его так:
$ npm install -g webpack-dev-server
Теперь можно запускать сервер командой webpack-dev-server
.
Мы запустим сервер по адресу http://localhost:8080/webpack-dev-server/ с конфигурацией из webpack.config.js
.
Порт можно изменить опцией --port
.
$ webpack-dev-server --port 10000
http://localhost:10000/webpack-dev-server
Конфигурацию webpack-dev-server
также можно указать в файле webpack.config.js
, в секции devServer
.
module.exports = {
context: __dirname,
devtool: "source-map",
entry: "./js/profile.js",
output: {
path: __dirname + "/dist",
filename: "bundle.js"
},
module:{
loaders: [
{test : /.css$/, loader: 'style!css!'}
]
},
devServer: {
inline:true,
port: 10000
},
}
На сегодня все. Узнать больше о вебпаке можно из документации.
Автор: freetonik