Skip to content

Repository files navigation

Jossy

Jossy -- сборщик JS-файлов в один или несколько модулей. При правильном использовании позволяет не только легко собирать модули, но и также легко пересобирать их при изменении принципов сборки.

Установка

Jossy написан на NodeJS, поэтому сначала необходимо установить на разработческую машину сам NodeJS (требуется версия 8 или новее). Если NodeJS у вас уже установлен, то достаточно набрать

npm install -g jossy

Jossy необходим только на этапе разработки, поэтому на боевых серверах NodeJS не понадобится (если вы его, конечно, больше нигде не используете).

Синтаксис и возможности

Подключение файлов

Включить содержимое внешнего файла в текущий можно директивой #include

//#include file.js

Путь к файлу указывается относительно расположения текущего файла. Технически, вместо строки с директивой просто вставляется содержимое указанного файла. Однако, если указанный файл уже подключен в текущем модуле ранее, то повторно он включен не будет. Например, файл f1.js

alert(1);

Файл f2.js

//#include f1.js
alert(2);

И файл f3.js

//#include f1.js
//#include f2.js

Если скормить Jossy файл f3.js, то на выходе получим

alert(1);
alert(2);

Импорт файлов

Директива #import подключает содержимое внешнего файла так же, как #include, но помещает его в особый блок импортов. По умолчанию этот блок выводится в начало файла — так удобнее группировать импорты вверху, а основной код оставлять за ними.

//#import file.js

Поддерживается та же запись с областями, что и у #include (::label и ::):

//#import String.js::escapeHTML
//#import String.js::

Например, файл f1.js

alert(1);

файл f2.js

//#import f1.js
alert(2);

даст на выходе

alert(1);
alert(2);

то есть импорты перед основным кодом. Если нужно вывести блок импортов не в начале, а в другом месте, укажите его позицию директивой #imports — блок переносится в точку последнего вхождения #imports.

alert('index');
//#imports
//#import foo.js::bar

Тогда блок импортов выведется после строки alert('index');, а не в начале файла.

Исключение файлов из сборки

Директива #without указывает Jossy исключить из сборки все файлы, которые используются в указанном (включая указанный, разумеется).

Пример. В проекте есть несколько десятков виджетов. Код каждого виджета лежит в отдельном файле. В каждом виджете указаны его зависимости с помощью директивы #include. Какие-то виджеты используются на большинстве страниц, и при сборке логично их код вынести в отдельный файл common.js. Выбираем часто используемые виджеты, создаём файл common.js и пишем туда

//#include widget1.js
//#include widget2.js
//#include widget3.js

На одной из страниц используется виджет, достаточно объёмный, чтобы не включать его в common.js, назовём его big-widget. В файле big-widget.js указаны его зависимости, среди которых, разумеется, много тех, которые уже есть в common.js. Если мы просто соберём файл big-widget.js, то получим много продублированного кода. Поэтому рядом с common.js создаём файл feature.js с содержимым

//#without common.js
//#include big-widget.js

Теперь код, попавший в common.js, не попадёт в feature.js. Главное не забыть подключить на страницу не только feature.js, но и common.js.

Условная сборка

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

//#set flag

//#if flag
alert('flag');
//#endif

//#if not flag
alert('not flag');
//#endif

//#unset flag

Флаги глобальные. Указать их можно не только в коде директивами #set и #unset, но при запуске сборщика (о запуске сборщика ниже).

Например, файл file.js

//#if ie
alert('IE only');
//#endif

Файл common.js

//#include file.js

И файл common-ie.js

//#set ie
//#include file.js

Точно также можно создать флаг debug и писать отладочные строки только внутри //#if debug ... //#endif, тогда отладочный код никогда не попадёт на боевые сервера.

Подключение кусков файлов

Эта фича узкоспециализирована, но очень полезна при разработке библиотек и фреймворков. Например, в нашей библиотеке есть файл String.js, содержащий несколько десятков функций для работы со строками. Выделять каждую функцию в отдельный файл как-то неправильно, но и подключать потом несколько сотен строк кода ради одной функции тоже не хочется. В результате, обычно всё заканчивается копипастой. В случае с Jossy файл String.js размечается на области. Имена у областей могут быть произвольными, но лучше, чтобы они совпадали с именами функций.

var String = {};

//#label truncate
String.truncate = function() {

};
//#endlabel truncate

//#label escapeHTML
String.escapeHTML = function() {

};
//#endlabel escapeHTML

Теперь, если нам нужна только функция escapeHTML, то при подлючении файла String.js пишем

//#include String.js::escapeHTML

В результате в сборку попадёт только

var String = {};

String.escapeHTML = function() {

};

Если нужно подключить несколько областей, указываем несколько

//#include String.js::trim::truncate

Если нужно подключить всё, кроме размеченных областей (например, нам нужен только namespace String), то

//#include String.js::

Если же какой-то области необходима другая область из текущего файла, то используем #include без указания файла.

//#label truncate
//#include ::trim
String.truncate = function() {};
//#endlabel truncate

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

Например,

//#include String.js::escapeHTML
alert(1);
//#include String.js::truncate

После сборки получим

var String = {};

String.escapeHTML = function() {

};

alert(1);

String.truncate = function() {

};

Поэтому использовать #label внутри функций и выражений нельзя, на выходе получим поломанный JavaScript.

Кроме этого, #without тоже смотрит на эти области. Поэтому, например, escapeHTML может попасть в common.js, а truncate -- в feature.js.

Макросы

Директива #define задаёт текстовую подстановку: первый аргумент — искомое выражение, остальные аргументы (слитые в строку) — замена. Подстановка применяется ко всем строкам файла, идущим после директивы, и к каждому вхождению на строке. Несколько #define накапливаются: каждая новая строка проходит через все ранее объявленные подстановки.

alert('$__content');
//#define $_ My_Class_
alert('$__content');

После сборки:

alert('$__content');
alert('My_Class__content');

Подстановка локальна: она действует только на строки того файла, в котором объявлена, и не затрагивает содержимое подключаемых через #include файлов — каждый файл подставляется в своём собственном пространстве.

Разделение по слоям

Директива #layer семантически похожа на #if, но в отличие от него позволяет не только включить или исключить часть кода в процессе сборки, но и получить только тот код, который внутри слоя, исключив основной код.

//#layer layer1
alert(1);
//#endlayer
alert(2);

Такой файл мы можем собрать тремя разными способами. Во-первых, без дополнительных параметров в сборку попадёт только alert(2). Во-вторых, мы можем при сборке передать параметр --layer=layer1, получив после сборки alert(1). Наконец, в-третьих, можно передать параметр --layers=layer1, тогда в сборку попадёт и основной код, и код из слоя layer1.

Использование

Сборка из командной строки

jossy -i input.js -o output.js --set debug --set ie

Где debug и ie -- флаги, которые можно использовать в директиве #if. Если параметр -o не указан, то результат сборки выводится в stdout.

Параметры:

  • --set <flag> (можно многократно) — булевы флаги для директивы #if;
  • --label <label> (можно многократно) — области входного файла, которые попадут в вывод;
  • --layer <layer> — собрать только код данного слоя, основной код исключается; нельзя использовать вместе с --layers;
  • --layers <layer> (можно многократно) — базовый код плюс перечисленные слои; нельзя использовать вместе с --layer;
  • --fail-on-errors — при ошибке сборки прервать процесс с кодом 1, а не вкладывать в вывод throw new Error(...).

Если в выходной файл невозможно записать (каталог не существует или не предназначен для записи), Jossy выводит ошибку в stderr и завершается с кодом 1.

Использование сборщика из NodeJS

var {Jossy} = require('jossy');
new Jossy().compile('path/to/file.js', {ie: true}).then(function(result) {
    console.log(result);
});

Конструктор принимает аргумент failOnErrors:

var jossy = new Jossy(true);

По умолчанию (false) ошибка сборки (неизвестная директива, незакрытый #endif/#endlabel/#endlayer, #include без аргумента) не прерывает сборку: в вывод вкладывается строка throw new Error("JossyError: …"), артефакт успешно компилируется, но упадёт с диагностикой при выполнении. При failOnErrors та же ошибка выбрасывает исключение, и сборка прерывается.

Метод compile(file, context, labels, layers) принимает:

  • file – путь до файла

  • context — объект булевых флагов (аналог --set);

  • labels — массив имён областей, которые попадут в вывод (аналог --label);

  • layers — строка (собрать только этот слой, аналог --layer) или массив строк (базовый код плюс перечисленные слои, аналог --layers);

    jossy.compile('path/to/file.js', {ie: true}, ['label1'], ['css', 'js']).then(function(result) { console.log(result); });

Один инстанс Jossy подходит для сборки нескольких связанных файлов — например, всего JavaScript одной страницы: прочитанные и разобраннные файлы кешируются между вызовами compile(), поэтому общие зависимости читаются и разбираются один раз. Однако кеш не перепроверяется по диску: если исходные файлы изменились после предыдущего compile(), пересборка использует застарелое содержимое. В долгоживущих процессах (демон сборки, watch-режим) перед пересборкой после изменений вызовите jossy.clearCache() — он сбрасывает все закэшированные файлы (включая виртуальные из compileCode) — либо создайте новый инстанс Jossy.

About

Сборщик JavaScript-файлов.

Resources

Stars

8 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages