Skip to content
Piotrek Koszuliński edited this page Feb 4, 2016 · 12 revisions

Before talking about plugins, let's first have a clear point about "packages", to avoid confusion.

Packages give the physical infrastructure for organizing files, installing and updating code and resolving dependencies. The JavaScript modules kept in such packages are the building blocks of CKEditor.

Packages generally include JavaScript modules. Many modules (e.g. from the core library) bring the infrastructure to create and manage editors. Plugins, in the other hand, are modules that add visual and interactive features to them.

Currently there are the following types of plugins: features, themes and creators. Read more about them in the Architecture Overview.

In terms of the code plugins are JavaScript modules which export classes inheriting from the Plugin class. They can feature asynchronous initialization and can require other plugins to be initialized before them. Loading and initialization of plugins is handled by the editor during its bootstrap.

A simple plugin could look like this:

import Plugin from '../core/plugin.js';

export default class MyPlugin extends Plugin {
	constructor( editor ) {
		super( editor );

		this.prepareYourself();
	}

	// The init() method is executed right after all plugins are instantiated.
	init() {
		this.editor.dance();
	}
}

Features, Creators and Themes

The Plugin class is just a base class for more specialized ones – Feature, Creator and Theme. This means that while implementing a CKEditor feature you will want to define a class which inherits from the Feature class.

Thanks to the fact that features, creators and themes implement the same concept and are loaded by a single plugin collection, they can easily require one another and are automatically initialized in the right order. For instance, a specific creator or theme may depend on a specific feature, so this feature should be automatically initialized before them.

Plugin Initialization

Plugins are initialized by the editor upon its creation. Each editor may have a different set of plugins enabled. The API makes it possible to load and initialize plugins on demand but that's not an officially supported practice (yet, because it may be an interesting enhancement in the future).

Each plugin module returns a class which is then instantiated for each editor using it. Following that, the init() method of the plugin instance is called.

Paths

Usually, the developer needs to provide paths to the plugins that should be initialized. For example, a simple editor using the creator located in ckeditor5/creator-classic/classic.js and two features located in ckeditor5/image/captionedimage.js and ckeditor5/link/link.js files (note: these are paths produced by the builder) can be initialized as follows:

CKEDITOR.create( '#editor', {
	creator: 'creator-classic/classic',
	features: [
		'image/captionedimage',
		'link'
	]
} );

As you can see config.creator and config.features accept simplified plugin paths. They are resolved by the PluginCollection.getPluginPath() method to real paths.

Note: It's also possible to initialize plugins by providing their classes (constructors) what may be useful in certain use cases. See ckeditor/ckeditor5-core#175.

Initialization Order

CKEditor guarantees that plugins are initialized in their dependency order. In the below example, let's suppose that A depends on X (not in the configuration) and B depends on C. In such case, the plugins will be initialized in this order: X, A, C, B, D, E.

CKEDITOR.create( '#editor', {
    features: 'A,B,C,D,E'
} );

Asynchronous Initialization

To enable asynchronous initialization, the init() method must return a promise. For example:

class extends Plugin {
    init() {
        return new Promise( ( resolve ) => {
            setTimeout( () => {
                // Async stuff.

                resolve(); // Resolve when ready.
            }, 1000 );
        } );
    }
}

Plugin Identification

A plugin is identified by its class or a path if it was provided to the plugin collection. For instance, when the editor has been created like this:

CKEDITOR.create( '#editor', {
	creator: 'creator-classic/classic',
	features: [
		'image/captionedimage',
		'link'
	]
} );

then you can access the creator and plugins as follows:

editor.plugins.get( 'creator-classic/creator' );
editor.plugins.get( 'image/captionedimage' );
editor.plugins.get( 'link' );

// All calls return instances of their respective plugins.

However, if the CaptionedImage plugin requires the plugin located in ckeditor/image/baseimage.js, then this is not going to work:

editor.plugins.get( 'image/baseimage' ); // -> null

The reason why this plugin isn't available by its path is that the plugin was initialized as a dependency of the CaptionedImage plugin and dependencies are defined as plugin classes.

Note: If you wonder why plugins have no names and are identified by their constructors read ckeditor/ckeditor5-design#121.

Dependencies

Let's see how the CaptionedImage plugin would be implemented. In order to work it requires the BaseImage plugin to be initialized before it.

import BaseImage from './baseimage.js'; // Refers to the same package.
import Feature from '../core/feature.js'; // Refers to the ckeditor5-core package.

export default class CaptionedImage extends Feature {
	// This getter lets the PluginCollection know that CaptionedImage requires BaseImage.
	// As you can see - we do this by exposing the BaseImage class, not the path to it.
	static get requires() {
		return [ BaseImage ];
	}

	constructor() {
		const baseImage = this.editor.plugins.get( BaseImage );

		// The baseImage is already instantiated, but it's not initialized yet.
	}

	init() {
		const baseImage = this.editor.plugins.get( BaseImage );

		// Now, the baseImage is also initialized.

		baseImage.addFeature( this.getFeature() );
	}
}