вторник, 9 июня 2015 г.

Require JS Module dependencies tree visualization

Файл rjs.js

// Порядок использования:
//
// 1. Загрузите данный файл первым в качестве зависимости внутри вашего главного JavaScript-файла.
// 2. Как только HTML-страница будет загружена выполните в консоли браузера команды:
//     window.rjs.buildTree();
//     console.log(window.rjs.tree);
//    В результате в объекте window.rjs.tree будет сформировано дерево зависимостей ваших модулей.
// 3. Для формирования списка зависимостей модулей вызовите метод window.rjs.toList().
//
// Методы доступные после загрузки HTML-страницы:
// rjs.buildTree()
// - Формирует карту зависимостей ваших модулей внутри объекта rjs.tree.
// - Выведите значение rjs.tree в консоли браузера, чтобы увидеть карту зависимостей модулей.
// rjs.toList()
// - Возвращает строку со списком зависимостей всех модулей.

// onResourceLoad - это внутренний крюк, который может быть использован для вывода сообщения о том, что модуль был
// создан или он экспортировал свое значение. Таким образом становится возможна отрисовка порядка загрузки модулей и их зависимостей.
requirejs.onResourceLoad = function (context, module, dependeciesArray) {

    // context - внутренний объект, испольуемый RequireJS для хранения экспортированных значений и состояния загрузки модулей.
    // В данном случае он никак не используется.
 
    // module - объект содержащий информацию о только что загруженном модуле.
    // Свойства объекта module:
    // module.name - нормализованное имя модуля. Для плагина это просто имя плагина.
    // module.url - URL исползуемый для загрузки файла модуля. Может быть относительным путем.
 
    // dependeciesArray - массив зависимостей определенных для данного модуля. Каждый элемент массива это объект module.
 
    // Сформировать глобальный объект window.rjs при загрузке первого модуля
    if (!window.rjs) {
        window.rjs = {
              tree: {} // дерево зависимостей модулей
            , buildTree: buildTree // функция формирования карты зависисмостей модулей (определена ниже)
            , toList: toList // функция формирования списка зависисмостей модулей (определена ниже)
            , toPaths: toPaths
            , buildPathsTree: buildPathsTree
            , getMainFileTree: getMainFileTree
            , drawTree: drawTree
        };
    }

    // Если модуля нет в дереве зависимостей, то добавить объект с данными нового модуля в дерево
    if (!window.rjs.tree[module.name]) {
        window.rjs.tree[module.name] = {moduleName: module.name, moduleDependeciesNames: [], moduleDependeciesTree: {}, modulePath: module.url ? module.url.split('?')[0] : ''};
        // moduleDependeciesNames - массив имен модулей, от которых зависит данный модуль
        // moduleMap - карта зависимостей данного модуля
    }
 
    // Для формирования полного дерева зависимостей модулей друг от друга
    var i, len;
    if (dependeciesArray) { // Если у модуля есть зависимости, то
        for (
            i = 0, len = dependeciesArray.length;
            i < len;
            i++
        ) { // добавить в массив имен зависимостей имена модулей, от которых зависит данный модуль
            window.rjs.tree[module.name].moduleDependeciesNames.push(dependeciesArray[i].name);
        }
    }
 
    // На данном этапе в дерево добавлены имена всех загруженных модулей.
    // В массив имен зависимостей всех модулей добавлены имена модулей, от которых они зависят.
    // Однако карта зависимостей у всех модулей пока пуста.

};

// Функция формирования карты дерева зависисмостей модулей
// На данном этапе карта зависимостей у всех модулей пока пуста.
// Поэтому данная функция заполняет занчения moduleMap для каждого модуля.
function buildTree () {
    var tree = window.rjs.tree
        , moduleName
        , currentModule
        , i
        , len
        , currentDependencyName;
    for (moduleName in tree) { // Для каждого имени модуля, содержащегося в дереве
        if (tree.hasOwnProperty(moduleName)) { // Если имя модуля содержится в дереве, то взять объект с данными данного модуля
            currentModule = tree[moduleName]; // вида {moduleName: module.name, moduleDependeciesNames: [], moduleMap: {}, modulePath: ''}
            for (
                i = 0, len = currentModule.moduleDependeciesNames.length;
                i < len;
                i++
            ) {
                currentDependencyName = currentModule.moduleDependeciesNames[i]; // Взять имя текущей зависимости данного модуля
                currentModule.moduleDependeciesTree[currentDependencyName] = tree[currentDependencyName]; // В карту данного модуля добавить объект текущего модуля из дерева
            }
        }
    }
}

// Фунцкия формирования списка зависисмостей модулей
// Данная функция возвращает строку, состоящую из списка зависисмостей модулей
function toList () {
    var list = []
        , tree = window.rjs.tree
        , moduleName
        , currentModule
        , i
        , len;
    for (moduleName in tree) { // Для каждого имени модуля, содержащегося в дереве
        if (tree.hasOwnProperty(moduleName)) { // Если имя модуля содержится в дереве, то взять объект с данными данного модуля
            currentModule = tree[moduleName]; // вида {moduleName: module.name, moduleDependeciesNames: [], moduleMap: {}, modulePath: ''}
            len = currentModule.moduleDependeciesNames.length;
            if (len) {
                for (i = 0; i < len; i++) {
                    list.push(moduleName + ' < ' + currentModule.moduleDependeciesNames[i]); // Добавить строку вида "moduleName > moduleDependecyName" для каждой зависимости данного модуля
                }
            } else {
                list.push(currentModule.moduleName); // Добавить строку вида "moduleName" для данного модуля
            }
        }
    }
    list.sort(function(a, b){if (a > b) {return 1} else if (a < b) {return -1;} else {return 0;}});
    return list.join('\n');
}

// Фунцкия формирования списка путей к файлам модулей
// Данная функция возвращает строку, состоящую из списка путей к файлам модулей
function toPaths () {
    var paths = []
        , tree = window.rjs.tree
        , moduleName
        , currentModule;
    for (moduleName in tree) { // Для каждого имени модуля, содержащегося в дереве
        if (tree.hasOwnProperty(moduleName)) { // Если имя модуля содержится в дереве, то взять объект с данными данного модуля
            currentModule = tree[moduleName]; // вида {moduleName: module.name, moduleDependeciesNames: [], moduleMap: {}, modulePath: ''}
            paths.push(
                   moduleName.search('.html') === -1
                ? currentModule.modulePath.replace('./', '')
                : 'js/' + moduleName
            ); // Добавить строку вида "modulePath" для данного модуля
        }
    }
    paths.sort(function(a, b){if (a > b) {return 1} else if (a < b) {return -1;} else {return 0;}});
    return paths.join('\n');
}

// Функция формирования дерева путей загрузки модулей
function buildPathsTree () {
    var paths = []
        , tree = window.rjs.tree
        , moduleName
        , currentModule;
    for (moduleName in tree) { // Для каждого имени модуля, содержащегося в дереве
        if (tree.hasOwnProperty(moduleName)) { // Если имя модуля содержится в дереве, то взять объект с данными данного модуля
            currentModule = tree[moduleName]; // вида {moduleName: module.name, moduleDependeciesNames: [], moduleMap: {}, modulePath: ''}
            paths.push(
                   moduleName.search('.html') === -1
                ? currentModule.modulePath.replace('./', '')
                : 'js/' + moduleName
            ); // Добавить строку вида "modulePath" для данного модуля
        }
    }
    paths.sort(function(a, b){if (a > b) {return 1} else if (a < b) {return -1;} else {return 0;}});
    return convertArrayOfDelimitedStringsIntoHierarchicalObject(paths, '/');
}

// Функция конвертации массива с разграниченными строками в иерархическое дерево
function convertArrayOfDelimitedStringsIntoHierarchicalObject (input, delimiter) {
    // input = ['Fred-Jim-Bob', 'Fred-Jim', 'Fred-Thomas-Rob', 'Fred'];
    // delimiter = '-';
    var output = []
        , i, j, k
        , inputLength
        , chain, chainLength
        , currentNode, currentNodeLength
        , wantedNode
        , lastNode
        , newNode;
    for (i = 0, inputLength = input.length; i < inputLength; i++) {
        chain = input[i].split(delimiter);
        currentNode = output;
        for (j = 0, chainLength = chain.length; j < chainLength; j++) {
            wantedNode = chain[j];
            lastNode = currentNode;
            for (k = 0, currentNodeLength = currentNode.length; k < currentNodeLength; k++) {
                if (currentNode[k].name === wantedNode) {
                    currentNode = currentNode[k].dependecies;
                    break;
                }
            }
            // If we couldn't find an item in this list of dependecies
            // that has the right name, create one:
            if (lastNode === currentNode) {
                currentNode[k] = {name: wantedNode, dependecies: []};
                newNode = currentNode[k];
                currentNode = newNode.dependecies;
            }
        }
    }
    return output.length > 0 ? output[0] : {};
}

// Функция выбора главного файла
function getMainFileTree () {

    function buildTreeObject (module) {
        var treeObject = {
              name: module.moduleName
            , path: module.modulePath
            , dependecies: []
        };
     
        for (var key in module.moduleDependeciesTree) {
            treeObject.dependecies.push(buildTreeObject(module.moduleDependeciesTree[key]));
        }
     
        treeObject.dependecies.sort(function(a, b){
            a = a.name;
            b = b.name;
            if (a > b) {return 1} else if (a < b) {return -1;} else {return 0;}
        });
 
        return treeObject;
    }

    return buildTreeObject(window.rjs.tree['main']);
 
}

// Функция отрисовки дерева зависимостей модулей
function drawTree (obj, prefix, options) {

    if (typeof obj === 'string') {obj = {name: obj};}
    if (prefix === undefined) {prefix = '';}
    if (options === undefined) {options = {};}

    function chr (s) {
        var chars = {
            '│' : '|',
            '└' : '`',
            '├' : '+',
            '─' : '-',
            '┬' : '-'
        };
        if (options.unicode === false)  {
            return chars[s];
        } else {
            return s;
        }
    };

    var dependecies = obj.dependecies || []
        , lines = (obj.name || '').split('\n')
        , splitter = '\n' + prefix + (dependecies.length ? chr('│') : ' ') + ' ';

    return prefix
           + lines.join(splitter) + '\n'
           + dependecies.map(function (module, index) {
                                    var last = (index === dependecies.length - 1)
                                       , more = (module.dependecies && module.dependecies.length)
                                       , prefix_ = prefix + (last ? ' ' : chr('│')) + ' ';

                                    return prefix
                                           + (last ? chr('└') : chr('├')) + chr('─')
                                           + (more ? chr('┬') : chr('─')) + ' '
                                           + drawTree(module, prefix_, options).slice(prefix.length + 2);
                               }).join('');

}


Файл index.js

require.config({
      paths: {
          onResourceLoad: 'lib/require/rjs'
        , main: './main'
      }
    , urlArgs: '_=' + Math.random()
});

require(
    [
          'onResourceLoad'
        , 'main'
    ]
    , function(
          onResourceLoad
        , main
    ) {
 
        main();

        if (console && console.log) {
            window.rjs.buildTree();
            //for (var key in window.rjs.tree) {console.log(key);}
            //console.log(window.rjs.toList());
            console.log(window.rjs.drawTree(window.rjs.getMainFileTree()));
            // console.log(window.rjs.toPaths());
            console.log(window.rjs.drawTree(window.rjs.buildPathsTree()));
        }

     
    }
);

Файл main.js

require.config({
      paths: {
          FirstModule: 'app/first/controller'
        , SecondModule: 'app/second/controller'
        , ThirdModule: 'app/third/controller'
      }
});

define(
    [
          'FirstModule'
        , 'SecondModule'
        , 'ThirdModule'
    ]
    , function(
          FirstModule
        , SecondModule
        , ThirdModule
    ) {

        return function () {
            FirstModule.init();
            SecondModule.init();
            ThirdModule.init();
        };
     
    }
);

Комментариев нет:

Отправить комментарий