Начинаем работать с browserify

http://www.sitepoint.com/getting-started-browserify/?utm_source=javascriptweekly&utm_medium=email
  • Перевод
  • Tutorial

Введение


Решения, написанные на 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.js
npm – по умолчанию поставляется с 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 файл


Давайте начнем с создания файла, который мы будем обрабатывать с помощью Browserify. Например, давайте возьмем суперпопулярный модуль 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 куча. Вот несколько, которые вы возможно сможете найти полезными для себя:
Поделиться публикацией

Похожие публикации

Комментарии 62
    +3
    В чем преимущество перед AMD/RequireJS?
      –1
      Это аналог AMD/RequireJS. Полностью повторяет структуру CommonJS модулей. Позволяет использовать их в браузере.
        +15
        Это не ответ.

        Вот, нашел статью по этому вопросу: esa-matti.suuronen.org/blog/2013/03/22/journey-from-requirejs-to-browserify/

        Вольный перевод заключения:

        Преимущества Browserify перед RequireJS

        • Всегда делает конкатенацию в один файл. Отпадают проблемы со сборкой в дальнейшем.
        • Позволяет использовать пакеты из npm.
        • Проще синтаксис: module.exports = ... .
        • Позволяет использовать фишки NodeJS.
        • Source maps, вот том числе с поддержкой CoffeeScript (хотя RequireJS, возможно, уже имеет эти возможности).
        • Замечательный API плагинов.


        Недостатки Browserify перед RequireJS

        • Всегда делает конкатенацию в один файл. Отладка браузеров, не имеющих поддержки source maps, может стать мукой.
        • Для работы с Browserify необходимо запускать в консоли watcher, который будет пересобирать код по мере его редактирования. RequireJS можно пользоваться в браузере, вообще без консольной утилиты.
        • Есть люди, выступающие против того, чтобы помещать в репозиторий npm пакеты, предназначенные только для браузера. Я не знаю, какова позиция мейнтейнеров Node/npm на этот счет, но в принципе вы не ограничены npm. При желании можете использовать, например, Bower.
        • Документация могла бы быть и получше Предполагается, что вы знаете многие нюансы из мира Node. См. пост по ссылке для разъяснения некоторых из них.
        • Отсутствует коммьюнити, если не считать таковым коммьюнити самого Node. Хотелось бы mailing list и IRC-канал.


        Актуальность данной информации — март 2013.
          +1
          Спасибо за ответ. Думаю, многим он будет полезен.

          Что касается минусов, то могу сказать так:
          — Браузеров без поддержки source maps всё меньше и меньше, а такие гиганты как Mozilla и Google уже давно ввели их поддержку.
          — Мммм, да, но в большинстве случаев у вас и так висит watch для каких-нибудь препроцессоров или т.п.
          — Это не минус а одно из мнений. Тем более что вам мешает создавать пакеты отдельно и паблишить их в npm, откуда потом через package.json вы их и будете тянуть командой npm install?

          Последние два пункта хоть и верны, но для меня, лично, спорны. Вы видели requirejs community, например?
            +2
            Есть еще один минус. Довольно существенный:

            нельзя сделать подгрузку модулей по необходимости, имеется ввиду загрузку приложения по частям
            в том числе нельзя сделать асинхронную подгрузку модулей.
              +1
              Да, но в большинстве случаев, дабы минимизировать просадку на лишние запросы к серверу, все файлы всё равно минимизируются в один. В require.js это сделано с помощью r.js, здесь это работает по умолчанию.
                +1
                Но если приложение огромное, то загрузка по частям наоборот комфортнее, чем загрузка сразу всего.
                С require можно сделать и так и так и смешанный вариант — т.е. объеденить несколько модулей и загружать их кусками, а browserify альтернативы не дает.
                  0
                  Насколько же оно должно быть огромное… У нас весь код LMS (а она, поверьте, не маленькая) умещался в 230Кб, которые кешировались на клиенте после первой же загрузки. Какой профит вы с этого получаете? На какие чанки вы собираетесь разбивать проект? По сколько кб/файл, чем это обусловлено / продиктовано?

                  Я не говорю, что browserify — панацея от всех бед, не подумайте. У него, как и любого другого проекта есть свои плюсы и минусы. Но конкатинация модулей в один файл явно не является проблемой или недостатком.

                    0
                    У нас только лишь js одного сингл-пейдж раздела весит 1МБ + CSS с инлайновыми картинками 2 МБ. Тоже будете весь проект разом грузить?)
                      0
                      Господи, что же за проект, где SPA весит 1Мб?! Это получается в районе 1 048 576 символов (минифицированного кода, как я понимаю).

                      Как связаны CSS и модульная система JS? Хотя base64 в CSS вы всё равно зря кладёте. Браузер рендерит страницу только тогда, когда загрузились стили, а если стили весят 2Мб, то вы намеренно увеличиваете время загрузки страницы. Объедините изображения в 2 спрайта: ретина/не ретина и живите счастливо.

                      P.S. Отвечая на ваш вопрос: да, я буду загружать один файл, потому что, как я уже сказал выше, он будет кеширован сразу после первой загрузки. Подключение сайта по частям не даст нормального результата: пользователь будет ждать каждый раз ответа от CDN, что в результате получится больше по времени(суммарном), чем обычная загрузка 1 файла с того же CDN. И это не говоря уже о постоянном ощущении «подлагивания» при переключении страниц.
                        0
                        Сам движок на Ангуляре весит примерно как у вас. Плюс закешированные html-шаблоны, плюс всякие плагины. Плюс jQuery, селектайзы и т.п, которые естественно, не на каждой странице нужны.

                        У нас деплой раз в неделю, кеширование не спасет. Тем более, неизмененные модули останутся в кеше, а загрузится только то, где были правки
                          0
                          Ну, видимо в вашем случае действительно подобный подход себя оправдывает. Всё ведь сугубо индивидуально, сами понимаете. Как я уже писал выше, browserify не является панацеей или единственно правильным решением, все исходит от самого проекта. Так или иначе, надеюсь, вы нашли статью занимательной :)
                      0
                      Во фронтенде не раз использовал three.js для панорам, который весит поболее 230кб (минифицированный 430кб), и грузить его надо, только когда дело до панорам доходит. Тут RequireJS себя с лучшей стороны показывает, предоставляя такие возможности.
                  0
                  Можно. Свой transform + свой browser-pack + чуть клиентского кода. Но из коробки Browserify этого не умеет.
                  0
                  Браузеров без поддержки source maps всё меньше и меньше

                  соурс-мапы не помогают получать адекватные стек-трейсы. особенно доставляет в модульных тестах.
                    0
                    Со стек-трейсами верно, но по поводу тестов не согласен: всё зависит от человека, который пишет тесты. Я не понимаю, в чём различие при тестировании конкатинированного файла?
                      0
                      когда тест валится трейсы показывают место возникновения ошибки. при тестировании сборки, исходники, которые надо править, приходится доискивать лапками.
                    0
                    — Браузеров без поддержки source maps всё меньше и меньше, а такие гиганты как Mozilla и Google уже давно ввели их поддержку.

                    По поддержке source maps все более менее только у chrome (возможно у Safari тоже). Firefox странновато с ними работает, у него есть баги в реализации (одни и те же sourse maps в лисе и хроме указывают на разные места, в хроме на верные, в лисе непонятно куда)
                    Firebug вообще не поддерживает source maps code.google.com/p/fbug/issues/detail?id=5765
              +1
              1. Не все модули из NPM будут сразу работать с Browserify. Раньше Lodash не работал, например.
              2. У вас ссылка потерялась.
              Вы можете запустить команду, используя sudo, но я бы всё-таки рекомендовал вам сначало ознакомиться с этим постом.

              3. Кто-нибудь может объяснить комментарий автора Lodash?
              Why are you Browserifying Lo-Dash? It's not the target that Browserify is after.
                0
                3. — странно. Может, он предлагает оставить Lo-Dash в глобальной области видимости? :/
                  0
                  Или, может быть, он указывает на то, что Lo-Dash не нуждается в «браузерификации» в том смысле, что он «и без того работает в браузере».

                  Есть ведь и такая точка зрения на Browserify, что это прежде всего способ получить во браузере аналог целого ряда API Node.js, тогда как конкатенация скриптов — это вторичный (хотя и полезный) эффект.
                0
                Кто-нибудь использовал на практике? Мне интересна скорость сборки — понятно, что зависит от SSD/не-SSD и т.п., но скажите хотя бы порядок для ~150 файлов?
                  +1
                  Macbook Pro Late 2013 (с SSD), сборка большого проекта (300+ файлов) ~ 0.7s ± 100ms
                    0
                    Неплохо, спасибо.
                      0
                      Если вы пользуетесь инкрементальной сборкой и собираете пакеты по мере необходимости, то вы не ощущаете время сборки 1 пакета. А для полной сборки 0.7-0.8 сек — мелочи. Рад, что смог ответить на ваш вопрос.
                      0
                      Теперь бы еще для чистоты узнать сколько займет сборка тех же файлов, но уже с помощью Require.js.
                        +4
                        Здесь я уже понадеюсь на хабр и на читателей. Надеюсь, у кого-нибудь найдется подобная информация. Но, честно говоря, я не понимаю фетиша замера скорости для подобного рода модульных систем. В смысле, разница в скорости не будет настолько грандиозной, какая разница сколько ждать при деплое — пол секунды или секунду? :) Намного важнее, почему тот или иной инструмент удобнее/неудобнее.
                          –3
                          Require.js. — ничего не собирает, она загружает по требованию
                            +1
                            У require.js есть оптимизатор r.js, который занимается сборкой.
                              –2
                              да, но это уже делает утилита, цель которой не собрать все в один файл, а просто оптимизировать загрузку модулей, если она, например видит, что модули сильно связаны, она их пытается объединить, но не факт, что все соберется в один файл
                                +1
                                Вы не правы. r.js не находит лишь только те модули, имена который при require указываются динамически. Но в таком случае мы в build-файле указываем в include те модули, которые точно нужно включить в сборку. Ниже я писал, что сам require.js можно даже не включать в сборку, а заменить его более легковесным almond.
                                Вот пример build-конфига:
                                ({
                                    paths: {
                                        requireLib: '../third-party/almond' // Указываем путь к require.js-плейсхолдеру almond.js
                                    },
                                    include: ['requireLib', 'router/front-page-router', 'router/challenge-page-router'], // Инклудим и almond, и любые другие модули.
                                    baseUrl: '.',
                                    mainConfigFile: 'loader.js', // Это основной файл с конфигом, в котором прописаны настройки require.js
                                    name: 'loader',
                                    out: 'loader-min.js',
                                })
                                
                                  0
                                  Простите, в чем не прав?
                                    0
                                    Если вы считаете, что основная цель optimizer собрать в один файл, грубо говоря, цель — «конкатенация», то тогда я с вами соглашусь, что неправ, но мне видится другая цель, а именно, сборка модулей по принципу связанности.
                                      +1
                                      Нет, оптимайзер делает не простую конкатенацию. Он запишет в сборку только те модули, которые будут связаны. Если модуль нигде не используется, то он пропускается(точнее, он даже не читается). Выше я писал, что проблема может быть, если мы делаем что-то вроде require(fp ? 'router1' : 'router2'), и чтобы этим модули были в сборке, то мы просто их указываем в списке include в конфигурации сборки.

                                      Рассмотрим другой пример. У нас есть package1, у которого есть зависимость jQuery, и есть package2, который так же имеет зависимость jQuery. Мы можем собрать весь проект в один файл. А можем оставить проект модульным пофайлово, но все же оптимизировать отдельные packages. Естественно, есть смежные модули, такие как jQuery, код которых не имеет смысла включать в оба оптимизированных пакета, мы будем его грузить как обычно, отдельным файлом.
                                      Тогда build-конфиг будет примерно такого вида:
                                      ({
                                          mainConfigFile: 'loader.js', // Это основной файл с конфигом, в котором прописаны настройки require.js
                                      	modules: [
                                      		{
                                      			name: "modules-package-01",
                                      			exclude: ["jquery"]
                                      		}
                                      	],
                                      	dir: './deploy/'
                                      })
                                      


                                      При данной конфигурации не будет создан единый loader.min.js. R.js поместит все найденные модули в папку ./deploy, модули, которые мы указали, будут оптимизированны(будет произведена конкатенация зависимостей), однако, jQuery конкатенирован не будет, и при загрузке пакета(набора модулей) на клиенте модуль с jQuery будет запрашиваться отдельно. В итоге мы получаем склеенный package, в котором есть куча уникальных зависимостей, и он будет грузиться одним файлом(там будет набор моделей, вьюх, роутер, и все другое, что для других пакетов не нужно). И jQuery все еще будет грузиться из кеша отдельным файлом(хоть с CDN гугла, нам не важно).
                                      Как по мне — это невероятно крутая фича, и при правильном применении можно собирать проект очень грамотно. И я сильно сомневаюсь, что подобный функционал кто-то предоставляет еще.

                                      Если интересно, читайте доки оптимизатора require.js, или можете быстро пробежаться по примеру build-конфига, где все подробно расписано с комментариями.
                                        0
                                        Я написал, что require js ничего не собирает, имея в виду конкретно модуль require js, а не r.js — оптимизатор, который собирает, и то не всегда в один файл и цель, которого не простая конкатенация, вы меня тут отправляете к докам, пишите про build конфиг, зачем?.. r.js — это addon, его можно использовать или нет. Основное назначение Require js, повторю, отложенная загрузка по требованию, вот это мой посыл, а вы его видимо не понимаете и пишите, про то, что есть еще r.js, который собирает и какой он крутой, я вам об одном пишу, а вы о другом.
                                          +1
                                          Ок, я вас понял. Вы правы, а я нет, если вам так угодно. Однако, r.js — часть проекта require.js, и он собирает так, как его попросишь, хоть в один файл, хоть в несколько.
                                            0
                                            Почитайте, пожалуйста здесь историю создания проекта и самое главное мотивацию, т.е. основным мотивом было асинхронная отложенная загрузка модулей на клиенте, в браузере, собственно в третий раз, настаиваю на том, что это главное — это котлета, теперь попытаюсь отделить её от мух, которые вы решили заметить на котлете. В процессе развития и использования проекта скорее всего встал вопрос, а что, то если разработчики слишком например сильно будут дробить свои проекты на множество модулей, тогда возникает ситуация, когда на клиенте может возникнуть множество запросов на сервер по загрузке этих модулей, и естественным образом возникло желание сократить количество таких запросов, попытавшись оптимизировать загрузку путем объединения нескольких модулей в один, по степени связности, и вот возник оптимизатор, который так, на всякий случай, может используя стиль подтягивания модулей и плюс ваш конфигурационный файл объединить все в один, и это только частый случай работы оптимизатора, а вы его выставляете, этот частный случай, за котлету, если этот частный случай вы повсеместно используете в своих проектах, то тогда, извините за то, что я вас побеспокоил. В других проектах ваш частный случай вообще может не рассматриваться. Оптимизация, по определению носит сугубо эврестический характер, и никогда не является панацеей, некоторые разработчики в процессе написания модулей оптимизируют их контент сами, без использования дополнительных утилит. Ну если использовать require js, только для того, чтобы потом все объединить в один файл и грузить на клиент, просто потому, что нравиться стиль определения и подхватывания модулей, а потом сборки, то тогда действительно можно рассматривать его в качестве альтернативы другим подходам и библиотекам. Спасибо за понимание.
                          +1
                          Гарантирую, что на порядок дольше. Там все же несколько иная система сборки, так как может быть использован более сложный синтаксис.
                          На проектах я делаю сборку только для финального тестирования, проблема была только 1 раз, из-за минифицированной библиотеки, я просто заменил ее на несжатую, и проект собрался. А если еще и хотите избавиться от самого require.js из собранного проекта, то можно использовать almond, правда с некоторыми ограничениями в проекте. Так же, если используете загрузку темплейтов через require.js (плагин text), то темплейт тоже будет включен в файл сборки, r.js сделает его инлавновым и выглядеть он будет как обычный модуль, примерно вот так:

                          define("text!tpl2.txt",[],function(){return"template text"})
                          
                            +1
                            require.js надо собирать только для продакшена — тут время сборки особой роли не играет. время сборки существенно при разработке, но в этом случае ничего собирать не надо.
                          +2
                          Я использую. Работает быстро, для разработки есть еще watchify (https://github.com/substack/watchify). Он висит в фоне, следит за изменениями в зависимостях и очень быстро пересобирает бандл.
                          +1
                          К асинхронной загрузке модулей, на клиент, browserfy не имеет никого отношения, сравнивать его с require js не стоит, это разные по назначению библиотеки. Browserify собирает модули в один, используя стиль require node js. Require JS позволяет асинхронно загружать модули на клиент по мере их необходимости, и это принципиально и даже концептуально их отличает. Browserify можно немного сравнить с Grunt с точки зрения основного назначения — а именно, получение целевого модуля собранного из других, и вот тут можно сравнить те или иные подходы по их использованию.
                            0
                            А кто-нибудь читал, что RequireJS тоже умеет делать оптимизацию?
                            Единственное преимущество — возможность использовать NPM-пакеты, хотя все равно стоит почитать про browserify, статье уже год и разработчики наверняка расширили библиотеку.
                              +1
                              основных преимуществ два. во первых, возможность использовать один и тот же код, как node.js, так и в браузерах (так называемый isomorphic javascript). во-вторых, CommonJS немного лаконичнее, чем AMD.
                                +2
                                Пользуясь случаем, хочу заодно попиарить своё экспериментальное решение, о котором я уже писал на хабре: это загрузчик, который позволяет загружать одни и те же модули (в том числе на лету) и под вебом, и под Node.js вообще без каких-либо преобразований-сборок-итп. В моём понимании именно это есть изоморфный JavaScript (оттранслировать то можно что угодно куда угодно, так что Browserify это уже читерство, а не изоморфный JS ;-) ). Правда, самый существенный недостаток — это, конечно, свой (изоморфный) формат модулей, в который нужно вручную переделывать требуемые библиотеки. Впрочем, я попытался сделать его насколько возможно простым.
                                  0
                                  чем переделка в свой формат отличается от трансляции?
                                    0
                                    Просто библиотек почти нету в этом формате, поэтому нужно переделать формат модуля в тот момент, когда хочется новой библиотекой воспользоваться. Но никакой трансляции не требуется «на этапе сборки» (между изменением кода и его запуском под вебом или в ноде).

                                    Вообще я имел в виду, что «изоморфный JavaScript» на мой взгляд другое означает :-)
                              +1
                              Для тех, кому не нравится каждый раз пересобирать проект — есть библиотека smoothie. Её удобно подключать во время разработки, а уже при сборке — использовать browserify.
                                0
                                Чем она лучше watchify?
                                  +1
                                  Ничем, эти библиотеки решают разные задачи: watchify — пересобирает проект при изменениях, а smoothie — клиентская библиотека для работы (синхронного/асинхронного подключения) с commonjs модулями.
                                    0
                                    Ага, понял. Т.е. смысл как раз что бы файлы отдельно лежали.

                                    В принципе, у нас нормально watchify прижился с source maps во время разработки.
                                      0
                                      Да, верно. Посмотрите ещё в сторону gulp, возможно будете усложнять систему сборки и замените им watchify.
                                        0
                                        У нас и так gulp :-)

                                        watchify у нас только как один из слоев, там еще reactify используется и так далее.
                                +1
                                Самое веселое с require.js начинается в тот момент, когда хочется выйти за рамки возможного и написать свой «супер крутой плагин», который реализует в себе загрузку модулей как прослойка. В этот момент вы со сборщиком r.js один на один, потому что он не знает какие зависимости будет грузить плагин. Для этого приходится собственноручно прописывать в include все необходимые модули, что в обычном приложении можно опустить. Поэтому приходится описывать свой процесс сборки (плагины это позволяют), и вот тут начинается деление на dev/production окружение, которые живут своей жизнью и которые надо поддерживать отдельно. Browserify исключает двухфазность проекта ценой монолитной сборки, и этим можно пожертвовать ради однообразия вашего окружения, что сэкономит время и нервы при сборке. В следующих проектах никакого require.js, это для себя решил.
                                Кстати, есть еще Webpack как аналог для CommonJs модулей в браузере.
                                  +1
                                  Вы сейчас описали мою боль. Однако я не собираюсь уходить от require.js, концепция монолитного проекта меня не очень привлекается, когда речь идет о чем-то действительно тяжелом. Я сейчас иду в сторону модулей, которые объединяются в своего рода пакет(не те пакеты, которые предлагает require.js). А вот уже сами пакеты я и оптимизирую. Прочитайте мой комментарий выше, возможно вам подойдет моя практика.
                                    0
                                    Я сейчас иду в сторону модулей, которые объединяются в своего рода пакет(не те пакеты, которые предлагает require.js). А вот уже сами пакеты я и оптимизирую

                                    Я как раз такие модули и использую с Browserify (естественно уже не в чистом виде)
                                    Т.е есть модуль, у него могут быть его субмодули, при работе модуль бандлится с субмодулями в один файл. При этом модуль может иметь зависимость от какой либо общей библиотеки (например то же jquery) и эта общая библиотека в бандл не включается.
                                    Для модуля это выглядит как:
                                    var subModule1 = require("./dir/sub1"); //будет включен в бандл
                                    var subModule2 = require("./dir/sub1");//будет включен в бандл
                                    var $ = require("jquery");// не будет включен в бандл, будет загружена отдельно асинхронно.
                                    ....


                                    При этом модуль не пытается исполниться, пока его зависимости не загружены. Поэтому описание (var someModule= require(«someModule»)) выглядит очень приятно, по сравнению с AMD модулями.

                                    Browserify мне понравился своей модульностью и нативным node.js.
                                    Я могу заменить в Browserify любой компонент через его конфиг и самое приятное — я могу получить от Browserify свой модуль в разобраннов виде в JSON формате:
                                    [
                                    {
                                    "id": "a1b5af78",
                                    "source": "console.log(require('./foo')(5))",
                                    "deps": { "./foo": "b8f69fa5" },
                                    "entry": true
                                    },
                                    {
                                    "id": "b8f69fa5",
                                    "source": "module.exports = function (n) { return n * 111 }",
                                    "deps": {}
                                    }
                                    ]

                                    И дальше можно с этим JSON что-то делать странное :)
                                    В свою очередь r.js выглядит очень странно: 1 Мб js файл, с никакой информации об ошибках.
                                      +1
                                      Я выбрал Browserify и очень этому рад. На следующих крупных проектах я тоже планирую использовать Browserify. На небольших проектах с полностью стандартным функционалом вероятнее всего буду использовать require.js или может модули из ES6.
                                        0
                                        А как масштаб проекта влияет на выбор формата модулей?
                                          0
                                          Настройка окружения.
                                            0
                                            Ну, если работаете уже со всеми форматами модулей — достаточно разобраться и настроить один раз.

                                            И если уже работаете с ES6-модулями, то зачем Browserify?
                                              0
                                              Разные задачи. посмотрите мой комментарий чуть выше, где я описал как я использую Browserify
                                                0
                                                Так к тому и вопросы, что эти задачи решаются при использовании любого формата модулей и нет никакой очевидной необходимости менять их в зависимости от масштаба/сложности проекта. AMD по определению поддерживает асинхронную подгрузку. Бандлы, очевидно, можно создавать с помощью r.js. Что касается ES6-модулей, то там вообще круто разделено само определение модуля и способ его загрузки — «бандлить» можно как угодно и транс-компилировать в AMD (если нужно) или в CommonJS (для Browserify-подобных упаковщиков).
                                    0
                                    Есть еще такой инструмент как lmdjs, который, насколько я помню, умеет как раз подгружать отдельные модули (бандлы)

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое