http://habrahabr.ru/post/224825/
Введение
Решения, написанные на JavaScript становятся сложнее из года в год. Это, несомненно, обусловлено разрастанием такого прекрасного зверя, как веб. Многие из нас сейчас работают с JavaScript модулями — независимыми функциональными компонентами, которые собираются вместе и работают как единое целое. Так же такой подход позволяет нам реализовать взаимозаменяемость компонентов, не прикончив попутно код. Многие из нас использовали для этого паттерн AMD и его реализацию в RequireJS.
В последнем году Browserify вышел на арену и превзошел все ожидания. Как только сообщество начало потихоньку успокаиваться, я решил написать обзор Browserify — что он из себя представляет, и как его можно использовать в вашем рабочем процессе.
Что такое Browserify?
Browserify позволяет вам использовать стиль node.js модулей для работы в браузере. Мы определяем зависимости и потом Browserify собирает их в один маленький и чистенький JavaScript файл. Вы подключаете ваши JavaScript файлы используя
require("./ваш_файл.js");
выражение. Также вы можете использовать публичные модули из npm. Для Browserify не составляет никакого труда создание source map'ов (карт исходных файлов до компрессии), так что даже не смотря на конкатинацию, вы сможете отлаживать отдельные части пакета ровно так же, как вы и привыкли это делать с отдельными файлами.
Зачем импротировать node-модули?
Импортирование модулей — это как благословение: вместо того, чтобы шерстить сайты в поисках ссылок на скачку той или иной JavaScript библиотеки — вы просто подключаете их используя
require()
(предварительно проверив, что они установлены через npm) и всё! Вы также можете использовать такие популярные JavaScript библиотеки, как
jQuery,
Underscore,
Backbone и даже
Angular (неофициальный порт). Они все доступны для скачивания и работы через npm. Если вы работаете над сайтом, который уже использует node, вы просто упрощаете себе разработку, ведь теперь вы можете использовать общую архитектуру всех ваших JS скриптов. Мне действительно нравится такой подход!
Что вам потребуется
Чтобы начать работать с Browserify, вам необходимо иметь следующее:
node.jsnpm – по умолчанию поставляется с node.js
Browserify – я объясню, как его установить
Ну, и, соответственно, набор JS файлов, с которыми вы хотите работать.
Начало работы
Чтобы начать работу, вам необходимо иметь установленные
node и
npm. Если вы уж совсем застряли, попробуйте эти
инструкции по установке Node.js через менеджер пакетов. Вам не придется выполнять никаких дополнительных телодвижений, чтобы начать использовать Browserify. Мы используем node исключительно потому, что npm работает поверх него. Как только вы получили npm, вы можете установить Browserify используя следующую команду:
npm install -g browserify
Давайте я немного поясню, что мы здесь делаем: мы используем npm для установки Browserify в глобальное окружение на вашей машине (флаг
-g
говорит npm установить модуль глобально). Если вы в результате получаете проблему, схожую со следующей:
Error: EACCES, mkdir '/usr/local/lib/node_modules/browserify'
То, скорее всего, вы нарвались на отсутствие доступа к нужной папке. Вы можете запустить команду, используя sudo, но я бы всё-таки рекомендовал вам сначало ознакомиться с этим постом.
Создаем ваш первый Browserify файл
Давайте начнем с создания файда, который мы будем обрабатывать с помощью Broserify. Например, давайте возьмем суперпопулярный модуль
Underscore. Мы будем использовать его для поиска супермена. Я назвал мой JS файл
main.js
и положил его в папку
js
.
Начнем с резервирования переменной
_
под Underscore, используя метод
require()
Browserify'а:
var _ = require('underscore');
Теперь, мы будем использовать функции
each()
и
find()
из подключенной нами библиотеки Underscore, Мы произведем поиск в двух массивах имен и на каждой итерации будем выводить значение выражения условия поиска супермена в консоль с помощью
console.log
. Лекс Лютер может только мечтать об этом. Наш конечный вариант кода будет выглядеть как-то так:
var _ = require('underscore'),
names = ['Bruce Wayne', 'Wally West', 'John Jones', 'Kyle Rayner', 'Arthur Curry', 'Clark Kent'],
otherNames = ['Barry Allen', 'Hal Jordan', 'Kara Kent', 'Diana Prince', 'Ray Palmer', 'Oliver Queen'];
_.each([names, otherNames], function(nameGroup) {
findSuperman(nameGroup);
});
function findSuperman(values) {
_.find(values, function(name) {
if (name === 'Clark Kent') {
console.log('It\'s Superman!');
} else {
console.log('... No superman!');
}
});
}
Мы бы хотели быть уверены, что Browserify сможет найти npm модуль, когда попробует добавить его в проект. Что ж, для того, чтобы это сделать, вам необходимо открыть терминал, перейти в директорию, в которой лежит ваш JavaScript проект, и запустить команду установки Underscore в эту директорию:
npm install underscore
Для тех, кто не знаком с механизмом работы node и npm поясню, что этот код создаст директорию
node_modules
в папке проекта. В этой директории будет располагаться ваш модуль Underscore. Команда получит последнюю версию Underscore из npm-репозитория (
https://registry.npmjs.org/underscore). С этим модулем в нашей директории
node_modules
, Browserify сможет легко найти и использовать его.
Запуск Browserify в первый раз
Когда мы запустим Browserify, он захочет собрать новый JavaScript файл со всеми прикрепленными к нему модулями. В нашем случае, он соберет JavaScript модуль с Underscore внутри. Нам потребуется только выбрать имя для нашего нового фалйа. Я, например, решил назвать его
findem.js
. Я запускаю команду из корневой папки проекта:
browserify js/main.js -o js/findem.js -d
Это команда считывает ваш main.js и пишет его содержимое в
findem.js
(разумеется, включая пакетные зависимости.
прим. пер.), который был указан с помощью опции
-o
. Я включил в запрос еще и опцию
-d
, поэтому наша команда вдобавок еще и сгенерирует source map для нашего файла. Благодаря этому, мы сможем отлаживать
underscore
и
main.js
как два отдельных файла.
Использование выходного файла Browserify
Подключается выходной Browserify файл ровно так же, как и любой другой JS файл:
<script src="js/findem.js"></script>
Импротирование ваших собственных JavaScript файлов
Совсем не здорово, если наше приложение будет состоять из одних папок
node_modules
. Чтобы подключить ваш собственный JavaScript код, вы можете использовать тот же подход с функцией
require()
. Данная строка кода импортирует JS файл с именем
your_module.js
в переменную
greatestModuleEver
:
greatestModuleEver = require('./your_module.js');
Чтобы импортировать ваш JavaScript таким образом, мы должны оформить наш JavaScript код как модуль. Чтобы это сделать, определить
module.exports
. Один из способов это сделать показан ниже.
module.exports = function(vars) {
// Ваш код
}
Примечание!Если вы используете ряд готовых сторонних библиотек, которых нет в npm и вы хотите найти простое решение, как их можно добавить в Browserify, то вам стоит взглянуть на npm-модуль Browserify-shim. Он сделает всю работу за вас. Мы не будем использовать его в данной статье, но некоторым разработчикам эта информация может пригодиться.Наш пример с модулем
Чтобы нагляднее показать, как это работает, мы вынесем наши массивы из прошлого супергеройского примера и расположим их в отдельном JS модуле, который вернет нам массив имен. Модуль будет выглядеть следующим образом:
module.exports = function() {
return ['Barry Allen', 'Hal Jordan', 'Kara Kent', 'Diana Prince', 'Ray Palmer', 'Oliver Queen', 'Bruce Wayne', 'Wally West', 'John Jones', 'Kyle Rayner', 'Arthur Curry', 'Clark Kent'];
}
Что ж, теперь мы импортируем этот модуль в нашу переменную
names = require("./names.js");
:
var _ = require('underscore'),
names = require('./names.js');
findSuperman(names());
function findSuperman(values) {
_.find(values, function(name) {
if (name === 'Clark Kent') {
console.log('It\'s Superman!');
} else {
console.log('... No superman!');
}
});
}
Наша переменная
names
ссылается на экспортированную функцию из модуля. Поэтому, мы используем переменную
names
как функцию, чтобы передать нужный нам массив в функцию
findSuperman()
.
Запустите эту
browserify
команду ещё раз, чтобы скомпилировать код, а потом откройте его в вашем браузере. Он должен работать, как и ожидалось, итерируя каждое значение массива и логируя, является ли человек из списка имен суперменом или нет:
Passing in Variables and Sharing Modules Across Our App
Давайте немного усложним наше простенькое приложение поиска супермена — вынесем функцию
findSuperman()
в отдельный модуль. Таким образом, мы как бы теоретически можем найти супермена из разных частей нашего приложения, и мы всегда сможем заменить наш модуль поиска супермена более эффективным, если это потребуется в будущем.
Мы так же можем передавать переменные в наши модули и использовать их в нашей возращаемой функции (
module.exports
), и чтобы это проиллюстрировать, мы создадим файл
findsuperman.js
, который будет принимать массив имен:
module.exports = function (values) {
var foundSuperman = false;
_.find(values, function(name) {
if (name === 'Clark Kent') {
console.log('It\'s Superman!');
foundSuperman = true;
} else {
console.log('... No superman!');
}
});
return foundSuperman;
}
Я добавил возвращаемое значение для нашей функции
findSuperman()
. Если она найдет супермена, вернет
true
, в противном случае —
false
. А дальше пускай решает код, который вызывает этот модуль. Но как бы то нибыло, мы упустили одну вещь: наш код использует Underscore, но мы не объявили его. После добавления Underscore, наш код принял такой вид:
var _ = require('underscore');
module.exports = function (values) {
...
}
Когда мы используем Browserify, он просматривает все наши JS файлы на предмет импорта модулей, строит дерево зависимостей, и это позволяет ему подключать модуль всего один раз, т.е. в нашем примере, мы подключаем Underscore в главном файле, и так же подключаем его в
findsuperman.js
, но когда Browserify соберет это всё воедино, он добавит его всего один раз. Круто, не правда ли?
Наше нынешнее JavaScript приложение в данный момент использует наш новый модуль с новым возвращаемым значением
true/false
. В случае нашего примера, мы просто используем
document.write
чтобы сказать, смогли мы найти супермена в списке имен, или же нет:
var _ = require('underscore'),
names = require('./names.js'),
findSuperman = require('./findsuperman.js');
if (findSuperman(names())) {
document.write('We found Superman');
} else {
document.write('No Superman...');
}
Нам даже больше не требуется подключать Underscore в наш главный файл, поэтому мы можем смело удалить эту строчку, он по-прежнему будет подключен в конце файла
findsuperman.js
.
Управление npm-зависимостями Browserify через package.json
Предположим, у вас есть друг, который так же хотел бы использовать ваш код. Будет довольно глупо ожидать от него, что он откуда-то узнает, что для работы вашего модуля ему сперва потребуется подключить underscore. Чтобы решить эту проблему, мы можем создать файл под названием
package.json
в корневой директории нашего проекта. Этот файл дает вашему проекту имя (убедитесь в отсутствии пробелов), описание, устанавливает ему автора и версию, и, что самое главное, npm зависимости. Те же, кто уже сталкивался с разработкой под node — мы используем тот же механизм:
{
"name": "FindSuperman",
"version": "0.0.1",
"author": "Patrick Catanzariti",
"description": "Code designed to find the elusive red blue blur",
"dependencies": {
"underscore": "1.6.x"
},
"devDependencies": {
"browserify": "latest"
}
}
Список зависимостей в данный момент ограничен нашем
"underscore": "1.6.x"
, где первая часть зависимости — это имя, и вторая — версия.
"lastest"
или
"*"
позволит вам получить самую последнюю версию, которая есть в npm. Также, вы можете указать номер версии как
1.6
(фиксированным числом) или
1.6.х
(для версий от
1.6.0
до
1.7
, не включительно).
Мы также можем подключить browserify как зависимость, но т.к. это не зависимость, необходимая для запуска проекта — любой пользователь сможет найти файл
findsuperman.js
без необходимости прогона Browserify. Но мы подключим его как одну из
devDependencies
— модулей, необходимых разработчикам, чтобы поддерживать приложение.
Теперь, когда у нас есть файл
package.json
, нам не придется заставлять нашего друга запускать
npm install underscore
. Он может просто запустить
npm install
и все необходимые зависимости будут установлены в директорию
node_modules
.
Автоматизация процесса Browserify
Запускать команду
browserify
каждый раз безумно нудно, не говоря уже о том, что дико неудобно. К счастью, есть несколько способов автоматизировать работу с Browserify.
npm
npm сам по себе может запускать консольные скрипты, как если бы вы печатали их самостоятельно. Чтобы это сделать, просто создайте раздел
scripts
в
package.json
. Это делается следующим образом:
"scripts": {
"build-js": "browserify js/main.js > js/findem.js"
}
Чтобы теперь всё это запустить, вам достаточно написать в командной строке:
npm run build-js
Но это тоже не особо удобно. Нам по-прежнему требуется запускать эту команду каждый раз. И это весьма раздражает. Лучше всего использовать npm модуль
watchify. Watchify прост, лёгок и сэкономит вам кучу времени. Он будет отслеживать изменения в ваших JS скриптах и автоматически перезапускать Browserify.
Теперь добавим его в
package.json
к
devDependencies
, и не забудем прописать дополнительную строку в разделе
scripts
(она будет напрямую запускать наш модуль
watchify
, если не требуется пересборка пакетов).
"devDependencies": {
"browserify": "latest",
"watchify": "latest"
},
"scripts": {
"build-js": "browserify js/main.js > js/findem.js",
"watch-js": "watchify js/main.js -o js/findem.js"
}
Чтобы это запустить, просто напишите команду:
npm run watch-js
Это будет работать как по волшебству. У этой команды почти нет консольного вывода, что может ввести вас в заблуждение, но будьте уверены — команда работает правильно. Если вы все-таки очень хотите получать больше информации о работе процесса — вы можете добавить флаг
-v
в вашу watchify команду:
"watch-js": "watchify js/main.js -o js/findem.js -v"
Данный модуль будет оповещать вас каждый раз, когда изменения в вашем файле будут сохранены:
121104 bytes written to js/findem.js (0.26 seconds)
121119 bytes written to js/findem.js (0.03 seconds)
Генерируем Source Maps в npm
Чтобы сгенерировать source maps используя npm, добавьте
-d
после вашей команды
browserify
(или
watchify
):
"scripts": {
"build-js": "browserify js/main.js > js/findem.js -d",
"watch-js": "watchify js/main.js -o js/findem.js -d"
}
Для того, чтобы иметь оба (один для дебага, другой для продакшена) файла, вам надо написать что-то вроде этого:
"watch-js": "watchify js/main.js -o js/findem.js -dv"
Grunt
Множество людей (да и я в том числе) уже знакомы с
Grunt, и, к счастью, продолжают его использовать. К счастью потому, что Browserify прекрасно работает с этой билд-системой!
Нам потребуется изменить наш
package.json
, добавив туда директиву на подключение Grunt. Мы больше не будем использовать секцию
scripts
, это переляжет на плечи Grunt'a:
{
"name": "FindSuperman",
"version": "0.0.1",
"author": "Patrick Catanzariti",
"description": "Code designed to find the elusive red blue blur",
"dependencies": {
"underscore": "1.6.x"
},
"devDependencies": {
"browserify": "latest",
"grunt": "~0.4.0",
"grunt-browserify": "latest",
"grunt-contrib-watch": "latest"
}
}
Мы добавили сл. зависимости в наш проект:
grunt – чтобы убедиться, что у нас установлен Grunt для нашего проекта.
grunt-browserify – модуль, который позволит вам использовать browserify внутри вашего проекта.
grunt-contrib-watch – модуль, который будет отслеживать изменения ваших файлов и вызывать Browserify каждый раз при сохранении изменений в любом из наблюдаемых файлов.
Потом мы создадим файл, который назовем
Gruntfile.js
и положим его в корень проекта. Внутри этого
Gruntfile.js
мы должны написать следущее:
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-browserify');
grunt.registerTask('default', ['browserify', 'watch']);
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
browserify: {
main: {
src: 'js/main.js',
dest: 'js/findem.js'
}
},
watch: {
files: 'js/*',
tasks: ['default']
}
});
}
Мы начали наш Gruntfile с загрузки необходимых npm модулей, которые мы подключили в
package.json
:
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-browserify');
Мы регистрируем одну (и единственную) группу задач, которая будет запускаться по умолчанию (
browserify
и
watch
):
grunt.registerTask('default', ['browserify', 'watch']);
Далее мы уконфигурируем Grunt с помощью
initConfig
:
grunt.initConfig({
После этого, нам надо показать, где лежит наш
package.json
:
pkg: grunt.file.readJSON('package.json'),
В настройках Browserify мы пропишем файл, который мы хотим использовать в качестве исходного и куда положить browserified-версию файла:
browserify: {
main: {
src: 'js/main.js',
dest: 'js/findem.js'
}
}
Теперь мы создадим
watch
-задачу для детектирования изменений в оыode> каталоге:
watch: {
files: 'js/*',
tasks: ['default']
}
Из-за наших новых зависимостей для разработки(
devDependencies
) нам требуется сперва запустить
npm install
. Запускать эту команду придется только один раз, в дальнейшем вы будете пользоваться благами Grunt'а.
Генерирование Source Maps в Grunt
С версией 2.0.1 пакета
grunt-browserify
несколько изменился процесс генерирования source maps, т.к. интернет кишил неправильными решениями. Теперь, чтобы заставить Grunt и Browserify правильно создавать source map'ы, достаточно просто добавить флаг
debug: true
в
bundleOptions
:
browserify: {
main: {
options: {
bundleOptions: {
debug: true
}
},
src: 'js/main.js',
dest: 'js/findem.js'
}
}
Это тяжело выглядящее решение позволит нам в будущем передавать опции в Browserify простым и изящным способом.
Gulp
Gulp <3 Browserify. Статьи про работу с Browserify через Gulp — крайне популярная тема в IT сообществах, и не удивительно: Browserify и Gulp — это, фактически, передовой стек веб разработки. Я не хочу сказать, что фанатам Browserify необходимо использовать Gulp, совсем нет, это остается на личные предпочтения читателя. Как было сказано выше, вы можете без особого труда использовать npm или Grunt, но лично я отдаю своё предпочтение для небольших проектов команде
npm build
.
Для того, чтобы повторить всё то, что мы описали выше для Gulp, необходимо для начала установить его (глобально):
npm install -g gulp
Мы изменим наш
package.json
для подключения пары новых
devDependencies
, которые нам нужны:
"devDependencies": {
"browserify": "latest",
"watchify": "latest",
"gulp": "3.7.0",
"vinyl-source-stream": "latest"
}
Мы добавили следующее:
watchify – Мы использовали и описывали его работу еще при работе с
npm
gulp – Подключаем, собственно, Gulp
vinyl-source-stream – Виртуальная файловая система для чтения/записи файлов
Browserify имеет API для работы с потоками, поэтому мы можем использовать его напрямую из Gulp.
Browserify имеет API для работаы с потоками напрямую из Gulp. Большая часть руководств рекомендуют использовать
gulp-browserify
плагин, но сам Browserify предпочитает, чтобы работа с ним осуществлялась через его потоковый API. Мы используем
vinyl-source-stream
чтобы получить выходные данные Browserify и сохранить их в файл с помощью Gulp.
Потом мы создаем файл
gulpfile.js
в корне нашего проекта. Здесь будет лежать вся функциональность Gulp'а:
var browserify = require('browserify'),
watchify = require('watchify'),
gulp = require('gulp'),
source = require('vinyl-source-stream'),
sourceFile = './js/main.js',
destFolder = './js/',
destFile = 'findem.js';
gulp.task('browserify', function() {
return browserify(sourceFile)
.bundle()
.pipe(source(destFile))
.pipe(gulp.dest(destFolder));
});
gulp.task('watch', function() {
var bundler = watchify(sourceFile);
bundler.on('update', rebundle);
function rebundle() {
return bundler.bundle()
.pipe(source(destFile))
.pipe(gulp.dest(destFolder));
}
return rebundle();
});
gulp.task('default', ['browserify', 'watch']);
Мы начинаем с импорта наших npm модулей в проект. Вроде бы, тут всё яснее ясного — идем дальше. Тепреь мы должны создать три переменных для нашего билда:
sourceFile
– местоположение и название исходного Browserify-файла (в нашем случае
js/main.js
)
destFolder
– местоположение финального файла
destFile
– имя, которое мы хотим дать нашему финальному файлу
Следующий код я объясню более детально:
Как Browserify работает с Gulp
Первая задача в нашем
gulpfile.js
— это «browserify»:
gulp.task('browserify', function() {
Здесь мы передаем наш исходный файл
main.js
в npm пакет Browserify:
return browserify(sourceFile)
Потом мы используем потоковый Browserify API для того, чтобы вернуть поток с нашим JS контентом:
.bundle()
Здесь мы перекидываем информацию из потока в файл с названием
findem.js
и сохраняем его в директорию
js
.
.pipe(source(destFile))
.pipe(gulp.dest(destFolder));
Проще говоря, мы перекинули наш импортируемый файл через несколько этапов, которые превратили его в финальный файл для нашего проекта. Ура!
Объединяем Watchify и Gulp
Как мы уже поняли, использовать Browserify напрямую доставляет немало боли (хе-хе,
прим. пер) и намного проще запускать его автоматически, когда изменяется один из файлов пакета. Чтобы реализовать подобную схему, мы будем ещё раз использовать npm модуль
watchify
.
Мы начнём с установки задачи, которая называется наблюдателем:
gulp.task('watch', function() {
Мы присваиваем модуль наблюдателя переменной
bundler
(т.к. мы будем использовать его дважды):
var bundler = watchify(sourceFile);
Потом мы добавим обработчик события, который будет вызывать функцию
rebundle
каждый раз, когда вызывается событие
update
. Проще говоря, как только наблюдатель видит, что файл изменяется, он запускает
rebundle()
:
bundler.on('update', rebundle);
Так что же такое
rebundle()
? Это очень похоже на то, что делает наша задача Browserify:
function rebundle() {
return bundler.bundle()
.pipe(source(destFile))
.pipe(gulp.dest(destFolder));
};
return rebundle();
});
При какой-то острой необходимости, вы можете объединить
browserify
и
watchify
вместе, но я бы рекомендовал вам оставить их отдельно друг от друга, по крайней мере в этой статье. Для более глубокого изучения, вы можете посмотреть на
gulpfile.js Дана Тэлло.
Чтобы закончить наш
gulpfile.js
, нам нужно создать нашу задачу по умолчанию (работает по тому же принципу, что и grunt).
gulp.task('default', ['browserify', 'watch']);
У вас есть три варианта, как запустить вышенаписанный Gulp код. Наиболее простым является запуск задачи «по умолчанию», для запуска которой нужно написать в командной строке всего одно слово:
gulp
Это вызовет задачу
browserify
и создаст задачу-наблюдателя, которая будет следить за изменениями в ваших файлах.
Вы можете так же запустить задачу Browserify вручную:
gulp browserify
Аналогично с вашей задачей-наблюдателем:
gulp watch
Генерируем Source Maps используя Gulp и Browserify
Для того, чтобы сгенерировать source map для вашего JavaScript, добавьте
{ debug: true }
в обе функции
bundle()
.
Наша
browserify
задача будет похожа на что-то следующее:
gulp.task('browserify', function() {
return browserify(sourceFile)
.bundle({debug:true})
.pipe(source(destFile))
.pipe(gulp.dest(destFolder));
});
Функция
rebundle()
в нашей
watch
-задаче будет выглядеть так:
function rebundle() {
return bundler.bundle({debug:true})
.pipe(source(destFile))
.pipe(gulp.dest(destFolder));
}
Заключение
И всё же это только рассвет Browserify, и, могу сказать с полной уверенностью, он стоит на пути к улучшению и совершенствованию. В своём нынешнем состоянии, он является весьма полезным инструментом структурирования вашей модульной системы JavaScript приложения и особенно полезным для тех, кто использует Node на своей серверной части. Код становится намного чище и проще для Node разработчиков, когда вы начинаете использовать npm модули в обоих частях вашего приложения. Если вы ещё не пробовали Browserify, настоятельно рекомендую попробовать его в вашем следующем проекте и посмотреть, как он перевернет ваш мир!
Прочие ресурсы
Ресурсов по Browserify куча. Вот несколько, которые вы возможно сможете найти полезными для себя: