TOM.js — особая библиотека, для особых случаев
Приветствую всех.
Не знаю на сколько верно я описал данную библиотеку в заголовке, но рассказать я хочу именно о ней.
Библиотека TOM.js даёт возможность облегчить такие задачи как:
- загрузка/подгрузка скриптов/стилей с зависимостями
- создание/наследование классов
- перехват функций в пределах приложения
Я прекрасно осведомлен о том что есть всяческие RequireJS, klass.js и прочее, но данная библиотека представляет из себя наработки за несколько лет под конкретные задачи в проекте над которым я работаю.
Например функционал перехвата вызова функций я нигде не встречал, но нам в проекте необходим был данный функционал для разработки расширений на все случаи жизни, а позже и для других задач. А там уже и создание классов с нужным набором параметров и функций, ну и конечно же загрузчик файлов созданный с учётом специфики нашего проекта.
Изначально это была небольшая библиотека, которая за 4 года была переписана уже несколько раз из-за неприятных багов с зависимостями. В последний раз была попытка реализации загрузки при помощи RequireJS, с небольшой «надстройкой», но в итоге эта «надстройка» получилась такой закрученной (да и о зависимостях у RequireJS свои понятия) что оказалось легче реализовать свой загрузчик, но уже не допуская тех ошибок которые были в прошлых реализациях.
Что-же умеет данная часть библиотеки?
-
Загрузка модулей и скриптов
Для загрузки «модулей» (о них я расскажу немного ниже) и скриптов можно использовать около 5 вариаций вызовов1 способ, задача: загрузить /libraries/jquery/jquery.boot.js и /libraries/scroll/scroll.boot.js
TOM.boot.load( 'libraries/*', [ 'jquery', 'scroll' ], function( ){ } );
2 способ, задача: загрузить /jquery/jquery.boot.jsTOM.boot.load( '*', 'jquery', function( ){ } );
3 способ, задача: загрузить /jquery.boot.jsTOM.boot.load( '', 'jquery', function( ){ } );
4 способ, задача: загрузить /jquery.jsTOM.boot.load( '', 'jquery.js', function( ){ } );
5 способ, задача: загрузить code.jquery.com/jquery-1.12.0.min.jsTOM.boot.load( '', 'https://code.jquery.com/jquery-1.12.0.min.js', function( ){ } );
Как можно понять по примерам — структура функции вызова следующая:TOM.boot.load( 'путь к скрипту/модулю', 'имя файла/модуля' {строка или массив}, 'callback по окончанию загрузки' );
Логика подбора полного пути тут проста — если в имени нет расширения значит мы загружаем модуль (*.boot.js), если есть — то конкретный файл. А * (звёздочка) в пути подставляет в данное место имя модуля/файла, что позволяет сохранять понятную структуру директорий и файлов в больших приложениях. -
Загрузка скриптов из модуля с учётом зависимостей
Для начала следует разобрать что такое «модуль» в понимании данной библиотеки.
Модуль — это файл *.boot.js в котором прописаны конкретные файлы и их зависимости от других «модулей» и скриптов.Содержимое *.boot.js выглядит следующим образом:
TOM.boot.initiate( 'button', [ { file: '*.style.css' }, // загружаем button.style.css { file: '*.interface.js' }, // загружаем button.interface.js { file: '*.core.js', require: '*.interface.js' }, // загружаем button.core.js с зависимостью от button.interface.js { file: 'testButton.core.js', require: [ 'jquery', '*.core.js' ] } // загружаем testButton.core.js с зависимостями ] );
Здесь структура имеет следующий вид:TOM.boot.initiate( 'имя модуля', 'список объектов загружаемых файлов с параметрами' );
Ну, а сами объекты загружаемых файлов имеют следующую структуру:- file — имя загружаемого файла, где * (звёздочка) подставляет имя модуля
- require — зависимость (список зависимостей) как от файлов текущего модуля, так и от других модулей
- initialize — функция (список функций) которую следует выполнить после загрузки скрипта
- main — булевая переменная указывающая что все в данном модуле зависят от данного файла
На самом деле перехват функций будет происходить только там где Вам это необходимо, только в тех объектах которые вы «пропроксируете».
В нашем проекте например имеется 3 объекта которые прописаны в window и с которыми мы работаем, это наши так-называемые «области видимости»: api, core, interface именно с ними мы и работаем, потому только их и проксируем.
TOM.js по умолчанию создаёт core и interface, но работать с ними или нет это личное дело каждого.
Как с этим работать?
-
Проксирование
Первое что необходимо сделать — это произвести «проксирование» нужных объектов подобным образом:TOM.processor.proxy( core ); TOM.processor.proxy( interface );
Суть проксирования проста до безобразия — проходим по объекту и обёртываем функции определённым видом (добавляем pre-callback и post-callback). -
Обработка/Перехват вызова функций
После обработки нужных объектов вызванные внутри них функции- можно обрабатывать подобным образом:// Обработка вызова создания кнопки TOM.processor.bind( 'pre-core.test.addTestButton', function( sender ) { // Если мы не хотим на самом деле создавать кнопку - прерываем её создание if( !confirm( 'Действительно создать кнопку?' ) ) { return false; } } );
Обработчик имеет такую структуру:TOM.processor.bind( '{pre или post}-имя функции вызова которой ждём', 'callback функция', 'параметры' );
- pre или post — это обработка «до вызова» оригинала функции, или после — соответственно
- параметры — это объект с настройками данного обработчика
- stage — аналог pre/post в имени функции
- label — «метка» по которой мы сможем снять именно этот обработчик, не затрагивая другие
- priority — добавлять данный обработчик в начало или в конец очереди?
-
Другие возможности
Помимо непосредственно возможности добавления и снятия «обработчиков», можно так-же:- «возбуждать фейковые события»:
TOM.processor.signal( 'момент запуска (pre/post)', 'имя функции', 'объект вызывающий функцию', 'аргументы' );
- производить единоразовую обработку события:
TOM.processor.one( 'перечень тех же аргументов что и в TOM.processor.bind' );
- «возбуждать фейковые события»:
Данная часть библиотеки обыгрывает стандартный подход к созданию и наследованию классов в стандартном JavaScript, но с большим количеством нюансов и наработок.
-
Как создать/унаследовать класс?
- 1й способ
TOM.classes.create( 'область видимости / объект в котором нужно создавать класс', 'имя создаваемого класса', 'класс от которого нужно наследоваться', // Функция конструктор function constructor( ) { }, // -- Дальше идут функции в виде аргументов -- // function foo( ) { }, function bar( ) { } )
- 2й способ
TOM.classes.create( 'область видимости / объект в котором нужно создавать класс', 'имя создаваемого класса', 'класс от которого нужно наследоваться', // Функция конструктор function constructor( ) { }, // Функции в массиве или объекте [ function foo( ) { }, function bar( ) { } ] )
- 3й способ — такой-же как и 2й —, но в массиве находится ещё и конструктор
- 1й способ
-
Какие особенности данного скрипта?
Кроме совместимости с TOM.processor, в созданных классах более-менее адекватно работает вызов функций из родителя, при помощи:this.__parentCall__( ); // Вызов функции родителя исходя из arguments.callee this.__parentFunction__( 'имя функции', 'аргументы' ); // Вызов функции по имени
И много других мелочей.
Примечание: Я знаю что arguments.callee это плохо, и оно не работает в strict mode, но пока удобной замены не придумал.
Дэмо страница: tredsnet.github.io/TOM
GitHub репозиторий: github.com/tredsnet/TOM
Библиотека конечно не очень подготовлена для публикации, не «вычухан» код, не убраны лишние комментарии и заметки, где-то возможно нестандартное поведение (так как тестировалось только на нашем проекте). Но всему своё время, возможно и в таком виде библиотека будет кому-то полезна, а в случае заинтересованности пользователей — возможно и развитие в нужном направлении.
Спасибо что дочитали до конца. Буду рад любым комментариям, но прошу не забывать что библиотека создавалась под конкретные нужды, конкретного проекта.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.