plugincollection.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. import Plugin from './plugin.js';
  7. import CKEditorError from './ckeditorerror.js';
  8. import log from './log.js';
  9. import load from '../load.js';
  10. /**
  11. * Manages a list of CKEditor plugins, including loading, resolving dependencies and initialization.
  12. *
  13. * @class PluginCollection
  14. */
  15. export default class PluginCollection {
  16. /**
  17. * Creates an instance of the PluginCollection class, initializing it with a set of plugins.
  18. *
  19. * @constructor
  20. * @param {core.Editor} editor
  21. */
  22. constructor( editor ) {
  23. /**
  24. * @protected
  25. * @type {core.Editor}
  26. */
  27. this._editor = editor;
  28. /**
  29. * @protected
  30. * @type {Map}
  31. */
  32. this._plugins = new Map();
  33. }
  34. /**
  35. * Collection iterator. Returns `[ key, plugin ]` pairs. Plugins which are
  36. * kept in the collection twice (under their name and class) will be returned twice.
  37. */
  38. [ Symbol.iterator ]() {
  39. return this._plugins[ Symbol.iterator ]();
  40. }
  41. /**
  42. * Gets the plugin instance by its name or class.
  43. *
  44. * @param {String/Function} key The name of the plugin or the class.
  45. * @returns {Plugin}
  46. */
  47. get( key ) {
  48. return this._plugins.get( key );
  49. }
  50. /**
  51. * Loads a set of plugins and add them to the collection.
  52. *
  53. * @param {String[]} plugins An array of plugins to load.
  54. * @returns {Promise} A promise which gets resolved once all plugins are loaded and available into the
  55. * collection.
  56. * @param {core.Plugin[]} returns.loadedPlugins The array of loaded plugins.
  57. */
  58. load( plugins ) {
  59. const that = this;
  60. const editor = this._editor;
  61. const loading = new Set();
  62. const loaded = [];
  63. return Promise.all( plugins.map( loadPlugin ) )
  64. .then( () => loaded );
  65. function loadPlugin( pluginClassOrName ) {
  66. // The plugin is already loaded or being loaded - do nothing.
  67. if ( that.get( pluginClassOrName ) || loading.has( pluginClassOrName ) ) {
  68. return;
  69. }
  70. let promise = ( typeof pluginClassOrName == 'string' ) ?
  71. loadPluginByName( pluginClassOrName ) :
  72. loadPluginByClass( pluginClassOrName );
  73. return promise
  74. .catch( ( err ) => {
  75. /**
  76. * It was not possible to load the plugin.
  77. *
  78. * @error plugincollection-load
  79. * @param {String} plugin The name of the plugin that could not be loaded.
  80. */
  81. log.error( 'plugincollection-load: It was not possible to load the plugin.', { plugin: pluginClassOrName } );
  82. throw err;
  83. } );
  84. }
  85. function loadPluginByName( pluginName ) {
  86. return load( PluginCollection.getPluginPath( pluginName ) )
  87. .then( ( PluginModule ) => {
  88. return loadPluginByClass( PluginModule.default, pluginName );
  89. } );
  90. }
  91. function loadPluginByClass( PluginClass, pluginName ) {
  92. return new Promise( ( resolve ) => {
  93. loading.add( PluginClass );
  94. assertIsPlugin( PluginClass );
  95. if ( PluginClass.requires ) {
  96. PluginClass.requires.forEach( loadPlugin );
  97. }
  98. const plugin = new PluginClass( editor );
  99. that._add( PluginClass, plugin );
  100. loaded.push( plugin );
  101. // Expose the plugin also by its name if loaded through load() by name.
  102. if ( pluginName ) {
  103. that._add( pluginName, plugin );
  104. }
  105. resolve();
  106. } );
  107. }
  108. function assertIsPlugin( LoadedPlugin ) {
  109. if ( !( LoadedPlugin.prototype instanceof Plugin ) ) {
  110. /**
  111. * The loaded plugin module is not an instance of Plugin.
  112. *
  113. * @error plugincollection-instance
  114. * @param {LoadedPlugin} plugin The class which is meant to be loaded as a plugin.
  115. */
  116. throw new CKEditorError(
  117. 'plugincollection-instance: The loaded plugin module is not an instance of Plugin.',
  118. { plugin: LoadedPlugin }
  119. );
  120. }
  121. }
  122. }
  123. /**
  124. * Resolves a simplified plugin name to a real path. The returned
  125. * paths are relative to the main `ckeditor.js` file, but they do not start with `./`.
  126. *
  127. * For instance:
  128. *
  129. * * `foo` will be transformed to `ckeditor5/foo/foo.js`,
  130. * * `core/editor` to `ckeditor5/core/editor.js` and
  131. * * `foo/bar/bom` to `ckeditor5/foo/bar/bom.js`.
  132. *
  133. * @param {String} name
  134. * @returns {String} Path to the module.
  135. */
  136. static getPluginPath( name ) {
  137. // Resolve shortened feature names to `featureName/featureName`.
  138. if ( name.indexOf( '/' ) < 0 ) {
  139. name = name + '/' + name;
  140. }
  141. return 'ckeditor5/' + name + '.js';
  142. }
  143. /**
  144. * Adds the plugin to the collection. Exposed mainly for testing purposes.
  145. *
  146. * @protected
  147. * @param {String/Function} key The name or the plugin class.
  148. * @param {Plugin} plugin The instance of the plugin.
  149. */
  150. _add( key, plugin ) {
  151. this._plugins.set( key, plugin );
  152. }
  153. }