Files
kit/assets/core/core.js
T
2021-08-17 22:01:03 +03:00

561 lines
21 KiB
JavaScript

/**
* @class kit
* @property {kit.bsCore}
* @property {kit.toolkit}
*/
window.kit = window.kit || {};
(function(global){
var stackPromise = {};
var stateList = {
undefined: 0,
registered: 1,
loaded: 3,
installed: 4,
};
var moduleTypeList = {
loader: 'loader',
import: 'import',
};
var PluginManager = function(global, modules = {}){
this.plugins = {};
this.global = global;
this.registerPlugins(modules);
};
var __proto = PluginManager.prototype;
__proto.__manual = function(){
return {
config: {
type: {
import: 'Значение по умолчанию. Нативный загрузчик модулей. Файл модуля содержит ключевые фразы "export" и "export default"',
loader: 'Файл модуля содержит функцию kit.PluginManager.register(pluginId, function(module, exports, require){})',
},
alias: 'тип Array|string. Список ключей, которые могут быть использованы вместо pluginId, как и в require() так и в global',
import: 'тип string. Ссылка на файл с модулем',
loader: 'Используется для загрузки модулей типа loader',
deps: 'тип Array|string. Зависимости, Id плагинов.',
css: 'тип Array|string. Массив путей к стилям. Стили грузятся синхронно при загрузке плагина. Без событий',
js: 'тип Array|string. Массив путей к скриптам. Загрузка идет по цепочке. Для синхронной загрузки скрипты ложатся в массив. пр: [1.js, [1.1.js, 1.2.js]]',
loadJsAfterModule: 'определяет порядок загрузки скриптов и модуля',
onInstall: 'функция выполняемая после установки плагина (однократно)',
onLoad: 'функция выполняемая после загрузки плагина (однократно)',
path: 'префикс пути для модуля, скриптов и стилей',
modulePath: 'префикс пути для модуля. если не указан, то берется config.path',
},
}
}();
__proto.__checkState = function(pluginId, state){
return this.plugins.hasOwnProperty(pluginId) && this.plugins[pluginId].state >= state;
};
/**
* @param plugins
* @return {PluginManager}
*/
__proto.registerPlugins = function (plugins) {
if(Array.isArray(plugins)) {
plugins = Object.assign({}, plugins);
}
for (var pluginId in plugins) {
if(plugins.hasOwnProperty(pluginId)) {
this.register(pluginId, plugins[pluginId]);
}
}
return this;
};
__proto.register = function (pluginId, config = {}) {
//console.warn('PluginManager.register: plugin ' + pluginId + ' ('+(this.plugins[pluginId]?this.plugins[pluginId].state:'0')+')');
var defaultPlugin = {
module: {
exports: {},
},
config: {
},
promise: new Promise(function(resolve, reject){resolve()}),
id: pluginId,
state: stateList.undefined,
stateInit: {
loading: false,
installing: false,
},
};
var plugin = this.__getPlugin(pluginId);
if(plugin) {
pluginId = plugin.id;
} else {
plugin = this.plugins[pluginId] = defaultPlugin;
}
if(
this.__checkState(pluginId, stateList.installed) ||
(
(plugin.stateInit.loading || this.__checkState(pluginId, stateList.loaded)) && typeof config === 'object'
)
) {
console.warn('PluginManager.register: plugin ' + pluginId + ' уже невозможно конфигурировать ('+plugin.state+')');
return false;
}
var loader;
if(typeof config === 'function') {
loader = config;
config = plugin.config;
config.loader = loader;
config.type = 'loader';
}
if(typeof config === 'object') {
if(typeof config.onLoad !== 'function') {
config.onLoad = function(){};
}
if(typeof config.onInstall !== 'function') {
config.onInstall = function(){};
}
if(typeof config.onRegister !== 'function') {
config.onRegister = function(){};
}
if(typeof config.path !== 'string') {
config.path = '';
}
if(!config.hasOwnProperty('modulePath')) {
config.modulePath = config.path;
} else if(typeof config.modulePath !== 'string') {
config.modulePath = config.path;
}
if(typeof config.js === 'string') {
config.js = [config.js];
} else if(!Array.isArray(config.js)) {
config.js = [];
}
if(typeof config.css === 'string') {
config.css = [config.css];
} else if(!Array.isArray(config.css)) {
config.css = [];
}
if(typeof config.alias === 'string') {
config.alias = [config.alias];
} else if(!Array.isArray(config.alias)) {
config.alias = [];
}
config.loadJsAfterModule = !!config.loadJsAfterModule;
config.type = config.type || 'import'; //loader
if(typeof config.deps === 'string') {
config.deps = [config.deps];
} else if(!Array.isArray(config.deps)) {
config.deps = [];
}
} else {
console.warn('PluginManager.register: plugin ' + pluginId + ' is not a valid format');
return false;
}
Object.assign(plugin.config, config);
if(!this.__checkState(pluginId, stateList.registered)) {
plugin.state = stateList.registered;
plugin.config.onRegister(plugin);
}
return true;
};
/**
* @param pluginId
* @param callback
* @return {PluginManager}
*/
__proto.load = function (pluginId, callback) {
//console.log('PluginManager.load: plugin ' + pluginId + ' ('+(this.plugins[pluginId]?this.plugins[pluginId].state:'0')+')');
if(typeof callback !== 'function') {
callback = function(){};
}
var plugin = this.__getPlugin(pluginId);
if(plugin) {
pluginId = plugin.id;
}
if(!this.__checkState(pluginId, stateList.registered)) {
console.warn('kit.load: plugin ' + pluginId + ' is not register');
return this;
}
if(this.__checkState(pluginId, stateList.loaded)) {
//console.warn('PluginManager.load: plugin ' + pluginId + ' уже загружен');
callback();
return this;
}
if(plugin.stateInit.loading) {
//console.log('PluginManager.install: plugin ' + pluginId + ' уже грузится');
this.__exec(pluginId, callback);
return this;
}
plugin.stateInit.loading = true;
var configModule = plugin.config;
var stackPush = function (callbackPromise, argArr = []){
if(typeof plugin.promise === 'undefined') {
plugin.promise = callbackPromise.apply(this, argArr);
} else {
plugin.promise = plugin.promise.then(function(m){
return callbackPromise.apply(this, argArr);
});
}
}.bind(this);
var getModulePromise = (function () {
function loadScript(src, resolveCallback, rejectCallback) {
if(typeof resolveCallback !== 'function'){
resolveCallback = function(){};
}
if(typeof rejectCallback !== 'function'){
rejectCallback = function(res){
console.warn(res.message);
};
}
return new Promise(function(resolve, reject) {
var script = document.querySelector('script[src^="'+src+'"]');
if (!script) {
script = document.createElement('script');
script.src = '' + src + '?__pm' + (new Date()).getTime();
script.onload = () => resolve(script);
script.onerror = () => reject(new Error(`Ошибка загрузки скрипта ${src}`));
document.head.append(script);
} else {
console.warn(`Скрип ${src} уже загружен`);
resolve(script);
}
}).then(resolveCallback, rejectCallback);
}
function syncLoadScriptList(list) {
return new Promise(function(resolve, reject) {
var i = 0;
var status = true;
var count = list.length;
var checkResolve = function(src, cond){
if(!cond) {
console.error(`Ошибка загрузки скрипта ${src}`)
}
status = status && cond;
i++;
if(i === count) {
resolve();
}
};
var syncPromise = new Promise(function(_resolve, _reject){_resolve();});
list.forEach(function(item, i, arr){
syncPromise.then(function(){
var src = configModule.path + item;
var script = document.querySelector('script[src^="'+src+'"]');
if (!script) {
script = document.createElement('script');
script.src = '' + src + '?__pm' + (new Date()).getTime();
script.onload = () => checkResolve(src, true);
script.onerror = () => checkResolve(src, false);
document.head.append(script);
} else {
checkResolve(src, true);
}
});
});
})
}
function loadStyle(src) {
return new Promise(function(resolve, reject) {
var link = document.querySelector('link[href^="'+src+'"]');
if (!link) {
link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = src + '?__pm' + (new Date()).getTime();
link.media = 'all';
document.head.append(link);
resolve(link);
} else {
console.warn(`Скрип ${src} уже загружен`);
resolve(link);
}
});
}
var getImportPromise = function (){
if(configModule.module && configModule.type === 'import') {
return import(configModule.modulePath + configModule.module).then(function(m) {
m.__esModule = true;
plugin.module.exports = m;
}.bind(this), function(r){
console.error(r);
});
} else if(configModule.module && configModule.type === 'loader') {
return loadScript(configModule.modulePath + configModule.module).then(function() {
}.bind(this));
}
return new Promise(function(resolve, reject){resolve();});
}.bind(this);
var getResPromise = function(){
var p = new Promise(function(resolve, reject){resolve();});
configModule.js.forEach(function(item, i, array){
if(Array.isArray(item)) {
p = p.then(function () {
return syncLoadScriptList(item);
});
} else {
p = p.then(function () {
return loadScript(configModule.path + item);
});
}
//loadScript(configModule.path + item);
});
return p;
}.bind(this);
var setSkipLoadPromise = function (depsPromise) {
var noJsPromise, noImportPromise;
if(configModule.js.length === 0) {
noJsPromise = true;
}
if((!configModule.module && configModule.type === 'import') || (configModule.type === 'loader' && typeof configModule.loader === 'function')) {
noImportPromise = true;
}
plugin.skipLoadPromise = noImportPromise && noJsPromise && !depsPromise;
return plugin.skipLoadPromise;
}.bind(this);
configModule.css.forEach(function(item, i, array){
loadStyle(configModule.path + item);
});
var depsPromise = this.__getDepsPromise(configModule.deps);
setSkipLoadPromise(depsPromise);
if(depsPromise) {
stackPush(function () {
return depsPromise;
});
}
if(configModule.loadJsAfterModule) {
stackPush(getImportPromise);
stackPush(getResPromise);
} else {
stackPush(getResPromise);
stackPush(getImportPromise);
}
this.__exec(pluginId, function(){
plugin.state = stateList.loaded;
plugin.skipLoadPromise = true;
configModule.onLoad.call(plugin.module.exports);
callback();
}.bind(this));
return plugin.promise;
}).bind(this);
getModulePromise();
return this;
};
/**
* @param pluginId
* @param callback
* @return {PluginManager}
*/
__proto.install = function(pluginId, callback){
//console.warn('PluginManager.install: plugin ' + pluginId + ' ('+(this.plugins[pluginId]?this.plugins[pluginId].state:'0')+')');
if(typeof callback !== 'function') {
callback = function(){};
}
var plugin = this.__getPlugin(pluginId);
if(plugin) {
pluginId = plugin.id;
}
var module = plugin.module;
if(!this.__checkState(pluginId, stateList.registered)) {
console.warn('PluginManager.install: plugin ' + pluginId + ' is not register');
return this;
}
if(this.__checkState(pluginId, stateList.installed)) {
callback();
return this;
}
if(plugin.stateInit.installing) {
//console.log('PluginManager.install: plugin ' + pluginId + ' уже устанавливается');
this.__exec(pluginId, callback);
return this;
}
plugin.stateInit.installing = true;
this.load(pluginId);
var installer = function(){
var setGlobalExtension = function (key, aliasList, data) {
if(data === undefined) {
return;
}
if(!this.global.hasOwnProperty(key)) {
this.global[key] = data;
} else {
console.warn('PluginManager.install: plugin ' + pluginId + ' already exist in kit ('+plugin.state+')');
}
if(Array.isArray(aliasList) && aliasList.length) {
aliasList.forEach(function (alias) {
setGlobalExtension(alias, [], data)
}.bind(this))
}
}.bind(this);
if(plugin.config.type === moduleTypeList.loader && typeof plugin.config.loader === 'function') {
var returnData = plugin.config.loader.bind(module.exports).call(module.exports, module, module.exports, this.require.bind(this), global);
setGlobalExtension(pluginId, plugin.config.alias, returnData);
} else if(plugin.config.type === moduleTypeList.import && typeof plugin.config.module === 'string') {
setGlobalExtension(pluginId, plugin.config.alias, (typeof module.exports === 'object' && module.exports.default !== undefined)?module.exports.default:module.exports);
}
console.log('PluginManager.install: plugin ' + pluginId + ' установлен');
plugin.state = stateList.installed;
}.bind(this);
this.__exec(pluginId, function(){
installer();
plugin.config.onInstall.call(module.exports, plugin);
callback();
}.bind(this));
return this;
};
__proto.require = function (pluginId, callback) {
//console.warn('PluginManager.require: plugin ' + pluginId + ' ('+(this.plugins[pluginId]?this.plugins[pluginId].state:'0')+')');
var plugin = this.__getPlugin(pluginId);
if(plugin) {
pluginId = plugin.id;
}
var module = plugin.module;
if(!this.__checkState(pluginId, stateList.registered)) {
console.warn('PluginManager.require: plugin ' + pluginId + ' is not register');
return false;
}
if(typeof callback !== 'function') {
callback = function(){};
}
this.install(pluginId, callback);
return module.exports;
};
__proto.exec = function (deps, callback, argArr = []) {
if(!Array.isArray(deps)) {
deps = [deps];
}
if(typeof callback !== 'function') {
return undefined;
}
var depsPromise = this.__getDepsPromise(deps);
var getArgArr = function () {
if(argArr.length === 0) {
deps.forEach(function (pluginId) {
argArr.push(this.require(pluginId));
}.bind(this));
return argArr;
}
return argArr;
}.bind(this);
if(depsPromise) {
depsPromise.then(function(){
callback.apply(this, getArgArr());
});
} else {
callback.apply(this, getArgArr())
}
};
__proto.__exec = function (pluginId, callback, argArr = []) {
var module = this.plugins[pluginId];
var exec = function(_callback, argArr){
if(module.skipLoadPromise) {
_callback.apply(this, argArr);
} else {
module.promise = module.promise.then(function(){
_callback.apply(this, argArr);
}.bind(this));
}
}.bind(this);
exec(callback, argArr);
};
__proto.__getDepsPromise = function (pluginList) {
var skipPromise = true;
var promise = new Promise((resolve, reject)=>{
var i = 0;
var checkModule = function(){
if(++i === pluginList.length + 1) {
resolve();
}
};
checkModule();
pluginList.forEach(function(pluginId, i, array){
if(!this.__checkState(pluginId, stateList.registered)) {
checkModule();
return false;
}
this.install(pluginId, checkModule);
skipPromise = skipPromise && this.plugins[pluginId].skipLoadPromise;
}.bind(this));
});
return !skipPromise?promise:undefined;
};
__proto.__getPlugin = function (pluginId) {
if(this.plugins.hasOwnProperty(pluginId)) {
return this.plugins[pluginId];
}
for (var _pluginId in this.plugins) {
if(this.plugins.hasOwnProperty(_pluginId) && this.plugins[_pluginId].config.alias.indexOf(pluginId) !== -1) {
return this.plugins[_pluginId];
}
}
return undefined;
};
var manager = new PluginManager(global);
manager.register('PluginManager', {
alias: 'pm',
type: moduleTypeList.loader,
loader: function(module, exports, require) {
return module.exports = manager;
}
});
manager.install('PluginManager');
global.PluginManager = manager;
global.exec = manager.exec.bind(manager);
global.require = manager.require.bind(manager);
})(kit || window);