Skip to content
Frederico Caldeira Knabben 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.

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() );
	}
}

Why Not Naming Plugins?

In order to bundle a CKEditor build (create a single file distribution) the bundler must be able to analyze dependencies between plugins. The developer will only define, for example, that the bundle should contain the 'image/captionedimage', 'link' and 'creator-classic/classic' plugins, so the bundler must find the rest.

In the ES 6 modules system, dependencies between modules (and remember – plugin is simply a module) are resolved by import statements. You can run any bundler on the code shown in the Dependencies chapter and tools like Rollup or r.js (when bundling an AMD build of that code) will easily find out that the CaptionedImage plugin requires also two other modules.

We could name the plugins and define dependencies using those names (actually – paths, because the plugin loader needs paths), but that would mean that you're no longer able to apply the bundler of your choice, because only the official CKEditor bundler would know how to read the dependencies.

The alternative could be to use the names, but resolve them on the build step and automatically transform them into the current format. This would also allow using any bundler and plugins could always be retrieved by their names, but is definitely a much more complicated solution to code. But most importantly, that wouldn't be much of a difference.

The rule is simple – when you initialize the editor, you know which plugins you load so you can access them by the names you used. However, you don't know their dependencies, so you should not try to access them. On the other hand, when you're a plugin developer, you need to import your plugin's dependencies anyway, so then you alway have the keys to the plugin collection.

Clone this wiki locally