TinyMCE 4 Migration to CKEditor 5

Been doing a lot of research on a possible alternative that is better than TinyMCE 4. CKEditor 5 seemed more like a better UI/UX thing and no real better improvements under the hood. So I checked out Quill, Trix, Slate and many others. CKEditor 5 stood out.

CKEditor has a strong experienced team, great documentation, loads of regular updates and is a radical overhaul of their editor taking a modern approach.

Issues we had in the Past

We have had many issues with the TinyMCE editor and with text editing in general. Here are some of the issues:

  • TinyMCE not loading or slowly loading
  • Backspace not working
  • hard enter and soft enter issues
  • selecting text for styling with specific font preset
  • issues styling toolbar – adding numbers next to font or other styling
  • missing text around image / text wrap
  • column flow into next column / css columns did not work out due to lack of cross browser support and some ancient bugs, especially safari was a pain
  • column copy from second to third column using custom setup still not perfect
  • column copy icon (for same custom copy column function) showing when text does not overflow

Mind you, the columns issue seems to be mainly a browser issue. CSS Columns does exist but is hardly supported and most if not all html editors do not support columns unless you are talking tables. But have found a way to work with them, though not perfect by any means yet.

Inspiration

Blog post on the launch by CKEditor https://ckeditor.com/blog/CKEditor-5-A-new-era-for-rich-text-editing was very inspiring and did explain a lot on the why for the CKEditor and pushed me on to learn more about WYSIWYG Editors, contentEditable, Virtual Doms, select API and much more. But also another blog post by them on contentEditable.

Initial Look

CKEditor 5 does seem more modern and with a better API and works well with Vue. But we did not want our current font presets, font loading, text styling to break down nor do we want to spent too much time on this possible migration. But as text and fonts are a big part of the app and have been a big headache we did not mind discussing a change for the better

Data Model

CKEditor has a new data model and its own virtual dom. This is something you do have to read on to understand some of the benefits of it. Let’s start with their brief explanation on the data model:

With our new editor, we decided to revamp the approach to the data model. Instead of relying on the DOM to store it, we designed a custom model in JavaScript. This was a great move as it removed the limitations present on the DOM implementation of browsers and standardized the model to a much simpler system that is portable to any environment. It works in the very same way inside all browsers. What’s more,…

…. At low level, that API is translated into a set of standard operations, like “insert”, “move” or “delete”. This means that, at low level, all features look the same for the editor core.

See again this blog post: https://ckeditor.com/blog/CKEditor-5-A-new-era-for-rich-text-editing/CKEditor 5: A new era for rich text editing

So they are saying they have a better data model not relying on DOM for storage.

contentEditable only input and output

There is no more use of contentEditable as was done before so more stable /better dealing with different browser approaches. Many issues allows popped up as browsers would display the most basic of things like bold or italic text using different html tags causing display problems

Their reason to drop contentEditable usage as they did before in detail: https://medium.com/content-uneditable/contenteditable-the-good-the-bad-and-the-ugly-261a38555e9c

And so they went for their own model and virtual dom for better control over input and output

This model (where contentEditable handles features related to typing and selection, while the rest is handled in JavaScript) is also the direction taken by the W3C Editing Task Force.

Virtual DOM

Thanks to the Virtual DOM there is more control over html output. That and earlier mentioned data model. There is also a better undo system and history tracking thanks to this virtual dom:

The real benefit from Virtual DOM is it allows calculation the difference between each change and make minimal changes into the HTML document. https://www.quora.com/Why-is-Reacts-virtual-DOM-so-much-faster-than-the-real-DOM

So actually CKEditor not just uses its own data model but a virtual dom too and just like VueJS 2.x this Virtual DOM should make things faster and less dependent on underlying selector and contentEditable APIS. So all editing done in Models / Virtual DOM and only input and deletion / selection with browser apis

https://ckeditor.com/docs/ckeditor5/latest/framework/guides/architecture/editing-engine.htmlEditing engine – CKEditor 5 Documentation

NB A virtual dom is not faster than the DOM itself. It can even be slower:

https://medium.com/@hayavuk/why-virtual-dom-is-slower

It is mainly used for simplicity and avoiding different input and output.

Operational Transformations

They also mentioned operational transformations and that kind of went over my head, but their summary below helps:

The ability of applying two different set of operations in whichever order on the model and have the exact same results. This is possible by a technology called Operational Transformations.

Still, more is needed to really help me understand this.

Via Tweet by CKEditor Ecosystem I read more about it at https://ckeditor.com/blog/Lessons-learned-from-creating-a-rich-text-editor-with-real-time-collaboration/ . Seems they use operational transformations for conflict resolution when the editor is used by multiple users at the same time. Operational Transformations or OT are defined this way:

OT is based on a set of operations (objects describing changes) and algorithms that transform these operations accordingly, so that all users end up with the same editor content regardless of the order in which these operations were received

Basic Input

I do think the editor and its approach to let it do all the editing and the apis only selection, input and output will help with the delete / backspace issues we had and all follow up custom work, but we also need to see if it helps space wise:

It means that today CKEditor does not let the browser do anything to the content except handling typing, some deleting and that’s basically it. At the same time, it still uses the native selection system, keyboard navigation and other APIs such as those related to clipboard or focus management.
browser insert text only, but with CKEditor’s control .

and if most text manipulation is done in the virtual dom and not using the contentEditable, Select APIs this does sound like general speed improvements and overall improvements. Certainly less prone to bugs that is for sure.

Multi Root Editor

Multi root editor rules would rule editable areas on the grid and this may also be more efficient than one instance per editable area.. unless you edit with two or more people. This will require a custom editor. But we would need a custom inline editor as far as I can see it anyways.

Standard inline editor with multiple separate ones as at https://ckeditor.com/docs/ckeditor5/latest/examples/builds/inline-editor.html is already really fast as far as I can see though. But if your application does a lot of DOM manipulation this may be the way to go.

Styling

CKEditor does offer better styling options and in general the UI is much more modern. See:  https://ckeditor.com/docs/ckeditor5/latest/examples/framework/custom-ui.html with Bootstrap also possible as CSS framework to style the text toolbar.

CKEditor possible improvements

So what are the possible improvements on offer here? Here a short summary:

  • data model and virtual dom to deal with contentEditable issues – it let’s apis do what they do best but all editing in vDOM. No more issues with different html output
  • everything is a plugin so modular
  • new UI/UX
  • several editors https://github.com/ckeditor/ckeditor5#editors and all smaller in size than many other WYSIWYG. Also easy to build your own package
  • packages for all well known JS Frameworks such as VueJS
  • styling with Bootstrap possible

Useful urls:

Migration

Now, if you are convinced that their new approach to content editing is the way to go how would we migrate to CKEditor 5 from TinyMCE? Well, in our case (Laravel VueJS App) we would use a custom inline package and the Vue branch of CKEditor 5.

For a quick start we read this document.

We would need to :

  • remove the TinyMCE as an NPM package,
  • remove it from our text module and
  • remove or replace mixin.

Inline Editor w/ Vue

Then we would need to implement a new text toolbar (inline editor on possibly multi root),

https://github.com/ckeditor/ckeditor5-editor-inline

npm install --save @ckeditor/ckeditor5-editor-inline

import the editor using ES6 Modules

import Vue from 'vue'; 
import CKEditor from '@ckeditor/ckeditor5-vue'; 
Vue.use( CKEditor );

Actual component implementation example. Use the <ckeditor> component in your template:

<template>
    <div id="app">
        <ckeditor :editor="editor" v-model="editorData" :config="editorConfig"></ckeditor>
    </div>
</template>

<script>
    import InlineEditor from '@ckeditor/ckeditor5-editor-inline';

    export default {
        name: 'app',
        data() {
            return {
                editor: InlineEditor,
                editorData: '<p>Content of the editor.</p>',
                editorConfig: {
                    // The configuration of the editor.
                }
            };
        }
    }
</script>

Custom Inline Build / Integrate Existing Builds

Sample above is a ready made package. We could also integrate an existing build which is quite similar to the above mentioned standard build usage. You can then of course still configure it.

So again as before you install the inline package:

npm install --save @ckeditor/ckeditor5-editor-inline

then you import it

// Using ES6 imports:
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';

and then use it

InlineEditor
    .create( document.querySelector( '#editor' ) )
    .then( editor => {
        console.log( editor );
    } )
    .catch( error => {
        console

Build from source

We can also build one from source and take advantage of the framework.

This scenario allows you to fully control the building process of CKEditor. This means that you will not actually use the builds anymore, but instead build CKEditor from source directly into your project.

This integration method gives you full control over which features will be included and how webpack will be configured [url].

Inline Editor + Vue from source

The Vue configuration / setup to build a setup from source is different from the standard custom build in advanced docs at CKEditor https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/frameworks/vuejs.html#using-ckeditor-from-source

You need to configure config.vue.js and before that run

npm install --save \
    @ckeditor/ckeditor5-vue \
    @ckeditor/ckeditor5-dev-webpack-plugin \
    @ckeditor/ckeditor5-dev-utils \
    postcss-loader@3 \
    raw-loader@0.5.1

example vue.config.js is

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

module.exports = {
    // The source of CKEditor is encapsulated in ES6 modules. By default, the code
    // from the node_modules directory is not transpiled, so you must explicitly tell
    // the CLI tools to transpile JavaScript files in all ckeditor5-* modules.
    transpileDependencies: [
        /ckeditor5-[^/\\]+[/\\]src[/\\].+\.js$/,
    ],

    configureWebpack: {
        plugins: [
            // CKEditor needs its own plugin to be built using webpack.
            new CKEditorWebpackPlugin( {
                // See https://ckeditor.com/docs/ckeditor5/latest/features/ui-language.html
                language: 'en'
            } )
        ]
    },

    // Vue CLI would normally use its own loader to load .svg and .css files, however:
    //	1. The icons used by CKEditor must be loaded using raw-loader,
    //	2. The CSS used by CKEditor must be transpiled using PostCSS to load properly.
    chainWebpack: config => {
        // (1.) To handle editor icons, get the default rule for *.svg files first:
        const svgRule = config.module.rule( 'svg' );

        // Then you can either:
        //
        // * clear all loaders for existing 'svg' rule:
        //
        //		svgRule.uses.clear();
        //
        // * or exclude ckeditor directory from node_modules:
        svgRule.exclude.add( path.join( __dirname, 'node_modules', '@ckeditor' ) );

        // Add an entry for *.svg files belonging to CKEditor. You can either:
        //
        // * modify the existing 'svg' rule:
        //
        //		svgRule.use( 'raw-loader' ).loader( 'raw-loader' );
        //
        // * or add a new one:
        config.module
            .rule( 'cke-svg' )
            .test( /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/ )
            .use( 'raw-loader' )
            .loader( 'raw-loader' );

        // (2.) Transpile the .css files imported by the editor using PostCSS.
        // Make sure only the CSS belonging to ckeditor5-* packages is processed this way.
        config.module
            .rule( 'cke-css' )
            .test( /ckeditor5-[^/\\]+[/\\].+\.css$/ )
            .use( 'postcss-loader' )
            .loader( 'postcss-loader' )
            .tap( () => {
                return styles.getPostCssConfig( {
                    themeImporter: {
                        themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' ),
                    },
                    minify: true
                } );
            } );
    }
};

Once the configuration file is done you can run the following commands. Mind you, do add the plugins you need and in our case this is when we could add one or two to deal with the media manager and font presets.

npm install --save \
    @ckeditor/ckeditor5-inline-editor \
    @ckeditor/ckeditor5-essentials \
    @ckeditor/ckeditor5-basic-styles \
    @ckeditor/ckeditor5-link \
    @ckeditor/ckeditor5-paragraph \
    @ckeditor/ckeditor5-theme-lark

Also what changes next in the component code is that you also need to import all needed plugins. Example

<template>
    <div id="app">
        <ckeditor :editor="editor" v-model="editorData" :config="editorConfig"></ckeditor>
    </div>
</template>

<script>
    // ⚠️ NOTE: We don't use @ckeditor/ckeditor5-build-classic any more!
    // Since we're building CKEditor from source, we use the source version of ClassicEditor.
    import ClassicEditor from '@ckeditor/ckeditor5-editor-inline/src/editorinline';

    import EssentialsPlugin from '@ckeditor/ckeditor5-essentials/src/essentials';
    import BoldPlugin from '@ckeditor/ckeditor5-basic-styles/src/bold';
    import ItalicPlugin from '@ckeditor/ckeditor5-basic-styles/src/italic';
    import LinkPlugin from '@ckeditor/ckeditor5-link/src/link';
    import ParagraphPlugin from '@ckeditor/ckeditor5-paragraph/src/paragraph';

    export default {
        name: 'app',
        data() {
            return {
                editor: ClassicEditor,
                editorData: '<p>Content of the editor.</p>',
                editorConfig: {
                    plugins: [
                        EssentialsPlugin,
                        BoldPlugin,
                        ItalicPlugin,
                        LinkPlugin,
                        ParagraphPlugin
                    ],

                    toolbar: {
                        items: [
                            'bold',
                            'italic',
                            'link',
                            'undo',
                            'redo'
                        ]
                    }
                }
            };
        }
    };
</script>

CKEditor Plugins

As CKEditor is modular we would need custom plugins for:

  • the custom dropdown for fonts as well as the one for
  • the media manager connection

The simple plugin tutorial explains pretty well how we can create a simple plugin and this we can use to add our own custom buttons and or dropdown: https://ckeditor.com/docs/ckeditor5/latest/framework/guides/creating-simple-plugin.html

you need to install these dependencies

npm install --save @ckeditor/ckeditor5-image \
    @ckeditor/ckeditor5-core \
    @ckeditor/ckeditor5-ui

NB and we may need  @ckeditor/ckeditor5-engine as well and perhaps not the image plugin depending on what we are building

More to be added…

Text Styling

We would also need to see how text styling is loaded and overwritten on using the new editor. And we would need to make sure most styling if not all is preserved. Still in beta so we can suffer some changes, but we prefer to minimize all of course.

Selection and Removal

Then we would need to test and make sure font presets styles are applied properly, backspace, select and paste still works well. This part has not been worked out yet as some custom work for this may have to be removed and then we would have to see if CKEditor does this out of the box and if not what tweaks will be needed.

Jasper Frumau

Jasper has been working with web frameworks and applications such as Laravel, Magento and his favorite CMS WordPress including Roots Trellis and Sage for more than a decade. He helps customers with web design and online marketing. Services provided are web design, ecommerce, SEO, content marketing. When Jasper is not coding, marketing a website, reading about the web or dreaming the internet of things he plays with his son, travels or run a few blocks.