- PVSM.RU - https://www.pvsm.ru -
После моего недавнего выступления на MoscowJS #17 с одноимённым докладом [1] у многих возник интерес к этому инструменту. В рамках 11-го выпуска RadioJS, Миша Башкиров bashmish [2] рассказал, что решился попробовать его в своём новом проекте, об успешном опыте и множестве положительных эмоций. Но были озвучены вопросы и возникла дискуссия, в результате которой я решил написать эту статью, чтобы раскрыть основные тезисы с доклада и рассказать о том, что тогда не успел.
Статья ориентирована, как на профессионалов, так и на тех, кто с похожими технологиями ещё не сталкивался.
Итак, начнём.
Современный мир веб-разработки открывает перед нами, как невероятные возможности, так и проблемы. По масштабу их можно разделить на два:
Как это отражается в реальной работе?
Наиболее оптимальное решение — разбивать код на изолированные модули. Исторически для этого сложилось два подхода — AMD и CommonJS. О них уже многое писали и говори, но приведу краткий обзор (кто знаком — может пропустить).
Сводится к определению модуля через define() и вызову через require():
define(['jquery', 'products'], function ($, products) {
return {
show: function () {
products.forEach(function (item) {
$('.items').append(item.html);
});
}
};
});
Достаточно удобно. Но при росте зависимостей это превращается в спагетти-код. Поэтому разработчики добавили CommonJS-обёртку:
define(function (require, module, exports) {
var $ = require('jquery');
var products = require('products');
module.exports = {
show: function () {
products.forEach(function (item) {
$('.items').append(item.html);
});
}
};
});
Подробней об этом хорошо изложено в статье [3] от clslrns [4].
Нативно реализован на серверном JavaScript в Node.js/Rhino.
Сводится к определению модуля через глобальную переменную и вызову через require():
var $ = require('jquery');
var products = require('products');
module.exports = {
show: function () {
products.forEach(function (item) {
$('.items').append(item.html);
});
}
};
Основные преимущества перед AMD:
var _ = require('lodash');
var data = require('./stock.json');
module.exports = _.where(data, function (item) {
return item['count'] > 0;
});
Подробней об этом рассказывал [5] в своём докладе Антон Шувалов из Rambler.
Оба эти подхода позволяют:
Так что же теперь? Что воплотит наши фантазии в жизнь? Что лучшее мы имеем на сегодняшний день?
Вы только представьте. Любая логика. Любые форматы. Ваш проект быстро собирается. Ваш проект быстро загружается. Вы имеете самые развитые инструменты разработки. А теперь давайте взглянем подробней.
Для эксперимента, создадим директорию src и в ней простой скрипт index.js:
console.log('Hello Habrahabr!');
В директории assets будет наши выходные файлы. Это те самые файлы, которые мы можем выложить на наш веб-сервер, CDN и т.д.
Глобально есть две стратегии использования webpack:
npm install webpack -g
webpack src/index.js assets/bundle.js
Если ещё не сделали это ранее — самое время начать: устанавливаем Node.js [6] и npm [7]. После этого в директории проекта создаём package.json. Это можно сделать командой:
npm init
Теперь, когда у нас есть npm, добавляем к проекту webpack:
npm install webpack —save-dev
Для сборки проекта создаём Node.js-скрипт. Например, build.js:
var path = require('path');
var webpack = require('webpack');
var config = {
context: path.join(__dirname, 'src'), // исходная директория
entry: './index', // файл для сборки, если несколько - указываем hash (entry name => filename)
output: {
path: path.join(__dirname, 'assets') // выходая директория
}
};
var compiler = webpack(config);
compiler.run(function (err, stats) {
console.log(stats.toJson()); // по завершению, выводим всю статистику
});
Запускаем сборку:
node build
В обоих случаях мы получаем директорию assets и в ней bundle.js — это наш собранный файл, где будет сам index.js и все подключаемые им зависимости. В примере выше, он у меня был размером 1528 байт.
Для его использования нам не нужен никакой дополнительный загрузчик, поэтому достаточно подключить только этот файл:
<!doctype html>
<html>
<body>
<script src="assets/bundle.js"></script>
</body>
</html>
Вот всё и заработало. Полноценно webpack может раскрыться, только через конфигурацию, поэтому далее я не буду приводить примеров для консоли, однако, открыть для себя всю магию консоли Вы без проблем можете в документации [8].
Одна из главных точек расширения webpack — это плагины. Они позволяют менять 'на лету' логику сборки, алгоритм поиска модулей, иными словами, залезать в самое сердце процесса сборки.
Подключение происходит через добавление секции plugins в передаваемых настройках.
Примеры плагинов:
Настройка (build.js):
...
plugins: [
new webpack.optimize.UglifyJsPlugin(),
...
После добавления этих строк, файл bundle.js уменьшается с 1528 до 246 байт.
Настройка (build.js):
...
plugins: [
DefinePlugin({
'NODE_ENV': JSON.stringify('production')
}),
...
Использование (index.js):
if (NODE_ENV === 'production') {
console.log('There is Production mode');
} else {
console.log('There is Development mode');
}
При сборке этот код будет собран в следующий вид:
if (true) {
console.log('There is Production mode');
} else {
console.log('There is Development mode');
}
А если включена минификация:
console.log('There is Production mode');
Это достаточно удобный функционал для того, чтобы разделять код на слои и делать продакшн-код ещё более чистым и быстрым.
Установка:
npm install bower-webpack-plugin --save-dev
Настройка (build.js):
...
plugins: [
new BowerWebpackPlugin({
modulesDirectories: ['bower_components'],
manifestFiles: ['bower.json', '.bower.json'],
includes: /.*/,
excludes: /.*.less$/
}),
...
Использование:
bower install jquery
index.js:
var $ = require('jquery');
if (NODE_ENV === 'production') {
$('body').html('There is Production mode.');
} else {
$('body').html('There is Development mode.');
}
Установка:
npm install css-loader style-loader extract-text-webpack-plugin—save-dev
Примечание: пакеты css-loader и style-loader — это загрузчики для загрузки и подключения в DOM стилей. Подробней о них речь пойдёт дальше.
Настройка (build.js):
...
module: {
loaders: [
{
test: /.css$/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
},
...
],
},
plugins: [
new ExtractTextPlugin('style.css'),
...
Использование:
h1 {
color: #036;
}
var $ = require('jquery');
require('./header.css');
$('body').prepend('<h1>Hello, Habrahabr</h1>');
body {
background: #eee;
}
var $ = require('jquery');
require('./index.css');
require('./header');
if (NODE_ENV === 'production') {
$('body').append('There is Production mode.');
} else {
$('body').append('There is Development mode.');
}
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="assets/styles.css" />
</head>
<body>
<script src="assets/bundle.js"></script>
</body>
</html>
Благодаря этой точке расширения webpack обеспечивает прозрачное подключение любой статики — CSS, LESS, SASS, Stylus, CoffeeScript, JSX, JSON, шрифтов, графики, шаблонов и т.д. Вы просто указываете имя файла в require(), а загрузчики сами обеспечивают все необходимые операции для его подключения.
Подключаете CSS? Загрузчики позаботятся обо всём сами — загрузят CSS-данные, а в момент исполнения добавят в DOM элемент с этими стилями.
Пишете модули в LESS, CoffeeScript или JSX? Загрузчики сами выполнят весь пре-процессинг при сборке — Вам достаточно просто указать имя файла.
Установка:
npm install style-loader css-loader json-loader handlebars-loader url-loader --save-dev
Настройка (build.js):
...
module: {
loaders: [
{test: /.css$/, loader: 'style-loader!css-loader'},
{test: /.json$/, loader: 'json-loader'},
{test: /.hbs$/, loader: 'handlebars-loader'},
{
test: /.(eot|woff|ttf|svg|png|jpg)$/,
loader: 'url-loader?limit=30000&name=[name]-[hash].[ext]'
},
...
Загрузчики указываются в порядке справа налево, т.е. для CSS — сначала используется css-loader, а его результат передаётся в style-loader, который помещает загруженные данные, как блок <style />.
url-loader — особенный загрузчик. В данном примере, если файлы графики и шрифтов имеют размер до 30кб, он вставляет их в виде data:uri. Если же они превышают этот объем, то он сохраняет их в выходную директорию с заданным шаблоном имени, где hash — уникальное значение для файла. Такой подход позволяет с одной стороны — избежать дополнительных обращений к серверу (даже при Keep-Alive, это дорогостоящая операция), с другой — избежать проблем с кэшированием одноимённых файлов (этот подход известен, как static freeze).
Использование:
{"name": "Habrahabr"}
<h1>Hello, dear {{name}}</h1>
var $ = require('jquery');
// загружаем данные из JSON-файла в объект:
var customer = require('./customer.json');
// загружаем и компилируем шаблон:
var Header = require('./header.hbs');
require('./header.css');
// отдаём данные в шаблон и выводим полученный HTML
$('body').prepend(Header(customer));
webpack отлично дружит с React и позже станет ясно почему, а пока просто приведу пример подключения JSX-скриптов.
Установка:
npm install react jsx-loader —save-dev
Настройка (build.js):
...
resolve: {
extensions: ['', '.js', '.jsx'],
},
module: {
loaders: [
{test: /.jsx$/, loader: 'jsx-loader'},
...
Использование:
var React = require('react');
module.exports = React.createClass({displayName: 'Toolbar',
render: function () {
return (
<div>
<button>Button 1</button>
</div>
);
}
});
var $ = require('jquery');
var React = require('react');
var Toolbar = require('./toolbar');
require('./index.css');
require('./header');
React.render(
React.createElement(Toolbar),
document.getElementById('toolbar')
);
if (NODE_ENV === 'production') {
$('body').append('There is Production mode.');
} else {
$('body').append('There is Development mode.');
}
<!doctype html>
<html>
<body>
<div id="toolbar"></div>
<script src="assets/bundle.js"></script>
</body>
</html>
Позволяет видеть обновления на странице без пересборки проекта. Просто, как магия.
Как это работает:
npm install webpack-dev-server -g
webpack-dev-server --content-base assets/
Установка:
npm install webpack-dev-server —save-dev
Использование:
var webpack = require('webpack');
var config = require('./webpack.config');
var compiler = webpack(config);
compiler.run(function () {
// stay silent
});
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config.js');
var devServer = new WebpackDevServer(
webpack(config),
{
contentBase: __dirname,
publicPath: '/assets/'
}
).listen(8088, 'localhost');
node dev-server
Перед нами появится index.html со строкой статуса в шапке: “App ready”. Если сейчам мы что-то изменим в коде, страница автоматически обновится.
Позволяет обновлять, удалять и добавлять модули в реальном режиме, т.е. даже без перезагрузки страницы (тем самым сохранив её состояние). Мало того, что это безумно весело, это позволяет на порядок быстрей прототипировать веб-приложения и разрабатывать сложные Single Page Applications.
Развёрнутый ответ автора на вопрос What exactly is Hot Module Replacement in Webpack? [11]
Как это работает в связке с React:
Подробней об этом можно почитать на странице разработчика расширения [12].
Или посмотреть на этом видео:
Невероятно, ведь так?
webpack позволяет разбивать собранный код на части. Например, Вы можете выделить общий код для всех страниц в assets/common.js, а для каждой отдельной страницы делать свой assets/feed.js, assets/products.js и т.д. Таким образом, при первой загрузке, common.js будет закеширован, а для каждой из страниц Вашего проекта будет достаточно догрузить небольшой файл с нужным для неё чанком. Забегая вперёд, Facebook использует порядка 50 чанков на странице выдачи, в то время, как Instagram в среднем по два, например — common.js и Feed.js.
По моим личным наблюдениям и наблюдением коллег производительность сборки у webpack на порядок выше аналогов. Во многом за счёт применения “умной” сборки и AST-парсинга.
Для тонкой и более эффективной оптимизации webpack предлагает развитую статистику по результату сборки Вашего проекта и инструменты для визуального анализа [13].
Итак, мы рассмотрели:
Помимо CommonJS, из коробки поддерживается и AMD — это позволяет быстро и безболезненно перебраться с Require.js.
Миграция с Browserify происходит также легко и волшебно, как и всё остальное — для этого в документации даже есть раздел webpack for Browserify users [14].
По личному опыту — на вопросы в github ответили в течении дня. Разработчики очень открытые и активные. Делал pull-request'ы — автор принял их на следующий день. Динамика каммитов на github впечатляет [15].
Безусловно. Например, команда Facebook использует webpack для веб-интерфейса Instagram. Если быть честным, то делая реверс-инжиниринг этого проекта я и наткнулся на webpack.
Кроме этого, Twitter использует webpack для своих проектов, о чём на конференции Fronteers 2014 рассказал [16] Nicholas Gallagher.
Призываю всех нас держать руку на пульсе. Мыслить глобально. Всегда развиваться и видеть, что творится в мире. Быть смелей, активней, экспериментировать. Изучать, как работают успешные проекты. Не держаться, а делиться и обмениваться своими открытиями, результатами и решениями. Это сделает каждого из нас быстрей, умней и эффективней.
Благодарю за внимание.
Документация по webpack — http://webpack.github.io/docs/ [17]
Скачать и попробовать — https://github.com/webpack/webpack [18]
Пример из статьи — https://github.com/DenisIzmaylov/hh-webpack [19]
Автор: DenisIzmaylov
Источник [20]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/77316
Ссылки в тексте:
[1] одноимённым докладом: http://www.slideshare.net/moscowjs/webpack-7-moscowjs-17
[2] bashmish: http://habrahabr.ru/users/bashmish/
[3] статье: http://habrahabr.ru/post/152833/
[4] clslrns: http://habrahabr.ru/users/clslrns/
[5] рассказывал: https://www.youtube.com/watch?v=89bZfKSvNGo&list=PL95OM-7UObpESHZ9PRm00Tke5VZ6ku6Qr
[6] Node.js: http://nodejs.org/
[7] npm: https://www.npmjs.com/
[8] документации: http://webpack.github.io/docs/cli.html
[9] bower: http://bower.io/
[10] http://localhost:8088/webpack-dev-server/: http://localhost:8088/webpack-dev-server/
[11] Развёрнутый ответ автора на вопрос What exactly is Hot Module Replacement in Webpack?: http://stackoverflow.com/questions/24581873/what-exactly-is-hot-module-replacement-in-webpack
[12] странице разработчика расширения: http://gaearon.github.io/react-hot-loader/
[13] инструменты для визуального анализа: http://webpack.github.io/analyse/
[14] webpack for Browserify users: http://webpack.github.io/docs/webpack-for-browserify-users.html
[15] впечатляет: https://github.com/webpack/webpack/graphs/contributors
[16] рассказал: https://github.com/web-standards-ru/web-standards-up/blob/master/2014-10-10_fronteers.md
[17] http://webpack.github.io/docs/: http://webpack.github.io/docs/
[18] https://github.com/webpack/webpack: https://github.com/webpack/webpack
[19] https://github.com/DenisIzmaylov/hh-webpack: https://github.com/DenisIzmaylov/hh-webpack
[20] Источник: http://habrahabr.ru/post/245991/
Нажмите здесь для печати.