Преглед изворни кода

Implemented afterInit support for plugins.

Piotrek Koszuliński пре 9 година
родитељ
комит
8dc29a5ab9

+ 7 - 6
packages/ckeditor5-core/src/editor/editor.js

@@ -109,18 +109,19 @@ export default class Editor {
 		const config = this.config;
 		const config = this.config;
 
 
 		return loadPlugins()
 		return loadPlugins()
-			.then( initPlugins )
-			.then( () => {
-				this.fire( 'pluginsReady' );
-			} );
+			.then( ( loadedPlugins ) => {
+				return initPlugins( loadedPlugins, 'init' )
+					.then( () => initPlugins( loadedPlugins, 'afterInit' ) );
+			} )
+			.then( () => this.fire( 'pluginsReady' ) );
 
 
 		function loadPlugins() {
 		function loadPlugins() {
 			return that.plugins.load( config.get( 'plugins' ) || [] );
 			return that.plugins.load( config.get( 'plugins' ) || [] );
 		}
 		}
 
 
-		function initPlugins( loadedPlugins ) {
+		function initPlugins( loadedPlugins, method ) {
 			return loadedPlugins.reduce( ( promise, plugin ) => {
 			return loadedPlugins.reduce( ( promise, plugin ) => {
-				return promise.then( plugin.init.bind( plugin ) );
+				return promise.then( plugin[ method ].bind( plugin ) );
 			}, Promise.resolve() );
 			}, Promise.resolve() );
 		}
 		}
 	}
 	}

+ 22 - 2
packages/ckeditor5-core/src/plugin.js

@@ -17,7 +17,16 @@ import mix from '../utils/mix.js';
  */
  */
 export default class Plugin {
 export default class Plugin {
 	/**
 	/**
-	 * Creates a new Plugin instance.
+	 * Creates a new Plugin instance. This is first step of a plugin initialization.
+	 * See also {@link #init} and {@link #afterInit}.
+	 *
+	 * A plugin is always instantiated after its {@link #requires dependencies} and the
+	 * {@link #init} and {@link #afterInit} methods are called in the same order.
+	 *
+	 * Usually, you'll want to put your plugin's initialization code in the {@link #init} method.
+	 * The constructor can be understood as "before init" and used in special cases, just like
+	 * {@link #afterInit} servers for the special "after init" scenarios (e.g. code which depends on other
+	 * plugins, but which doesn't {@link #requires explicitly require} them).
 	 *
 	 *
 	 * @param {module:core/editor/editor~Editor} editor
 	 * @param {module:core/editor/editor~Editor} editor
 	 */
 	 */
@@ -71,12 +80,23 @@ export default class Plugin {
 	 */
 	 */
 
 
 	/**
 	/**
-	 * Initializes the plugin.
+	 * The second stage (after plugin {@link #constructor}) of plugin initialization.
+	 * Unlike the plugin constructor this method can perform asynchronous.
+	 *
+	 * A plugin's `init()` method is called after its {@link #requires dependencies} are initialized,
+	 * so in the same order as constructors of these plugins.
 	 *
 	 *
 	 * @returns {null|Promise}
 	 * @returns {null|Promise}
 	 */
 	 */
 	init() {}
 	init() {}
 
 
+	/**
+	 * The third (and last) stage of plugin initialization. See also {@link #constructor} and {@link #init}.
+	 *
+	 * @returns {null|Promise}
+	 */
+	afterInit() {}
+
 	/**
 	/**
 	 * Destroys the plugin.
 	 * Destroys the plugin.
 	 *
 	 *

+ 63 - 2
packages/ckeditor5-core/tests/editor/editor.js

@@ -15,28 +15,35 @@ class PluginA extends Plugin {
 	constructor( editor ) {
 	constructor( editor ) {
 		super( editor );
 		super( editor );
 		this.init = sinon.spy().named( 'A' );
 		this.init = sinon.spy().named( 'A' );
+		this.afterInit = sinon.spy().named( 'A-after' );
 	}
 	}
 }
 }
+
 class PluginB extends Plugin {
 class PluginB extends Plugin {
 	constructor( editor ) {
 	constructor( editor ) {
 		super( editor );
 		super( editor );
 		this.init = sinon.spy().named( 'B' );
 		this.init = sinon.spy().named( 'B' );
+		this.afterInit = sinon.spy().named( 'B-after' );
 	}
 	}
 }
 }
+
 class PluginC extends Plugin {
 class PluginC extends Plugin {
 	constructor( editor ) {
 	constructor( editor ) {
 		super( editor );
 		super( editor );
 		this.init = sinon.spy().named( 'C' );
 		this.init = sinon.spy().named( 'C' );
+		this.afterInit = sinon.spy().named( 'C-after' );
 	}
 	}
 
 
 	static get requires() {
 	static get requires() {
 		return [ PluginB ];
 		return [ PluginB ];
 	}
 	}
 }
 }
+
 class PluginD extends Plugin {
 class PluginD extends Plugin {
 	constructor( editor ) {
 	constructor( editor ) {
 		super( editor );
 		super( editor );
 		this.init = sinon.spy().named( 'D' );
 		this.init = sinon.spy().named( 'D' );
+		this.afterInit = sinon.spy().named( 'D-after' );
 	}
 	}
 
 
 	static get requires() {
 	static get requires() {
@@ -130,17 +137,25 @@ describe( 'Editor', () => {
 				plugins: [ PluginA, PluginD ]
 				plugins: [ PluginA, PluginD ]
 			} );
 			} );
 
 
+			const pluginsReadySpy = sinon.spy().named( 'pluginsReady' );
+			editor.on( 'pluginsReady', pluginsReadySpy );
+
 			return editor.initPlugins().then( () => {
 			return editor.initPlugins().then( () => {
 				sinon.assert.callOrder(
 				sinon.assert.callOrder(
 					editor.plugins.get( PluginA ).init,
 					editor.plugins.get( PluginA ).init,
 					editor.plugins.get( PluginB ).init,
 					editor.plugins.get( PluginB ).init,
 					editor.plugins.get( PluginC ).init,
 					editor.plugins.get( PluginC ).init,
-					editor.plugins.get( PluginD ).init
+					editor.plugins.get( PluginD ).init,
+					editor.plugins.get( PluginA ).afterInit,
+					editor.plugins.get( PluginB ).afterInit,
+					editor.plugins.get( PluginC ).afterInit,
+					editor.plugins.get( PluginD ).afterInit,
+					pluginsReadySpy
 				);
 				);
 			} );
 			} );
 		} );
 		} );
 
 
-		it( 'should initialize plugins in the right order, waiting for asynchronous ones', () => {
+		it( 'should initialize plugins in the right order, waiting for asynchronous init()', () => {
 			const asyncSpy = sinon.spy().named( 'async-call-spy' );
 			const asyncSpy = sinon.spy().named( 'async-call-spy' );
 
 
 			// Synchronous plugin that depends on an asynchronous one.
 			// Synchronous plugin that depends on an asynchronous one.
@@ -184,6 +199,52 @@ describe( 'Editor', () => {
 				);
 				);
 			} );
 			} );
 		} );
 		} );
+
+		it( 'should initialize plugins in the right order, waiting for asynchronous afterInit()', () => {
+			const asyncSpy = sinon.spy().named( 'async-call-spy' );
+
+			// Synchronous plugin that depends on an asynchronous one.
+			class PluginSync extends Plugin {
+				constructor( editor ) {
+					super( editor );
+					this.afterInit = sinon.spy().named( 'sync' );
+				}
+
+				static get requires() {
+					return [ PluginAsync ];
+				}
+			}
+
+			class PluginAsync extends Plugin {
+				constructor( editor ) {
+					super( editor );
+
+					this.afterInit = sinon.spy( () => {
+						return new Promise( ( resolve ) => {
+							setTimeout( () => {
+								asyncSpy();
+								resolve();
+							}, 0 );
+						} );
+					} );
+				}
+			}
+
+			const editor = new Editor( {
+				plugins: [ PluginA, PluginSync ]
+			} );
+
+			return editor.initPlugins().then( () => {
+				sinon.assert.callOrder(
+					editor.plugins.get( PluginA ).afterInit,
+					editor.plugins.get( PluginAsync ).afterInit,
+
+					// This one is called with delay by the async init.
+					asyncSpy,
+					editor.plugins.get( PluginSync ).afterInit
+				);
+			} );
+		} );
 	} );
 	} );
 } );
 } );