(() => { var loadingSubscribablesCache = Object.create(null), // Tracks component loads that are currently in flight loadedDefinitionsCache = Object.create(null); // Tracks component loads that have already completed ko.components = { get: (componentName, callback) => { var cachedDefinition = loadedDefinitionsCache[componentName]; if (cachedDefinition) { ko.tasks.schedule(() => callback(cachedDefinition.definition) ); } else { // Join the loading process that is already underway, or start a new one. var subscribable = loadingSubscribablesCache[componentName], completedAsync; if (subscribable) { subscribable.subscribe(callback); } else { // It's not started loading yet. Start loading, and when it's done, move it to loadedDefinitionsCache. subscribable = loadingSubscribablesCache[componentName] = new ko.subscribable(); subscribable.subscribe(callback); beginLoadingComponent(componentName, definition => { loadedDefinitionsCache[componentName] = { definition: definition }; delete loadingSubscribablesCache[componentName]; // For API consistency, all loads complete asynchronously. However we want to avoid // adding an extra task schedule if it's unnecessary (i.e., the completion is already // async). // // You can bypass the 'always asynchronous' feature by putting the synchronous:true // flag on your component configuration when you register it. if (completedAsync) { // Note that notifySubscribers ignores any dependencies read within the callback. // See comment in loaderRegistryBehaviors.js for reasoning subscribable['notifySubscribers'](definition); } else { ko.tasks.schedule(() => subscribable['notifySubscribers'](definition)); } }); completedAsync = true; } } }, clearCachedDefinition: componentName => delete loadedDefinitionsCache[componentName], register: (componentName, config) => { if (!config) { throw new Error('Invalid configuration for ' + componentName); } if (defaultConfigRegistry[componentName]) { throw new Error('Component ' + componentName + ' is already registered'); } defaultConfigRegistry[componentName] = config; } }; // The default loader is responsible for two things: // 1. Maintaining the default in-memory registry of component configuration objects // (i.e., the thing you're writing to when you call ko.components.register(someName, ...)) // 2. Answering requests for components by fetching configuration objects // from that default in-memory registry and resolving them into standard // component definition objects (of the form { createViewModel: ..., template: ... }) // Custom loaders may override either of these facilities, i.e., // 1. To supply configuration objects from some other source (e.g., conventions) // 2. Or, to resolve configuration objects by loading viewmodels/templates via arbitrary logic. var defaultConfigRegistry = Object.create(null); var createViewModelKey = 'createViewModel'; // Takes a config object of the form { template: ..., viewModel: ... }, and asynchronously convert it // into the standard component definition format: // { template: , createViewModel: function(params, componentInfo) { ... } }. // Since both template and viewModel may need to be resolved asynchronously, both tasks are performed // in parallel, and the results joined when both are ready. We don't depend on any promises infrastructure, // so this is implemented manually below. function loadComponent(componentName, callback) { var result = {}, config = defaultConfigRegistry[componentName] || {}, templateConfig = config['template'], viewModelConfig = config['viewModel']; if (templateConfig) { if (!templateConfig['element']) { throwError(componentName, 'Unknown template value: ' + templateConfig); } // Element ID - find it, then copy its child nodes var element = templateConfig['element']; var elemInstance = document.getElementById(element); if (!elemInstance) { throwError(componentName, 'Cannot find element with ID ' + element); } if (!elemInstance.matches('TEMPLATE')) { throwError(componentName, 'Template Source Element not a