var kit; kit = window.kit = window.kit || {}; kit.service = kit.service || {}; kit.config = kit.config || {}; (function(global){ var stateList = { undefined: 0, registered: 1, loaded: 3, installed: 4, }; var moduleTypeList = { loader: 'loader', import: 'import', }; /** * @class PluginManager * @param global * @param plugins * @memberOf kit */ var PluginManager = function(global, plugins = {}){ this.plugins = {}; this.global = global; this.registerPlugins(plugins); }; /** * * @type {kit.PluginManager|PluginManager} * @private */ var __proto = PluginManager.prototype; __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; }; /** * @return {boolean} */ __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; } /** * @todo !!! */ if( (this.__checkState(pluginId, stateList.installed) && typeof config.loader === 'function') || ( (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(this.__checkState(pluginId, stateList.registered)) { plugin.stateInit.installing = false; plugin.state = stateList.registered; if(plugin.config.autoRequire) { this.require(pluginId); } } } 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); var event = new CustomEvent("onRegister" + plugin.id + "Plugin", { "detail": {plugin: plugin} }); document.dispatchEvent(event); } return true; }; /** * @param pluginId * @param callback * @return {PluginManager} */ __proto.load = function (pluginId, callback) { var self = this; //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 getFileLoadNode(src) { src = src.match(/^[^#?]*/)[0]; return document.querySelector('script[src^="'+src+'"]') || document.querySelector('link[href^="'+src+'"]') || document.querySelector('meta[property="kit:file-load"][content="'+src+'"]'); } function loadScript(src, resolveCallback, rejectCallback) { src = String(src).split('?')[0]; if(typeof resolveCallback !== 'function'){ resolveCallback = function(){}; } if(typeof rejectCallback !== 'function'){ rejectCallback = function(res){ console.warn(res.message); }; } return new Promise(function(resolve, reject) { var loadNode = getFileLoadNode(src); if (loadNode) { global.config.debug && console.warn(`Скрипт ${src} уже загружен`); // self.__ready(resolve, [loadNode]); resolve(); } else { var script = document.createElement('script'); script.src = '' + src + '?__pm' + (new Date()).getTime(); script.onload = function() {resolve(script);}; script.onerror = function() {reject(new Error(`Ошибка загрузки скрипта ${src}`));}; document.head.append(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 loadNode = getFileLoadNode(src); if(loadNode){ self.__ready(resolve, [src, true]); // resolve(src, true); } else { var 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); } }); }); }) } function loadStyle(src) { return new Promise(function(resolve, reject) { var loadNode = getFileLoadNode(src); if(loadNode) { global.config.debug && console.warn(`Стиль ${src} уже загружен`); // self.__ready(resolve, [src]); resolve(); } else { var 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); } }); } 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' && !(configModule.hasOwnProperty('loader') && typeof configModule.loader === 'function')) { 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 jsPromiseExists = true, importPromiseExists = true; if(configModule.js.length === 0) { jsPromiseExists = false; } if((!configModule.module && configModule.type === 'import') || (configModule.type === 'loader' && typeof configModule.loader === 'function')) { importPromiseExists = false; } plugin.skipLoadPromise = !importPromiseExists && !jsPromiseExists && !depsPromise; return plugin.skipLoadPromise; }.bind(this); configModule.css.forEach(function(item, i, array){ loadStyle(configModule.path + item); }); var depsPromise = this.__getDepsPromise(this.depsCorrection(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); var event = new CustomEvent("onLoad" + plugin.id + "Plugin", { "detail": {plugin: plugin} }); document.dispatchEvent(event); 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)) { console.warn(`PluginManager.install(${pluginId}): key ${key} overwritten in kit (${plugin.state}).`); } this.global[key] = data; if(Array.isArray(aliasList) && aliasList.length) { aliasList.forEach(function (alias) { setGlobalExtension(alias, [], data) }.bind(this)) } }.bind(this); var installed = false; /** * @todo !!! */ 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); var returnData = plugin.config.loader.bind(window).call(module.exports, module, module.exports, this.require.bind(this), global); setGlobalExtension(pluginId, plugin.config.alias, returnData); installed = true; } 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); installed = true; } if(installed){ console.log('%cPluginManager.install: plugin ' + pluginId + ' установлен', "color: green;"); plugin.state = stateList.installed; } return installed; }.bind(this); this.__exec(pluginId, function(){ if(!this.__checkState(pluginId, stateList.installed)) { if(installer()){ plugin.config.onInstall.call(module.exports, plugin); var event = new CustomEvent("onInstall" + plugin.id + "Plugin", { "detail": {plugin: plugin} }); document.dispatchEvent(event); } } callback(); }.bind(this)); return this; }; /** * @name kitPluginManagerRequire * @param {string} pluginId * @param {callback} callback * @return {boolean} * @memberOf {kit} */ __proto.require = function (pluginId, callback) { var plugin = this.__getPlugin(pluginId); if(!plugin) { console.warn('PluginManager.require: plugin ' + pluginId + ' not found ('+(this.plugins[pluginId]?this.plugins[pluginId].state:'0')+')'); return undefined; } pluginId = plugin.id; var module = plugin.module; if(!this.__checkState(pluginId, stateList.registered)) { console.warn('PluginManager.require: plugin ' + pluginId + ' is not register'); return undefined; } if(typeof callback !== 'function') { callback = function(){}; } this.install(pluginId, callback); return module.exports; }; __proto.depsCorrection = function(deps){ if(!Array.isArray(deps) ) { deps = [deps]; } var correct = []; deps.forEach(function(pluginId, i, arr) { var realPlugin = this.__getPlugin(pluginId); if(realPlugin === undefined) { console.warn(`Plugin "${pluginId}" not registered!`); return false; } correct.push(realPlugin.id); }.bind(this)); return correct; }; /** * @memberOf kit */ __proto.exec = function (deps, callback, argArr = [], context = undefined) { if(typeof deps === 'function') { context = argArr; argArr = callback; callback = deps; deps = []; } if(context === undefined) { context = this; } deps = this.depsCorrection(deps); if(typeof argArr === 'undefined') { argArr = []; } deps = Array.from(new Set(deps.concat(global.config.requiredDependencies))); 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(context, getArgArr()); }); } else { callback.apply(context, getArgArr()) } }; __proto.__exec = function (pluginId, callback, argArr = []) { var plugin = this.plugins[pluginId]; var exec = function(_callback, argArr){ if(plugin.skipLoadPromise) { _callback.apply(this, argArr); } else { plugin.promise = plugin.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; }; /** * @param {(string|string[])} deps * @param {function} callback * @memberOf kit */ __proto.ready = function(deps, callback = undefined) { if(typeof deps === 'function') { callback = deps; deps = []; } this.__ready(this.exec, [deps, callback], this); }; __proto.__ready = function(fn, args = [], context = window) { context = context || this; if (/complete|interactive|loaded/.test(document.readyState)) { fn.apply(context, args); } else { document.addEventListener("DOMContentLoaded", function () { fn.apply(context, args); }.bind(this)); } }; __proto.__getPlugin = function (pluginId) { if(this.plugins.hasOwnProperty(pluginId)) { return this.plugins[pluginId]; } var _pluginId, alias; for (_pluginId in this.plugins) { if(this.plugins.hasOwnProperty(_pluginId) && this.plugins[_pluginId].config.alias.indexOf(pluginId) !== -1) { return this.plugins[_pluginId]; } } var pluginIdLowerCase = pluginId.toLowerCase(); for (_pluginId in this.plugins) { if(!this.plugins.hasOwnProperty(_pluginId)) { continue; } if(pluginIdLowerCase === _pluginId.toLowerCase()) { return this.plugins[_pluginId]; } for(alias of this.plugins[_pluginId].config.alias) { if(pluginIdLowerCase === alias.toLowerCase()) { return this.plugins[_pluginId]; } } } return undefined; }; /** * @class kitConfig */ var defaultConfig = { version: '1.10', assetsPath: '', requiredDependencies: [], autoloadPluginList: [], debug: false, defaultPhoneMask: '+7 (999) 999-99-99', }; 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'); manager.register('config', { type: moduleTypeList.loader, loader: function(module, exports, require, global) { var config = global.config || {}; config = Object.assign({}, defaultConfig, config); window.__pmConfig__ = global.config = config; } }); manager.install('config'); window.__PluginManager__ = manager; global.ready = manager.ready.bind(manager); global.exec = manager.exec.bind(manager); global.require = manager.require.bind(manager); /** * @var __pmGlobal__ */ window.__pmGlobal__ = global; })(kit);