浏览代码

Feature: PluginCollection - support for loading plugins using the plugin names.

Kamil Piechaczek 8 年之前
父节点
当前提交
b8f519ea57

+ 1 - 1
packages/ckeditor5-core/src/editor/editor.js

@@ -45,7 +45,7 @@ export default class Editor {
 		 * @readonly
 		 * @member {module:core/plugin~PluginCollection}
 		 */
-		this.plugins = new PluginCollection( this );
+		this.plugins = new PluginCollection( this, this.config.get( 'plugins' ) );
 
 		/**
 		 * Commands registered to the editor.

+ 43 - 6
packages/ckeditor5-core/src/plugincollection.js

@@ -19,8 +19,9 @@ export default class PluginCollection {
 	 * Creates an instance of the PluginCollection class, initializing it with a set of plugins.
 	 *
 	 * @param {module:core/editor/editor~Editor} editor
+	 * @param {Array.<module:core/plugin>} availablePlugins
 	 */
-	constructor( editor ) {
+	constructor( editor, availablePlugins ) {
 		/**
 		 * @protected
 		 * @member {module:core/editor/editor~Editor} module:core/plugin~PluginCollection#_editor
@@ -29,9 +30,20 @@ export default class PluginCollection {
 
 		/**
 		 * @protected
+		 * @member {Map.<String,module:core/plugin>} module:core/plugin~PluginCollection#_availablePlugins
+		 */
+		this._availablePlugins = new Map();
+
+		/**
+		 * @protected
 		 * @member {Map} module:core/plugin~PluginCollection#_plugins
 		 */
 		this._plugins = new Map();
+
+		// Save available plugins.
+		for ( const plugin of availablePlugins ) {
+			this._availablePlugins.set( plugin.pluginName, plugin );
+		}
 	}
 
 	/**
@@ -72,13 +84,13 @@ export default class PluginCollection {
 		return Promise.all( plugins.map( loadPlugin ) )
 			.then( () => loaded );
 
-		function loadPlugin( PluginConstructor ) {
+		function loadPlugin( PluginConstructorOrName ) {
 			// The plugin is already loaded or being loaded - do nothing.
-			if ( that.get( PluginConstructor ) || loading.has( PluginConstructor ) ) {
+			if ( that.get( PluginConstructorOrName ) || loading.has( PluginConstructorOrName ) ) {
 				return;
 			}
 
-			return instantiatePlugin( PluginConstructor )
+			return instantiatePlugin( PluginConstructorOrName )
 				.catch( ( err ) => {
 					/**
 					 * It was not possible to load the plugin.
@@ -86,14 +98,16 @@ export default class PluginCollection {
 					 * @error plugincollection-load
 					 * @param {String} plugin The name of the plugin that could not be loaded.
 					 */
-					log.error( 'plugincollection-load: It was not possible to load the plugin.', { plugin: PluginConstructor } );
+					log.error( 'plugincollection-load: It was not possible to load the plugin.', { plugin: PluginConstructorOrName } );
 
 					throw err;
 				} );
 		}
 
-		function instantiatePlugin( PluginConstructor ) {
+		function instantiatePlugin( PluginConstructorOrName ) {
 			return new Promise( ( resolve ) => {
+				const PluginConstructor = getPluginConstructor( PluginConstructorOrName );
+
 				loading.add( PluginConstructor );
 
 				assertIsPlugin( PluginConstructor );
@@ -110,6 +124,29 @@ export default class PluginCollection {
 			} );
 		}
 
+		function getPluginConstructor( PluginConstructorOrName ) {
+			if ( typeof PluginConstructorOrName === 'function' ) {
+				return PluginConstructorOrName;
+			}
+
+			const PluginConstructor = that._availablePlugins.get( PluginConstructorOrName );
+
+			if ( !PluginConstructor ) {
+				/**
+				 * The loaded plugin module is not available.
+				 *
+				 * @error plugincollection-instance
+				 * @param {*} plugin The constructor which is meant to be loaded as a plugin.
+				 */
+				throw new CKEditorError(
+					'plugincollection-instance: Given plugin name is not available.',
+					{ plugin: PluginConstructorOrName }
+				);
+			}
+
+			return PluginConstructor;
+		}
+
 		function assertIsPlugin( PluginConstructor ) {
 			if ( !( PluginConstructor.prototype instanceof Plugin ) ) {
 				/**

+ 104 - 22
packages/ckeditor5-core/tests/plugincollection.js

@@ -10,8 +10,8 @@ import Plugin from '../src/plugin';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import log from '@ckeditor/ckeditor5-utils/src/log';
 
-let editor;
-let PluginA, PluginB, PluginC, PluginD, PluginE, PluginF, PluginG, PluginH, PluginI, PluginX;
+let editor, availablePlugins;
+let PluginA, PluginB, PluginC, PluginD, PluginE, PluginF, PluginG, PluginH, PluginI, PluginJ, PluginK, PluginX;
 class TestError extends Error {}
 class ChildPlugin extends Plugin {}
 class GrandPlugin extends ChildPlugin {}
@@ -28,7 +28,13 @@ before( () => {
 	PluginG = createPlugin( 'G', GrandPlugin );
 	PluginH = createPlugin( 'H' );
 	PluginI = createPlugin( 'I' );
+	PluginJ = createPlugin( 'J' );
+	PluginK = createPlugin( 'K' );
 	PluginX = class extends Plugin {
+		static get pluginName() {
+			return 'X';
+		}
+
 		constructor( editor ) {
 			super( editor );
 
@@ -41,14 +47,35 @@ before( () => {
 	PluginF.requires = [ PluginE ];
 	PluginE.requires = [ PluginF ];
 	PluginH.requires = [ PluginI ];
+	PluginJ.requires = [ 'K' ];
+	PluginK.requires = [ PluginA ];
 
-	editor = new Editor();
+	editor = new Editor( {
+		plugins: []
+	} );
 } );
 
 describe( 'PluginCollection', () => {
+	beforeEach( () => {
+		availablePlugins = [
+			PluginA,
+			PluginB,
+			PluginC,
+			PluginD,
+			PluginE,
+			PluginF,
+			PluginG,
+			PluginH,
+			PluginI,
+			PluginJ,
+			PluginK,
+			PluginX
+		];
+	} );
+
 	describe( 'load()', () => {
 		it( 'should not fail when trying to load 0 plugins (empty array)', () => {
-			let plugins = new PluginCollection( editor );
+			let plugins = new PluginCollection( editor, availablePlugins );
 
 			return plugins.load( [] )
 				.then( () => {
@@ -57,7 +84,7 @@ describe( 'PluginCollection', () => {
 		} );
 
 		it( 'should add collection items for loaded plugins', () => {
-			let plugins = new PluginCollection( editor );
+			let plugins = new PluginCollection( editor, availablePlugins );
 
 			return plugins.load( [ PluginA, PluginB ] )
 				.then( () => {
@@ -68,8 +95,20 @@ describe( 'PluginCollection', () => {
 				} );
 		} );
 
+		it( 'should add collection items for loaded plugins using plugin names', () => {
+			let plugins = new PluginCollection( editor, availablePlugins );
+
+			return plugins.load( [ 'A', 'B' ] )
+				.then( () => {
+					expect( getPlugins( plugins ).length ).to.equal( 2 );
+
+					expect( plugins.get( 'A' ) ).to.be.an.instanceof( PluginA );
+					expect( plugins.get( 'B' ) ).to.be.an.instanceof( PluginB );
+				} );
+		} );
+
 		it( 'should load dependency plugins', () => {
-			let plugins = new PluginCollection( editor );
+			let plugins = new PluginCollection( editor, availablePlugins );
 			let spy = sinon.spy( plugins, '_add' );
 
 			return plugins.load( [ PluginA, PluginC ] )
@@ -83,8 +122,23 @@ describe( 'PluginCollection', () => {
 				} );
 		} );
 
+		it( 'should load dependency plugins defined by plugin names', () => {
+			let plugins = new PluginCollection( editor, availablePlugins );
+			let spy = sinon.spy( plugins, '_add' );
+
+			return plugins.load( [ 'J' ] )
+				.then( ( loadedPlugins ) => {
+					expect( getPlugins( plugins ).length ).to.equal( 3 );
+
+					expect( getPluginNames( getPluginsFromSpy( spy ) ) )
+						.to.deep.equal( [ 'A', 'K', 'J' ], 'order by plugins._add()' );
+					expect( getPluginNames( loadedPlugins ) )
+						.to.deep.equal( [ 'A', 'K', 'J' ], 'order by returned value' );
+				} );
+		} );
+
 		it( 'should be ok when dependencies are loaded first', () => {
-			let plugins = new PluginCollection( editor );
+			let plugins = new PluginCollection( editor, availablePlugins );
 			let spy = sinon.spy( plugins, '_add' );
 
 			return plugins.load( [ PluginA, PluginB, PluginC ] )
@@ -99,7 +153,7 @@ describe( 'PluginCollection', () => {
 		} );
 
 		it( 'should load deep dependency plugins', () => {
-			let plugins = new PluginCollection( editor );
+			let plugins = new PluginCollection( editor, availablePlugins );
 			let spy = sinon.spy( plugins, '_add' );
 
 			return plugins.load( [ PluginD ] )
@@ -115,7 +169,7 @@ describe( 'PluginCollection', () => {
 		} );
 
 		it( 'should handle cross dependency plugins', () => {
-			let plugins = new PluginCollection( editor );
+			let plugins = new PluginCollection( editor, availablePlugins );
 			let spy = sinon.spy( plugins, '_add' );
 
 			return plugins.load( [ PluginA, PluginE ] )
@@ -131,7 +185,7 @@ describe( 'PluginCollection', () => {
 		} );
 
 		it( 'should load grand child classes', () => {
-			let plugins = new PluginCollection( editor );
+			let plugins = new PluginCollection( editor, availablePlugins );
 
 			return plugins.load( [ PluginG ] )
 				.then( () => {
@@ -140,7 +194,7 @@ describe( 'PluginCollection', () => {
 		} );
 
 		it( 'should set the `editor` property on loaded plugins', () => {
-			let plugins = new PluginCollection( editor );
+			let plugins = new PluginCollection( editor, availablePlugins );
 
 			return plugins.load( [ PluginA, PluginB ] )
 				.then( () => {
@@ -152,7 +206,7 @@ describe( 'PluginCollection', () => {
 		it( 'should reject on broken plugins (forward the error thrown in a plugin)', () => {
 			let logSpy = testUtils.sinon.stub( log, 'error' );
 
-			let plugins = new PluginCollection( editor );
+			let plugins = new PluginCollection( editor, availablePlugins );
 
 			return plugins.load( [ PluginA, PluginX, PluginB ] )
 				// Throw here, so if by any chance plugins.load() was resolved correctly catch() will be stil executed.
@@ -169,11 +223,13 @@ describe( 'PluginCollection', () => {
 		} );
 
 		it( 'should reject when loading a module which is not a plugin', () => {
-			let logSpy = testUtils.sinon.stub( log, 'error' );
+			class Y {}
 
-			let plugins = new PluginCollection( editor );
+			availablePlugins.push( Y );
 
-			class Y {}
+			let logSpy = testUtils.sinon.stub( log, 'error' );
+
+			let plugins = new PluginCollection( editor, availablePlugins );
 
 			return plugins.load( [ Y ] )
 				// Throw here, so if by any chance plugins.load() was resolved correctly catch() will be stil executed.
@@ -188,14 +244,35 @@ describe( 'PluginCollection', () => {
 					expect( logSpy.args[ 0 ][ 0 ] ).to.match( /^plugincollection-load:/ );
 				} );
 		} );
+
+		it( 'should reject when loading non-existing plugin', () => {
+			let logSpy = testUtils.sinon.stub( log, 'error' );
+
+			let plugins = new PluginCollection( editor, availablePlugins );
+
+			return plugins.load( [ 'Non-Existing-Plugin' ] )
+				// Throw here, so if by any chance plugins.load() was resolved correctly catch() will be stil executed.
+				.then( () => {
+					throw new Error( 'Test error: this promise should not be resolved successfully' );
+				} )
+				.catch( ( err ) => {
+					expect( err ).to.be.an.instanceof( CKEditorError );
+					expect( err.message ).to.match( /^plugincollection-instance/ );
+
+					sinon.assert.calledOnce( logSpy );
+					expect( logSpy.args[ 0 ][ 0 ] ).to.match( /^plugincollection-load:/ );
+				} );
+		} );
 	} );
 
 	describe( 'get()', () => {
 		it( 'retrieves plugin by its constructor', () => {
-			let plugins = new PluginCollection( editor );
-
 			class SomePlugin extends Plugin {}
 
+			availablePlugins.push( SomePlugin );
+
+			let plugins = new PluginCollection( editor, availablePlugins );
+
 			return plugins.load( [ SomePlugin ] )
 				.then( () => {
 					expect( plugins.get( SomePlugin ) ).to.be.instanceOf( SomePlugin );
@@ -203,11 +280,13 @@ describe( 'PluginCollection', () => {
 		} );
 
 		it( 'retrieves plugin by its name and constructor', () => {
-			let plugins = new PluginCollection( editor );
-
 			class SomePlugin extends Plugin {}
 			SomePlugin.pluginName = 'foo/bar';
 
+			availablePlugins.push( SomePlugin );
+
+			let plugins = new PluginCollection( editor, availablePlugins );
+
 			return plugins.load( [ SomePlugin ] )
 				.then( () => {
 					expect( plugins.get( 'foo/bar' ) ).to.be.instanceOf( SomePlugin );
@@ -218,18 +297,21 @@ describe( 'PluginCollection', () => {
 
 	describe( 'iterator', () => {
 		it( 'exists', () => {
-			let plugins = new PluginCollection( editor );
+			let plugins = new PluginCollection( editor, availablePlugins );
 
 			expect( plugins ).to.have.property( Symbol.iterator );
 		} );
 
 		it( 'returns only plugins by constructors', () => {
-			let plugins = new PluginCollection( editor );
-
 			class SomePlugin1 extends Plugin {}
 			class SomePlugin2 extends Plugin {}
 			SomePlugin2.pluginName = 'foo/bar';
 
+			availablePlugins.push( SomePlugin1 );
+			availablePlugins.push( SomePlugin2 );
+
+			let plugins = new PluginCollection( editor, availablePlugins );
+
 			return plugins.load( [ SomePlugin1, SomePlugin2 ] )
 				.then( () => {
 					const pluginConstructors = Array.from( plugins )