Сумин Андрей, 17 мая 2007.
В прошлой заметке я остановился на уникальных именах файлов. В этой статье я опишу, что это и как я их использую.
Реализовав функцию указания зависимостей
require
, я могу не заботится о порядке подключения файлов, однако в самих файлах я должен указать пути к файлам, от которых они зависят. Указание пути прямыми ссылками приведет к куче проблем:
Вообще это неудобно, хочется указать зависимость от функционала или объекта, а не от файла.
После активных обсуждений с Сергеем Бережным появилась идея делить файлы на группы (общие и относящиеся к проекту библиотеки) и присваивать файлам имена типа "{alias}.name", которое можно транслировать как в имя файла "alias/name.js", так и в имя объекта "alias.name". alias будет идентифицировать группу файлов. Это позволит использовать файлы следующим образом:
alias.object = {
method: function(){
alias.object1.method();
alias.object2.method();
loaded('{alias}.object');
}
};
require(
['{alias}.object1', '{alias}.object2' ...],
alias.object.method
);
Итак, во всех файлах у нас есть строка вида "{alias}.name", надо вместо нее подставить реальный путь к файлу. Оказалось, удобно подключить на странице файл, который лежит там же, где и все остальные файлы с этим алиасом, и взять его путь как основу.
<script src="path/alias.js"></script>
При загрузке этот файл устанавливает значение алиаса, причем само значение берется из scr тега script, т.е. поменяв путь к срипту, мы меняем значение алиаса.
var alias = {};
getBaseAndSetAlias('alias', 'alias.js', 'utf-8');
Первой строкой создается объект, чтобы все объекты из этого алиаса были его методами. Во второй строке вызывается метод ядра, который берет путь к файлу (второй аргумент) и присваивает его алиасу (первый аргумент). Третий аргумент указывает кодировку файлов с данным алиасом. Ниже я приведу возможную реализацию этого метода.
Для начала надо получить тег script по имени файла.
var scriptsByFileName = [];
function getScriptByFileName (file, listener, /* private */ tries){
// если файл уже спрашивали раньше и он был найден
if(scriptsByFileName[file]){
listener(scriptsByFileName[file]);
return;
}
// устанавливаем количество попыток нахождения
// файла в ноль, если оно не пришло
tries = tries || 0;
// тут надо отфильтровать один файл из списка
// по имени
var scripts = document.getElementsByTagName('script');
for (var i = 0, l = scripts.length; i < l; i++){
var src = scripts[i].getAttribute('src');
if(src && src.indexOf(file) >= 0){
scriptsByFileName[file] = scripts[i];
break;
}
}
// если тег найден, то вызываем listener с тегом
// в качестве параметра
if(scriptsByFileName[file]){
listener(scriptsByFileName[file]);
// в противном случае пробуем позже
}else if(tries < 100){
window.setTimeout(function (){getScriptByFileName(file, listener, ++tries)}, 10);
}
}
Этот метод у меня сделан асинхронным, потому что иногда можно выполнять код тега скрипт, а сам тег может еще не будет в документе. Все последующие методы тоже должны быть асинхронными.
Теперь можно легко вычислить путь к файлам.
function getBase(file, listener){
function returnBase(script){
var src = script.getAttribute('src');
listener(src.substring(0, src.indexOf(file)));
}
getScriptByFileName(file, returnBase);
};
И установить значение алиаса.
var aliases = {};
function setAlias(name, value){
aliases[name] = value;
};
Функция, которая выполняет все это getBaseAndSetAlias.
function getBaseAndSetAlias(alias, file, charset){
this.getBase(file, function(base){
setAlias(alias, base);
});
};
Теперь можно строить пути к файлам по строкам вида "{alias}.name".
function construct(string){
// замена точек на слэши
string = string.replace(/\./g, '/');
// замена алиаса на его значение
function replacealias(match){
var alias = match.substr(1, match.length - 2);
return aliases[alias];
}
string = string.replace(/\{[^\}]+\}/ig, replacealias);
// удаляю лишние двойные слэши
var http = string.match(/^https?\:\/\//i) || '';
string = string.replace(/^https?\:\/\//i, '');
string = string.replace(/\/\//ig, '/');
// результирующий путь
return http + string + '.js';
};
В результате можно во всех зависимостях и уведомлениях (require, loaded) писать имена файлов вида "{alias}.name", а при смене месторасположения файлов менять путь только в теге script в HTML.
Превращать "{alias}.name" в имя объекта мне нужно всего в одном месте, но в очень важном. Указывая тип компонента, я использую точно такой же метод указания, только теперь не на файл, а на объект.
function getComponent(location){
// запоминаю алиас
var alias = /^\{[^\}]+\}/.exec(location);
alias = alias[0].substr(1, alias[0].length - 2);
// путь к обекту
var name = location.replace('{' + alias + '}.', '').split('.');
// получаю объект
var Component = window[alias];
for (var i = 0, l = name.length; i < l; i++){
Component = Component[name[i]];
}
// возвращаю полученный объект
return Component;
}
Когда объект получен, можно вызвать его инициализирующий метод и получить компонент на странице.
Теперь я в своем коде использую следующие конструкции:
<script src="path/alias.js"></script>
<div class="component" onclick="return {type:'{alias}.name'}"></div>
require(
['{alias}.object1', '{alias}.object2' ...],
alias.object.method
);
И изменение месторасположения файлов требует изменения пути всего в одном месте.
Примеры можно взять те же, что и в предыдущей статье.
Пример файла, который динамически загружает скрипты, тут: http://jsx.ru/scripts/b/1.1.0/jsx.js.
Ищет компоненты на странице тут: http://jsx.ru/scripts/b/1.1.0/Components.js