TOM.js — особая библиотека, для особых случаев

Приветствую всех.
Не знаю на сколько верно я описал данную библиотеку в заголовке, но рассказать я хочу именно о ней.

b30a5e32164f4886924f29ab216abddb.png


Библиотека 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.js
    TOM.boot.load( '*', 'jquery',  function( ){ } );
    
    

    3 способ, задача: загрузить /jquery.boot.js
    TOM.boot.load( '', 'jquery', function( ){ } );
    
    

    4 способ, задача: загрузить /jquery.js
    TOM.boot.load( '', 'jquery.js', function( ){ } );
    
    

    5 способ, задача: загрузить code.jquery.com/jquery-1.12.0.min.js
    TOM.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й способ — такой-же как и  —, но в массиве находится ещё и конструктор
  • Какие особенности данного скрипта?


    Кроме совместимости с TOM.processor, в созданных классах более-менее адекватно работает вызов функций из родителя, при помощи:
    this.__parentCall__( ); // Вызов функции родителя исходя из arguments.callee
    this.__parentFunction__( 'имя функции', 'аргументы' ); // Вызов функции по имени
    
    

    И много других мелочей.
    Примечание: Я знаю что arguments.callee это плохо, и оно не работает в strict mode, но пока удобной замены не придумал.

Дэмо страница: tredsnet.github.io/TOM
GitHub репозиторий: github.com/tredsnet/TOM

Библиотека конечно не очень подготовлена для публикации, не «вычухан» код, не убраны лишние комментарии и заметки, где-то возможно нестандартное поведение (так как тестировалось только на нашем проекте). Но всему своё время, возможно и в таком виде библиотека будет кому-то полезна, а в случае заинтересованности пользователей — возможно и развитие в нужном направлении.

Спасибо что дочитали до конца. Буду рад любым комментариям, но прошу не забывать что библиотека создавалась под конкретные нужды, конкретного проекта.

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

© Habrahabr.ru