Skip to content

Draft: Proof of concept of a Rich Content Editor using CKEditor5

Enrique Alcántara requested to merge ckeditor-proof-of-concept into master

This is a proof of concept (POC) that explores the viability of implementing a WYSIWYG editor using CKEditor5. The WYSIWYG editor should generate markdown as output data. The POC will explore the following topics based on the evaluation criteria question in gitlab#231725 (comment 422124842):

  • Payload size and asynchronous loading.
  • Multiple instances of the editor in the same context.
  • CKEditor User Experience in mobile devices.
  • Implementing a custom parser for non-standard markdown syntax and embedded templating syntax like ERB.
  • Create an editor that displays content in WYSIWYG and raw markdown modes and retain cursor selection between modes.

Check the GitLab UI preview app to explore the stories implemented for this Proof of concept.

Payload size

To reproduce the results of this report, run the following command in GitLab UI after checking out this MR’s branch:

WEBPACK_REPORT=1 yarn storybook-static

This command will create a production build of GitLab UI’s storybook and will create a report that contains the bundle size of the rich content editor implemented with CKEditor. These are descriptions of the terms used in the report:

  • Stat size: The bundle size without webpack optimizations (like minification)
  • Parsed size: The bundle size after optimizations.
  • Gzipped size: The bundle size after gzip compression.

The bundle size accounts for all the assets provided by CKEditor: SVGs, CSS, and JavaScript. The following screenshots provide the bundle size information for CKEditor. We also included Toast UI to provide a comparison baseline:

CKEditor Toast UI
CKEditor webpack stats Toast UI webpack stats
  • CKEditor stats:
    • Stat size: 3.06mb
    • Parsed size: 939.6kb
    • Gzipped size: 191.37kb
  • Toast UI stats:
    • Stat size: 1.63mb
    • Parse size: 754.kb
    • Gzipped size: 227.66kb

CKEditor modular architecture and asynchronous loading

CKEditor features are organized and distributed as independent NPM packages. As a result, we can load assets asynchronously if necessary. The bundle size report on this Merge Request includes the foundational features already provided by the Static Site Editor or in development:

  • Inserting and modifying links
  • Inserting and modifying images
  • Inserting and modifying tables
  • Basic text formattting

We can build different distributions of the editor that can include or omit these packages. We can also load these modules asynchronously using webpack’s capabilities for such purpose. These could allow us to build a lighter version of the editor if needed.

Thoughts

In my opinion, this is a reasonable bundle size for an editor that is used as the core of a standalone application like the Static Site Editor. Another use case that I find ideal is loading the rich content editor asynchronously in the WebIDE to edit different types of Rich Content. Where this bundle size can become a performance problem is for the use case of submitting comments in the merge request and issue pages. In these pages, there‘s already a significant amount of content and assets loaded and we should be very measured in the new assets that we decide to include.

Multiple instances

CKEditor5 provides two different approaches to having multiple instances of the RichContentEditor in the same context.

Multi-root editor

We can organize multiple blocks as separate editors using the multi-root editor in CKEditor.

The multi-root editor allows having separate editing blocks that belong to the same editor instance. These editing blocks share the same undo stack and toolbar which could be an advantage in creating the perception of editing a web page where each section is not part of a continuous block of content.

You can test the multi-root editor in the following link: https://ckeditor.com/docs/ckeditor5/latest/examples/framework/multi-root-editor.html

Independent editor instances

The other option is just having multiple editor instances. This is demonstrated in this GitLab UI story.

Mobile experience

GitLab UI storybook demo

CKEditor5 provides the following mobile optimizations:

Responsive toolbars

CKEditor classic toolbar and block editor toolbar are responsive. When they are displayed in small screens, the toolbar items that do not fit in the screen’s width are hiden in a dropdown. Features like the table editor work well in mobile devices with touch screens considering how toolbars adapt to that form factor.

Responsive classic toolbar Responsive block toolbar Tables editing
Screen_Shot_2020-10-13_at_9.15.36_AM Screen_Shot_2020-10-08_at_1.42.55_PM Table editing in a mobile device

Supporting ERB templating syntax in the editor

GitLab UI storybook demo

CKEditor5 allows supporting new content by extending the editing capabilities using plugins. We implemented a demo that demonstrates how we rely on these capabilities to display ERB code snippets embedded in a Markdown document which is a common scenario in websites built with static site generators.

To support new syntax, we built a CKEditor plugin that provides the following functionalities:

  • Extends CKEditor’s document schema to indicate how embedded template syntax should be stored in the model.
  • Instructs CKEditor how to convert the embedded template syntax model to a view and vice-versa.

For a better understanding of what a document’s schema and about the model to view conversion, I recommend reading the following guides:

CKEditor5 provides a Markdown plugin package that allows passing Markdown content directly to the editor. We didn’t use this package because it does not allow us extend the Markdown parser which is a fundamental requirement for the Static Site Editor. Instead, we decided to handle the HTML -> Markdown conversion (and vice-versa) as a separate process to have full control of it. This process was implemented using remark and unified. The following diagram demonstrates how we use unified to build a bridge between a Rich Content Editor and a Markdown parser platform:

image

We extended remark and unified in the following ways:

  • Extended the Remark’s parser to support ERB syntax and include that information as a special node in the Abstract Syntax Tree.
  • Extended remark’s serializer to indicate how to convert the ERB especial node back to raw markdown.
  • Customized the Markdown -> HTML conversion to instruct unified on how to convert ERB Node into HTML and vice-versa.

Syncing the cursor position between WYSIWYG and Markdown modes

GitLab UI Storybook demo

The demo above demonstrates how to keep the cursor position between WYSIWYG mode and the raw markdown views. It is worth highlighting that selection state in a WYSIWYG mode is modeled in a way that is incompatible with a raw view of a Markdown document. To implement this demo, we created a conversion layer that may not work in more complex scenarios.

Edited by Michael Le

Merge request reports