Created | Urls | Favourites | Opened | Upvotes | Comments |
2 | 1 | 613 | 0 | 0 |
Updated Sep 2020. This CKEditor 5 tutorial is a step-by-step guide how to build a custom CKEditor 5 - allowing maximum flexibility in your CKEditor 5.
Then you finish this CKEditor 5 tutorial, you will be able to build CKEditor 5 from source and configure it to your own exact needs.
Index :
Appendixes :
CKEditor 5 is a modular framework for building Rich Text Editors and we can either download one of the pre-build CK5 editors or we can build our own version based on our particular needs. Building our own CK5 editor is a LOT more flexible as well as allowing custom functionality not otherwise avaliable.
The CKEditor team encourages us to build our own custom CK5 editor versions and have made it easy to do so - here I will build a simple custom editor without any extra just to get the basic CKEditor building covered, it will look like this :
There are basically 2 ways to build a CKEditor 5 from source :
To build a CKEditor 5 from source, a few tools are necessary :
Below is the folder structure in which we will create the custom CKEditor 5 build, starting with a build root folder called CustomCKEditor. All folders & files in bold you must create yourself. All folders & files NOT in bold will be automatically created later.
Ok, let's get the folder structure created :
CKEditor 5 packages are available for download from the NPM repository @npmjs.com, however because of dependencies between packages it is not viable to manually download the packages, instead we need a package manager, NPM, to download the packages for us.
The NPM tool is used for both creating, uploading & downloading packages and needs to be configured using a file called package.json and even while this tutorial is only using NPM for downloading packages, we will still add a few properties to package.json related to creating a package, eg. name & version, to make it a more complete package.json file.
For our purpose (downloading packages) the package.json file consist of 2 main sections :
{
"name": "customckeditor",
"version": "1.0.0",
"description": "Custom CKEditor 5 for test.",
"author": "Rasmus Rummel",
"license": "GPL-2.0-or-later",
"homepage": "https://topiqs.online/1119",
"dependencies": {
"@ckeditor/ckeditor5-editor-classic": "^22.0.0",
"@ckeditor/ckeditor5-essentials": "^22.0.0",
"@ckeditor/ckeditor5-basic-styles": "^22.0.0",
"@ckeditor/ckeditor5-heading": "^22.0.0",
"@ckeditor/ckeditor5-block-quote": "^22.0.0",
"@ckeditor/ckeditor5-list": "^22.0.0",
"@ckeditor/ckeditor5-link": "^22.0.0",
"@ckeditor/ckeditor5-theme-lark": "^22.0.0"
},
"devDependencies": {
"@ckeditor/ckeditor5-dev-utils": "^22.0.0",
"@ckeditor/ckeditor5-dev-webpack-plugin": "22.0.0",
"postcss-loader": "3.0.0",
"raw-loader": "4.0.1",
"style-loader": "1.2.1",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12"
}
}
Before continuing let us just short note what each package is for :
Note that the official CKEditor 5 build guides includes 1 more dev package :
With the package.json file finished, we can now execute NPM to download the packages specified in package.json (NPM will look for a package.json file in the folder from where the NPM tool is executed).
Coding the custom CKEditor 5 object is a lot easier than it sounds - you mostly just specify which packages to use (all the CKEditor 5 code already exists within the NPM packages we downloaded above).
import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
import List from '@ckeditor/ckeditor5-list/src/list';
import Link from '@ckeditor/ckeditor5-link/src/link';
export default class MyCustomEditor extends ClassicEditorBase { } //the classname is not important since we use export default
MyCustomEditor.builtinPlugins = [
Essentials, //necessary, eg. Enter will not work within this plugin
Bold,Italic,
Underline,
Heading,
BlockQuote,
List,
Link
];
Notice that the Bold, Italic and Underline plugins all come from the same package : @ckeditor/ckeditor5-basic-styles
Webpack is the tool that will bundle the app.js file with all the NPM packages directly or indirectly referenced in app.js creating the CKEditor 5 build.
However, we have to tell Webpack that it has to take the app.js file as the entry point of the bundling, how to process different kind of resources, eg. what loader to use for css and icons and what to call the output file (here ckeditor.js) and what module technology to use etc - we do that in a file called webpack.config.js (Webpack will automatically search for webpack.config.js in the same folder from which Webpack is executed).
'use strict'; // ES6 modules are expected to always use strict.
const path = require('path'); // We use this object below to calculate path for output (is often also used for entry).
const CKEditorWebpackPlugin = require('@ckeditor/ckeditor5-dev-webpack-plugin'); // for localization.
const { styles } = require('@ckeditor/ckeditor5-dev-utils'); // We use this object below to add a CKEditor 5 theme (the ckeditor5-theme-lark).
module.exports = {
entry: './app.js', // starting point(s) for building the bundle.
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'ckeditor.js', // The name of the file that hold the editor code (default is bundle.js, but here we call it ckeditor.js).
library: 'CustomEditor', // The name of the editor object.
libraryTarget: 'var', // Configure how the library will be exposed.
libraryExport: 'default' // Configure which module or modules will be exposed via the libraryTarget.
},
plugins: [
new CKEditorWebpackPlugin({
language: 'en', // default language.
additionalLanguages: 'all' // this will write ALL the language files out.
})],
module: {
rules: [
{
test: /\.svg$/,
use: ['raw-loader']
},
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
options: {
injectType: 'singletonStyleTag',
attributes: {
'data-cke': true
}
}
},
{
loader: 'postcss-loader',
options: styles.getPostCssConfig({
themeImporter: {
themePath: require.resolve('@ckeditor/ckeditor5-theme-lark')
},
minify: true
})
}
]
}
]
},
mode: 'production', // if the mode property is not set, Webpack will warn us and set it to production.
performance: { hints: false } // don't warn us that the bundle is bigger than 200kb.
};
Note that the official CKEditor 5 build from source webpack.config.js does NOT contain the path statement and will result in a "path is not defined" error then executing webpack.
With the CKEditor 5 packages downloaded, the app.js editor code created and the Webpack configuration file done, we are ready to execute Webpack to create our custom build CKEditor 5 :
With the editor module created (in the dist\ckeditor.js file) it is time to test it and finally see a real CKEditor 5 in the browser.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="dist/ckeditor.js"></script>
<script src="dist/translations/da.js"></script>
</head>
<body>
<div id="editor">
<p>Editor content goes here.</p>
</div>
<script>
CustomEditor.create(document.querySelector('#editor'), {
toolbar: [
'heading', // Heading plugin
'|', // separator
'bold', // Bold plugin
'italic', // Italic plugin
'underline', // Underline plugin
'|','numberedlist', // List plugin
'bulletedlist', // List plugin
'|','blockQuote', // BlockQuote plugin
'link', // Link plugin
'|','undo', // Essentials plugin
'redo' // Essentials plugin
],language: 'da'
})
.then(editor => {
window.editor = editor;
})
.catch(err => {
console.error(err.stack);
});
</script>
</body>
</html>
Just a few handy NPM commands then building a CKEditor 5 :
While there exists no such thing as a 'full' CK5 editor, it DOES give mening to make a CK5 editor loaded with as much functionality as possible if for nothing else then for having a list of available plugins and how to reference them (note that here I don't include commercial modules).
To max-load a CKEditor 5, we need to change package.json, app.js & index.html (only the webpack.config.js file is unchanged).
{
"name": "customckeditor",
"version": "1.0.0",
"description": "Custom CKEditor 5 for test.",
"author": "Rasmus Rummel",
"license": "GPL-2.0-or-later",
"homepage": "https://topiqs.online/1119",
"dependencies": {
"@ckeditor/ckeditor5-editor-classic": "12.1.3",
"@ckeditor/ckeditor5-essentials": "11.0.4",
"@ckeditor/ckeditor5-basic-styles": "11.1.3",
"@ckeditor/ckeditor5-heading": "11.0.4",
"@ckeditor/ckeditor5-block-quote": "11.1.2",
"@ckeditor/ckeditor5-list": "12.0.4",
"@ckeditor/ckeditor5-link": "11.1.1",
"@ckeditor/ckeditor5-autoformat": "11.0.4",
"@ckeditor/ckeditor5-alignment": "11.1.3",
"@ckeditor/ckeditor5-font": "11.2.1",
"@ckeditor/ckeditor5-highlight": "11.0.4",
"@ckeditor/ckeditor5-table": "13.0.2",
"@ckeditor/ckeditor5-image": "13.1.2",
"@ckeditor/ckeditor5-media-embed": "11.1.3",
"ckeditor5-upload-adapter": "1.0.2",
"ckeditor5-syntaxer": "1.0.9",
"@ckeditor/ckeditor5-paste-from-office": "11.0.4",
"@ckeditor/ckeditor5-theme-lark": "14.1.1"
},
"devDependencies": {
"@ckeditor/ckeditor5-dev-webpack-plugin": "8.0.1",
"@ckeditor/ckeditor5-dev-utils": "12.0.1",
"postcss-loader": "3.0.0",
"raw-loader": "3.0.0",
"style-loader": "0.23.1",
"uglifyjs-webpack-plugin": "2.1.3",
"webpack": "4.15.1",
"webpack-cli": "3.3.5"
}
}
devDependencies have not changed only dependencies, however even so let's short go through them all again (this color representing new dependencies) :
import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials'; // enables clipboard, Enter, ShiftEnter, typing and undo support
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
import FontFamily from '@ckeditor/ckeditor5-font/src/fontfamily';
import FontSize from '@ckeditor/ckeditor5-font/src/font';
import FontBackgroundColor from '@ckeditor/ckeditor5-font/src/fontbackgroundcolor';
import FontColor from '@ckeditor/ckeditor5-font/src/fontcolor';
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline';
import StrikeThrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough';
import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript';
import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript';
import Alignment from '@ckeditor/ckeditor5-alignment/src/alignment';
import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight';
import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat';
import List from '@ckeditor/ckeditor5-list/src/list';
import Link from '@ckeditor/ckeditor5-link/src/link';
import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice';
import Table from '@ckeditor/ckeditor5-table/src/table';
import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar';
import Syntaxer from 'ckeditor5-syntaxer';
import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed';
import Image from '@ckeditor/ckeditor5-image/src/image';
import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle';
import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar';
import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload';
import UploadAdapterPlugin from 'ckeditor5-upload-adapter';
export default class ClassicEditor extends ClassicEditorBase { }
ClassicEditor.builtinPlugins = [
Essentials, //necessary, eg. Enter will not work within this plugin
Bold,Italic,
Heading,
FontFamily,
FontSize,
FontBackgroundColor,
FontColor,
Underline,
StrikeThrough,
Subscript,
Superscript,
Alignment,
BlockQuote,
Highlight,
Autoformat, // does not have any toolbar buttons, instead it allows the ckeditor to interprete different keyboard shortcuts
List,Link,
PasteFromOffice, // does not have any toolbar buttons, instead it makes the ckeditor able to handle paste from office
Syntaxer,MediaEmbed,
Image, // necessary
ImageCaption, // adds a caption option to images
ImageToolbar, // adds a floating style toolbar for image positioning
ImageStyle, // adds the styles for the floating image toolbar for image positioning
ImageUpload, // holds the image upload toolbar button
UploadAdapterPlugin,Table,
TableToolbar // adds a floating toolbar to the table for cell merging, row & column creation & deletion
];
With new plugins come new configurations, configuration that with an intermediary CKEditor 5 object (like the one build in this tutorial) is done in the .html page that consumes the editor.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="dist/ckeditor.js"></script>
<script src="dist/translations/da.js"></script>
</head>
<body>
<div id="editor">
<p>Editor content goes here.</p>
</div>
<script>
CustomEditor.create(document.querySelector('#editor'), {
toolbar: [
'heading', // Heading plugin
'|', // separator
'fontFamily', // FontFamily plugin
'fontSize', // FontSize plugin
'fontBackgroundColor', // FontBackgroundColor plugin
'fontColor', // FontColor plugin
'highlight', // Highlight plugin
'|','bold', // Bold plugin
'italic', // Italic plugin
'underline', // Underline plugin
'strikeThrough', // StrikeThrough plugin
'subScript', // Subscript plugin
'superScript', // Superscript plugin
'|','numberedlist', // List plugin
'bulletedlist', // List plugin
'|','blockQuote', // BlockQuote plugin
'link', // Link plugin
'insertTable', // Table plugin
'imageupload', // ImageUpload plugin
'syntaxer', // Syntaxer plugin
'|','undo', // Essentials plugin
'redo' // Essentials plugin
],language: 'da',
table: {
contentToolbar: [ // TableToolbar plugin
'tableColumn','tableRow',
'mergeTableCells'
]
},
syntaxer: {
languages: ['c#', 'php', 'sql', 'javascript', 'json', 'xml', 'html', 'css'],
element: 'pre'
},
image: {
toolbar: ['imageStyle:alignLeft', 'imageStyle:full', 'imageStyle:alignRight'], // ImageToolbar plugin
styles: [ // ImageStyle plugin
'full','alignLeft',
'alignRight'
]
},
uploadAdapter: {
uploadUrl: '/home/ImageUpload' // url to http endpoint on your server for processing the uploaded image
/** It's your responsibility to save the image and return a relevant json object, eg. in case of success :
* {
* uploaded: true,
* url: urlToImageSavedOnYourServer
* }
*/
}
})
.then(editor => {
window.editor = editor;
})
.catch(err => {
console.error(err.stack);
});
</script>
</body>
</html>
That was all the code changes needed to create a max-loaded CKEditor 5.
Error : "npm ERR! cb.apply is not a function".
When : Installing npm packages using shell> npm install
Reason : I don't understand the reason, but it seems to be some kind of compatibility problem between maybe and older NPM in cache and a newer NPM installed.
Solution :
Error : "No matching version found for ...".
When : Installing npm packages using shell> npm install
Reason : The version specified a specific package does not exists (in the screenshot above, I had specified version 22.0.0 for postcss-loader, however that version does not exists).
Solution :
Error : "npm ERR! cb() never called".
When : Installing npm packages using shell> npm install
Rason : I have no idea.
Solution : I solved it the same way as for the "npm ERR! cb.apply is not a function" :
Error : "'webpack' is not recognized as an internal or external command, operable program or batch file."
When : Trying to execute webpack shell> webpack
Reason 1 : If webpack is NOT installed globally, you must execute webpack from it's install folder, that is: not from your package.json folder.
Solution 1 : If your current location is your package.json folder and you have installed webpack globally, then webpack will exists in node_modules and you can execute it like this : shell> node_modules\.bin\webpack
Reason 2 : If webpack IS installed globally, the problem likely is that the PATH environment variable have not been setup to point to the global webpack executable.
Solution 2 : Include the global webpack executable location (%USERPROFILE%\AppData\Roaming\NPM\) in the PATH environment variable :
Error : "path is not defined". (this error I only include here because the official CKEditor 5 build from source webpack.config.js will result in this error)
When : Executing webpack.
Reason : The typical webpack.config.js file will use a variable called path to calculate paths, often like this : path: path.resolve(__dirname, 'dist') - here the script is referencing a path variable (in bold), so if the path variable have not been declared & initialized, we will get the "path is not defined" error.
Solution : Declare a path variable at the top your webpack.config.js file and initialize it with the NPM 'path' module.