Sign up (with export icon)

Advanced editor bundling in Node.js

Show the table of contents

This article presents how to create a CKEditor 5 editor bundle in Node.js. It is a step-by-step guide on creating an editor bundle from the CKEditor 5 package downloaded from CKEditor 5 Builder, but can be easily adapted for a variety of setups. An editor bundle is required by CKEditor Cloud Services to enable document storage, import and export as well as connection optimization features.

Bundle requirements

Copy link

The editor bundle that can be used by CKEditor Cloud Services must fulfill certain requirements:

  • It needs to be built into a single .js file.
  • The plugins should be added in the builtinPlugins property to be included in the editor bundle. You cannot use the config.plugins option for adding the plugins to the editor.
  • The output.library property in the webpack configuration should be set toCKEditorCS value.
  • Plugins used in the editor bundle cannot execute external HTTP requests.
  • The editor instance needs to be a default export in the editor bundle.
Note

The officially supported CKEditor 5 plugins already meet the requirement of not executing external requests in case of using them in the editor bundle by CKEditor Cloud Services. If your custom plugins send any external requests, then you have two options:

  • If this plugin does not modify the editor’s model or the editor’s output then you can remove it in the editor configuration during the editor bundle upload.
  • If this plugin modifies the editor’s model you can refactor it into 2 plugins. One of them will take care of the editing part, and only the other will make the HTTP requests. Thanks to this you will be able to remove one of the plugins during the editor bundle upload without affecting the output data.

Refer to the Uploading the editor bundle section for more information on removing the plugins from the editor configuration.

Creating a bundle

Copy link

The following example acts as a template for how to prepare an editor bundle configuration. It assumes that you have basic CKEditor 5 files - the package.json, main.js, index.html and style.css files which are the result of using CKEditor 5 Builder with “Self-hosted (npm)” integration method.

Dependencies

Copy link

This example uses the following dependencies:

  • ckeditor5
  • ckeditor5-premium-features
  • webpack
  • webpack-cli
  • mini-css-extract-plugin

If you followed the Builder setup, ckeditor5* packages should be already there. For the rest run:

npm install -D webpack webpack-cli mini-css-extract-plugin
Copy code

After that, your package.json dependencies sections should look as below:

{
  "dependencies": {
    "ckeditor5": "^42.0.0",
    "ckeditor5-premium-features": "^42.0.0"
  },
  "devDependencies": {
    "mini-css-extract-plugin": "^2.9.0",
    "webpack": "^5.92.1",
    "webpack-cli": "^5.1.4"
  }
}
Copy code

Editor setup

Copy link

It is highly recommended to use the same editor setup for both creating a bundle and integrating CKEditor 5 with the frontend layer. This way, it is easier to keep everything synchronized and consistent between the front end and back end. This requires slight adjustments to the above setup in how parts of the editor are defined and used.

The common parts for both setups are the editor class, list of plugins and editor config. We will split the main.js file into 3 files:

  • The main.js file that will define shared parts.
  • The main-fe.js file, that will be responsible for editor setup in the frontend application.
  • The main-be.js file, from which the editor bundle will be generated. It will be bundler entry file.

The first step is adjusting the main.js file.

// main.js

import {
    ClassicEditor,
    Essentials,
    Paragraph,
    Bold,
    Italic
} from 'ckeditor5';

import {
    RealTimeCollaborativeEditing
} from 'ckeditor5-premium-features';

// 1. Remove style imports. Those will be used in main-fe.js.
// import 'ckeditor5/ckeditor5.css';
// import 'ckeditor5-premium-features/ckeditor5-premium-features.css';

// import './style.css';

// 2. Create a list of plugins which will be exported.
const pluginList = [
    Essentials,
    Paragraph,
    Bold,
    Italic,
    RealTimeCollaborativeEditing
];

// 3. Adjust 'editorConfig' to utilize 'pluginList'.
const editorConfig = {
    plugins: pluginList,
    ...
}

// 4. Export shared parts, instead of initializing editor.
// ClassicEditor.create(document.querySelector('#editor'), editorConfig);

export {
    ClassicEditor,
    pluginList,
    editorConfig
}
Copy code

Next, we will create main-fe.js which will be used on the front end.

// main-fe.js

import { ClassicEditor, editorConfig } from './main.js';

// 1. Move style imports from main.js here.
import 'ckeditor5/ckeditor5.css';
import 'ckeditor5-premium-features/ckeditor5-premium-features.css';

import './style.css';

// 2. Initialize editor.
ClassicEditor.create(document.querySelector('#editor'), editorConfig);
Copy code

After that, main-be.js file, used for bundling, should be created.

// main-be.js

import { ClassicEditor, pluginList } from './main.js';

class CKEditorCS extends ClassicEditor {}

// 1. Assign plugins to be used in bundle.
CKEditorCS.builtinPlugins = pluginList;

// 2. Export editor class.
export default CKEditorCS;
Copy code

Frontend building

Copy link

With the above changes, your application can be build and bundled the same way as before. The only adjustment needed is changing the file which is imported.

<!doctype html>
<html lang="en">
    <head>...</head>
    <body>
        ...
        <!-- <script type="module" src="./main.js"></script> -->
        <script type="module" src="./main-fe.js"></script>
    </body>
</html>
Copy code

Bundler configuration

Copy link

Next, you need to prepare webpack configuration to generate a valid bundle.

const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' );

module.exports = {
    entry: './main-be.js', // Your editor build configuration.
    output: {
        filename: 'editor.bundle.js', // Content of this file is required to upload to CKEditor Cloud Services.
        library: 'CKEditorCS', // It is required to expose you editor class under the "CKEditorCS" name!

        libraryTarget: 'umd',
        libraryExport: 'default',
        clean: true
    },
    plugins: [
        new MiniCssExtractPlugin()
    ],
    module: {
        rules: [
            {
                test: /\.css$/i,
                use: [MiniCssExtractPlugin.loader, 'css-loader']
            }
        ]
    },
    performance: { hints: false }
};
Copy code

Generating the bundle

Copy link

Then, run the following commands inside the CKEditor 5 package folder:

npx webpack --mode production
Copy code

This command will result in the generation of the ./dist/editor.bundle.js file. This is the bundle file which then should be uploaded to CKEditor Cloud Services server.

Building the editor bundle with TypeScript

Copy link

The examples presented above are using JavaScript in the editor source files. Building the editor bundle with TypeScript is also possible. To start using TypeScript, follow these steps:

  1. Integrate TypeScript with webpack. Refer to this guide for more details.
  2. Change the extension of the editor source file to .ts. Adjust the entry path in webpack.config.js.
  3. Refer to the Working with TypeScript guide and adjust the imports and configuration settings if needed.

Creating a bundle from legacy configuration

Copy link

The following example acts as a template for how to prepare an editor build and bundler configuration. It assumes that you have an existing CKEditor 5 package with ckeditor.js, package.json and webpack.config.js files.

Dependencies

Copy link

This example uses the following dependencies:

  • @ckeditor/ckeditor5-editor-classic
  • @ckeditor/ckeditor5-basic-styles
  • @ckeditor/ckeditor5-essentials
  • @ckeditor/ckeditor5-paragraph
  • @ckeditor/ckeditor5-real-time-collaboration
  • @ckeditor/ckeditor5-dev-utils
  • @ckeditor/ckeditor5-theme-lark
  • webpack
  • webpack-cli
  • postcss-loader
  • raw-loader
  • style-loader

The package.json file dependencies sections should look similar to one below.

{
  "dependencies": {
    "@ckeditor/ckeditor5-basic-styles": "41.4.2",
    "@ckeditor/ckeditor5-editor-classic": "41.4.2",
    "@ckeditor/ckeditor5-essentials": "41.4.2",
    "@ckeditor/ckeditor5-paragraph": "41.4.2",
    "@ckeditor/ckeditor5-real-time-collaboration": "41.4.2"
  },
  "devDependencies": {
    "@ckeditor/ckeditor5-dev-utils": "^32.1.2",
    "@ckeditor/ckeditor5-theme-lark": "41.4.2",
    "postcss-loader": "^4.3.0",
    "raw-loader": "^4.0.2",
    "style-loader": "^2.0.0",
    "webpack": "^5.91.0",
    "webpack-cli": "^4.10.0"
  }
}
Copy code

Editor build configuration

Copy link

This file presents an example of an editor build configuration. It is used by the bundler as an entry file. This ckeditorcs.js file should be created based on your ckeditor.js file.

// ckeditorcs.js

// The editor base creator to use.
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';

// All plugins that you would like to use in your editor.
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import RealTimeCollaborativeEditing from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativeediting';

class CKEditorCS extends ClassicEditor {}

// Load all plugins you would like to use in your editor this way.
// This is the only way to load plugins into the editor which will then be used in CKEditor Cloud Services.
CKEditorCS.builtinPlugins = [
    Essentials,
    Paragraph,
    Bold,
    Italic,
    RealTimeCollaborativeEditing
];

// Export your editor.
export default CKEditorCS;
Copy code

Bundler configuration

Copy link

This file presents an example of the bundler configuration. Here webpack is used as the bundler.

// webpack.config.js

const { styles } = require( '@ckeditor/ckeditor5-dev-utils' );

module.exports = {
    entry: './ckeditorcs.js', // Your editor build configuration.
    output: {
        filename: 'editor.bundle.js', // Content of this file is required to upload to CKEditor Cloud Services.
        library: 'CKEditorCS', // It is required to expose you editor class under the "CKEditorCS" name!

        libraryTarget: 'umd',
        libraryExport: 'default'
    },
    module: {
        rules: [
            {
                test: /\.svg$/,
                use: [ 'raw-loader' ]
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'style-loader',
                        options: {
                            injectType: 'singletonStyleTag'
                        }
                    },
                    {
                        loader: 'postcss-loader',
                        options: styles.getPostCssConfig( {
                            themeImporter: {
                                themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
                            },
                            minify: true
                        } )
                    }
                ]
            }
        ]
    },
    performance: { hints: false }
};
Copy code

The most important part here is output.library field. Without this, the bundle will not work with the CKEditor Cloud Services server.

module.exports = {
    // ...
    output: {
        library: 'CKEditorCS', // It is required to expose you editor class under the "CKEditorCS" name!
        // ...
    },
    // ...
};
Copy code

Generating the bundle

Copy link

Run the following commands inside the CKEditor 5 package folder:

npm install
npx webpack --mode production
Copy code

This command will result in the generation of the ./dist/editor.bundle.js file. This is the bundle file which then should be uploaded to the CKEditor Cloud Services server.

Note

Your webpack output.* configuration may slightly vary. For CKEditor 5, it is also common to store build artifacts in the ./build/ folder. In such a case, the bundle would be the ./build/editor.bundle.js file.

Editor bundle with watchdog, context or “super builds”

Copy link

Cloud Services expects the Editor class as the default export in the bundled file. If you are using an editor bundle with Watchdog and context or you are building multiple editors as “super build”, you need to take extra steps to be able to upload these editors.

You can build multiple editor bundles from multiple source files for different purposes. A single webpack build can be then used to bundle all of them. Thanks to this approach, you will have an editor bundle that is compatible with the Cloud Services and you can still use the other editor bundle and take advantage of the “super builds”, watchdog, or context.

Creating a legacy editor bundle with Watchdog

Copy link

Assuming that you have a ckeditor.js source file that is exporting the editor with watchdog:

import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js';
import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog';
// Other plugins imports.

class Editor extends ClassicEditor {}

Editor.builtinPlugins = [
    // Imported plugins.
];

const watchdog = new EditorWatchdog( Editor );
export default watchdog;
Copy code

You can now create the ckeditorcs.js source file with the same content as the above file. The only difference is the export in this file. Instead of exporting the watchdog, you should export the Editor instance: export default Editor;.

With the two source files, you can tweak the webpack config to bundle both editors in a single build step:

'use strict';

const path = require( 'path' );
const webpack = require( 'webpack' );
const { bundler, styles } = require( '@ckeditor/ckeditor5-dev-utils' );
const { CKEditorTranslationsPlugin } = require( '@ckeditor/ckeditor5-dev-translations' );
const TerserWebpackPlugin = require( 'terser-webpack-plugin' );

const config = {
    devtool: 'source-map',
    performance: { hints: false },
    optimization: {
        minimizer: [
            new TerserWebpackPlugin( {
                sourceMap: true,
                terserOptions: {
                    output: {
                        comments: /^!/
                    }
                },
                extractComments: false
            } )
        ]
    },
    plugins: [
        new CKEditorTranslationsPlugin( {
            language: 'en',
            additionalLanguages: 'all'
        } ),
        new webpack.BannerPlugin( {
            banner: bundler.getLicenseBanner(),
            raw: true
        } )
    ],
    module: {
        rules: [
            {
                test: /\.svg$/,
                use: [ 'raw-loader' ]
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'style-loader',
                        options: {
                            injectType: 'singletonStyleTag',
                            attributes: {
                                'data-cke': true
                            }
                        }
                    },
                    {
                        loader: 'css-loader'
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: styles.getPostCssConfig( {
                                themeImporter: {
                                    themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
                                },
                                minify: true
                            } )
                        }
                    }
                ]
            }
        ]
    }
};

module.exports = [
    // The first bundle will have the editor with watchdog and can be used in your application.
    {
        ...config,
        entry: path.resolve( __dirname, 'src', 'ckeditor.js' ),
        output: {
            library: 'Watchdog',
            path: path.resolve( __dirname, 'build' ),
            filename: 'ckeditor.js',
            libraryTarget: 'umd',
            libraryExport: 'default'
        }
    },
    // The second bundle will be ready to be uploaded to the Cloud Services server.
    {
        ...config,
        entry: path.resolve( __dirname, 'src', 'ckeditorcs.js' ),
        output: {
            library: 'CKEditorCS',
            path: path.resolve( __dirname, 'build' ),
            filename: 'ckeditorcs.js',
            libraryTarget: 'umd',
            libraryExport: 'default'
        }
    }
];
Copy code

Similarly, you can build multiple bundles if you are using context or “super builds”. In the case of a bundle with context make sure that the bundle for Cloud Services includes all the plugins that the context editor has. For super builds, you can simply create a copy of a source file and export a single editor instance that you would like to upload to Cloud Services.

Building the editor bundle with TypeScript

Copy link

The examples presented above are using JavaScript in the editor source files. Building the editor bundle with TypeScript is also possible. To start using TypeScript, follow these steps:

  1. Integrate TypeScript with webpack. Refer to this guide for more details.
  2. Change the extension of the editor source file to .ts. Adjust the entry path in webpack.config.js.
  3. Refer to the Working with TypeScript guide and adjust the imports and configuration settings if needed.