/**
* модуль призван имитировать в браузере поведение функции require так, как это происходит в node js.
* - он позволяет писать свои модули, экспортировать из них объекты, функции и переменные так, как мы это делаем в nodejs
* - подключать данные модули необходимо так же как мы это делаем в nodejs
* для этих целей каждый подключаемый модуль автоматически:
* - оборачивается в само вызывающуюся функцию для создания собственного пространства имен
* - устанавливает режим "use strict",
* - получает параметр module переданный само вызывающейся функции и содержащий {exports:{}},
* - внутри области видимости само вызывающейся функции создается переменная const global=window
* данная реализация имеет несколько важных ограничений:
* - модуль require.js должен быть подключен в секции head в index.html
* - модуль использующий вызовы require(...) сам должен быть подключен с помощью require(...)
* на практике это реализуется следующим образом:
* в секции body в index.html должен быть единственный вызов require, который подключит
* первый модуль, являющийся главным файлом приложения. Все остальные модули, должны подключаться через
* вызовы require из модулей также подключенных через require.
* !!! это не касается сторонних библиотек, таких как jquery и прочих !!!
*/
(function(module) {
"use strict";
// запоминаем адресное пространство модуля для глобальной ссылки на функцию require
module.require = require.bind(this);
// создаем хранилище с ссылками на загруженные модули
const required = module.required = {};
/**
* Функция позволяет подключить произвольный скрипт или json так как это делается в nodejs
* @param {string} path путь к загружаемому скрипту (может быть полным URL а также относительным путем)
* @return {string} возвращает экспортируемое пространство имен подключаемого модуля
*/
function require( path ) {
// выход если не указан ни один скрипт для загрузки
if( !path || typeof path !== "string")
return;
// получаем полный URL загружаемого скрипта (работает даже если были передан относительный путь )
const sourceURL = getScriptUrl( path, window.location.href );
// если модуль уже был загружен ранее возвращаем ссылку
if( required[sourceURL] )
return required[sourceURL].exports;
// иначе загружаем файл
load( sourceURL );
}
/**
* Функция загружает скрипт или json по указанному URL
* @param {string} sourceURL полный url загружаемого скрипта
* @return {promise} возвращает промис, который будет выполнен после того как скрипт
* и все его зависимости будут загружены
*/
function load( sourceURL ) {
// готовим место под загружаемый скрипт в массиве required
required[sourceURL] = {exports:{}};
// создадим промис и ссылку на его функцию resolve
let onload;
let promise = new Promise( function( resolve, reject ) {
onload = resolve;
} );
// загружаем скрипт (и все его зависимости)
getURL(
sourceURL,
// если загрузка успешно завершена
function (code) {
// создадим массив в который будем складывать промисы
const waitLoad = [];
if( sourceURL.search(/\.json$/)+1 ){
// если запрошен json то просто парсим текст с json-ом
required[sourceURL].exports = JSON.parse(code);
onload();
}else{
// если скрипт, то производим в code поиск всех require и загружаем их
findRequires(code)
.forEach(str=>{
const targetURL = getScriptUrl(str, sourceURL);
const re = new RegExp('(["\'`])('+str+')\\1','g');
// заменяем пути к загружаемым модулям внутри всех вызовов require на полные URL этих модулей
code = code.replace(re,(a,b,path)=>{
return b+targetURL+b;
});
// если скрипт не был загружен ранее, грузим его и добавляем в промис
if(!required[targetURL])
waitLoad.push(load( targetURL ));
});
// когда все зависимости загружены добавляем в браузер текущий скрипт
Promise.all(waitLoad).then(values => {
addScript(sourceURL, code);
onload();
});
}
},
// если загрузить файл не удалось
function (error) {
console.error("ERROR: не удалось загрузить файл по адресу", sourceURL);
onload();
}
);
return promise;
}
/**
* Функция производит поиск всех вызовов require("...") и формирует
* список путей запрашиваемых в коде файлов
* @param {string} code текст исходников в которых производится поиск
* @return {array} возвращает список запрашиваемых в коде скриптов
*/
function findRequires(code) {
const list = [];
// удаляем комментарии типа // ...
const text = code.replace(/\/\*[\s\S]*?\*\//g,"\n")
// удаляем комментарии типа /* ... */
.replace(/\/\/.*?$/mg,"")
// если в строках было чтото похожее на require("...") то удаляем
.replace(/((["'`])[\s\S]*?\2)/g,str=>{
let ret = str.replace(/require[\s\S]*?\([\s\S]*?\)/g,"o_O");
return ret;
})
// ищем все require и добавляем их в список
.replace(/require[\s\S]*?\([\s\S]*?(["'`])(.*?)\1/g,(a,b,source)=>{
if(source)
list.push(source);
return a;
});
return list;
}
/**
* функция преобразует переданный ей путь до скрипта в полный URL.
* Умеет относительные пути.
* @param {string} path путь к загружаемому скрипту
* @param {string} curentURL путь к текущему скрипту
* @return {string} полный URL загружаемого скрипта
*/
function getScriptUrl( path, curentURL ) {
if( !path || typeof path !== "string" ) path = "";
const parser = document.createElement('a');
if( path.search(/^\.{1,2}/)+1 || !(path.search(/\//)+1) ){
parser.href = curentURL
.split(/\//)
.slice(0,-1)
.join("/")
+"/"+path;
}else{
parser.href = path;
}
return parser.href;
}
/**
* функция добавляет код скрипта на страницу,
* - оборачивет его в самовызывающуюся функцию для создания собственного пространства имен
* - устанавливает для скрипта режим "use strict",
* - передает модулю параметр module = {exports:{}},
* - внутри области видимости создает переменную const global=window
* @param {string} url полные URL добавляемого скрипта
* @param {string} code код добавляемого скрипта
*/
function addScript( url, code ) {
let head = document.getElementsByTagName( 'head' )[ 0 ];
let script = document.createElement( 'script' );
script.charset="utf-8";
script.type = 'text/javascript';
//script.src = url;
code =
"(function(module){\n\"use strict\";\nconst global=window;\n"
+code
+"\n})(required[\""+url+"\"]);";
script.innerHTML = code;
head.appendChild( script );
}
/**
* AJAX - взятый гдето с просторов Интернета
*/
var getURL = function ( url, success, error ) {
if ( !window.XMLHttpRequest ) return;
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
if ( request.readyState === 4 ) {
if ( request.status !== 200 ) {
if ( error && typeof error === 'function' ) {
error( request.responseText, request );
}
return;
}
if ( success && typeof success === 'function' ) {
success( request.responseText, request );
}
}
};
request.open( 'GET', url );
request.send();
};
})(this);