CKEditor 5, Webpacker, VueJS, and you.
Tags: rails, vue, webpacker, ckeditor
Published: 2020-06-11 17:30:11

The documentation for using CKEditor 5 with VueJS is pretty good, at least as far as open source technical documentation goes, but it makes the assumption that you're using [vue-cli](https://cli.vuejs.org/) so it falls a bit short if you've set up Webpacker yourself or you're using Rails/Webpacker like I am.

_Note: I am [building CKEditor from source](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/frameworks/vuejs.html#using-ckeditor-from-source) rather than using the prebuilt package._

## The Setup

First we need to add the proper packages to build CKEditor in our project.

```bash
yarn add @ckeditor/ckeditor5-vue \
         @ckeditor/ckeditor5-dev-webpack-plugin \ 
         @ckeditor/ckeditor5-dev-utils \
         raw-loader@0.5.1
```

Next we can prepend a new instance of `CKEditorWebpackPlugin` to
`environment.plugins` in `config/webpack/environment.js`.

```javascript
// config/webpack/environment.js

const { environment } = require('@rails/webpacker');
// ... existing `require`s
const CKEditorWebpackPlugin = require('@ckeditor/ckeditor5-dev-webpack-plugin');

// ... existing code

environment.plugins.prepend('CKEditorWebpackPlugin', new CKEditorWebpackPlugin({
  addMainLanguageTranslationsToAllAssets: true,
  buildAllTranslationsToSeparateFiles: true,
  language: 'en',
}));

module.exports = environment;
```

**Don't forget to restart your server when your webpack config changes!**

If you only use the `language` property like the [documentation shows](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/frameworks/vuejs.html#configuring-vueconfigjs) you'll be met with two errors.

```
[CKEditorWebpackPlugin] Error: No JS asset has been found during the compilation. You should add translation assets directly to the application from the `translations` directory. If that was intentional use the `buildAllTranslationsToSeparateFiles` option to get rid of the error.
[CKEditorWebpackPlugin] Error: Too many JS assets has been found during the compilation. You should add translation assets directly to the application from the `translations` directory or use the `addMainLanguageTranslationsToAllAssets` option to add translations for the main language to all assets or use the `buildAllTranslationsToSeparateFiles` if you want to add translation files on your own.
```

Now that the Webpack plugin is set up we need to configure how the CKEditor SVG icons and css are parsed so we'll set up a loader for each. I like to keep my `config/webpack/environment.js` as clean as possible so we'll be creating small modules to include.

First we'll deal with the SVG icons. We need to use [raw-loader](https://webpack.js.org/loaders/raw-loader/) to load the SVG icons as text.

Create the following `config/webpack/loaders/ckeditor-svg.js`

```javascript
// config/webpack/loaders/ckeditor-svg.js

module.exports = {
  test: /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
  use: 'raw-loader',
};
```

Let's go ahead and create the CKEditor css loader at
`config/webpack/loaders/ckeditor-css.js`.

```javascript
// config/webpack/loaders/ckeditor-css.js

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

module.exports = {
  test: /ckeditor5-[^/\\]+[/\\].+\.css$/,
  use: [{
    loader: 'style-loader',
    options: {
      injectType: 'singletonStyleTag',
      attributes: {
        'data-cke': true,
      },
    },
  }, {
    loader: 'postcss-loader',
    options: styles.getPostCssConfig({
      minify: true,
      themeImporter: {
        themePath: require.resolve('@ckeditor/ckeditor5-theme-lark'),
      },
    }),
  }],
};
```

Now back in `config/webpack/environment.js` we can `require` these files and prepend them to `environment.loaders`.

```javascript
// config/webpack/environment.js

const { environment } = require('@rails/webpacker');
// ... existing `require`s
const ckeditorSvgLoader = require('./loaders/ckeditor-svg');
const ckeditorCssLoader = require('./loaders/ckeditor-css');

// ... existing code

environment.loaders.prepend('ckeditor-svg', ckeditorSvgLoader);
environment.loaders.prepend('ckeditor-css', ckeditorCssLoader);

module.exports = environment;
```

If we try to compile our project now it will fail with the following error (or something similar) for every CKEditor css file:

```
ERROR in ./node_modules/@ckeditor/ckeditor5-ui/theme/components/<component>/<stylesheet>.css (./node_modules/postcss-loader/src??ref--1-1!./node_modules/mini-css-extract-plugin/dist/loader.js!./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./node_modules/@ckeditor/ckeditor5-ui/theme/components/<component>/<stylesheet>.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
SyntaxError

(1:1) Unknown word

 > 1 | // extracted by mini-css-extract-plugin
```

To fix this we'll have to exclude CKEditor's assets for our other loaders. Add the following lines to `config/webpack/environment.js`.

```javascript
// config/webpack/environment.js

// ... the rest of the code

// The default value for css.exclude is `/\.module\.[a-z]+$/`
environment.loaders.get('css').exclude = [
  /\.module\.[a-z]+$/,
  /ckeditor5-[^/]+\/theme\/[\w-/]+\.css$/,
];
environment.loaders.get('file').exclude = path.resolve(__dirname, '..', '..', 'node_modules', '@ckeditor');
environment.loaders.get('moduleCss').exclude = /ckeditor5-[^/]+\/theme\/[\w-/]+\.css$/;

module.exports = environment;
```

## Using CKEditor

Add the packages you need.

```bash
yarn add @ckeditor/ckeditor5-editor-classic \
         @ckeditor/ckeditor5-essentials \
         @ckeditor/ckeditor5-basic-styles \
         @ckeditor/ckeditor5-link \
         @ckeditor/ckeditor5-paragraph \
         @ckeditor/ckeditor5-theme-lark
```

For the sake of this article I'll activate CKEditor in my pack so it can be used application-wide.

```javascript
// app/javascript/packs/application.js

import CKEditor from '@ckeditor/ckeditor5-vue';
import Vue from 'vue/dist/vue.esm';

Vue.use(CKEditor);

// ... the rest of the pack
```

From here we can use the `<ckeditor>` component anywhere in our Vue app.

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

<script>
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';

import BoldPlugin from '@ckeditor/ckeditor5-basic-styles/src/bold';
import EssentialsPlugin from '@ckeditor/ckeditor5-essentials/src/essentials';
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 {
  data: () => ({
    editor: ClassicEditor,
    editorData: '<p>Content of the editor.</p>',
    editorConfig: {
      plugins: [
        BoldPlugin,
        EssentialsPlugin,
        ItalicPlugin,
        LinkPlugin,
        ParagraphPlugin,
      ],

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

And that's it. Now go create!