When I first started using CKEditor 5 with create-react-app, I installed CKEditor as an npm module, and imported the ClassicEditor build as recommended by the quickstart.

Development mode (via npm start) worked well, and I was happily integrating CKEditor with React, but as soon as I ran npm run build (which generates the create-react-app production build), I ended up with the following error:

> [email protected] build c:\Dev\scratch\ckeditor-integration
> node scripts/build.js

Creating an optimized production build...
Failed to compile.

Failed to minify the code from this file:

        ./node_modules/@ckeditor/ckeditor5-build-classic/build/ckeditor.js:5:7350

Read more here: http://bit.ly/2tRViJ9

npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] build: `node scripts/build.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

Ruh-roh. What to do next? How can I solve this? Is this solvable? Is there another way?

There is another way, and there are some tradeoffs which I’ll describe later. If you follow the steps in this blog post, you should end up with something that looks like:

The thing you'll make

Incompatibilities between create-react-app and CKEditor 5

The problem seems to be with create-react-app, and there’s some discussion around why this is happening, but at the time of writing this blog post, there are no fixes for the underlying issue.

There’s currently a project for official CKEditor 5 bindings for React project being worked on, which is currently running into other problems generating production builds, specificially the following error when running npm run build :

"export 'default' (imported as 'ClassicEditorBuild') was not found in '@ckeditor/ckeditor5-build-classic/build/ckeditor'

I really don’t want to spend hours fiddling with webpack, babel, etc, and there are other, more interested people than me who are working on solving these problems. I mainly just want to get CKEditor working with the create-react-app template, so that I can create a production build of a personal project.

I found a way forward using webpack externals. Read on for details on how it’s done.

Initialising the project via create-react-app

Make sure you’ve got at least npm version 5.2 (which includes npx) installed, then run the following commands to download create-react-app, and initialise a new project:

npm install -g create-react-app
npx create-react-app ckeditor-integration
cd ckeditor-integration

To make sure your local environment is set up correctly, and make sure that there are no issues with the default create-react-app, before making any changes, check the default app runs via:

npm start

Check production builds run successfully via:

npm run build

The output should look something similar to:

Compiled successfully!

You can now view ckeditor-integration in the browser.

  Local:            http://localhost:3000/
  On Your Network:  http://192.168.230.1:3000/

Note that the development build is not optimized.
To create a production build, use npm run build.

You should see the default create-react-app site which looks like:

The thing you'll make

Loading CKEditor 5 via webpack externals

Next we modify the webpack configuration to load CKEditor as an external dependency. This means that it won’t be bundled as part of the webpack build process, but will be still be able to be imported as a module.

Run the following command:

npm run eject

npm run eject will, among other things, copy the webpack configuration files to a config folder in your project.

After ejecting the webpack configuration files, we’ll need to modify them to set up the externals. The following two files you’ll need to modify are located here:

<project_root>/config/webpack.config.dev.js
<project_root>/config/webpack.config.prod.js

Here’s the section you’ll need to add to the files above. The externals section can be added anywhere within top level of the module.exports section.

externals: {
    ClassicEditor: 'ClassicEditor'
},

The same concept can be applied if you’d prefer to use the Balloon Editor or the Inline Editor.

Updating public/index.html

Update public/index.html to look like:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <title>React App</title>
    <script src="https://cdn.ckeditor.com/ckeditor5/10.0.1/classic/ckeditor.js"></script>
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
  </body>
</html>

Note that ckeditor is loaded via a regular script tag. This is our webpack external depencency.

Updating src/App.js

Replace the code in App.js with the following:

import React, { Component } from 'react';
import './App.css';
import ClassicEditor from 'ClassicEditor';

class CKEditor extends Component {
	constructor(props) {
	  super(props); 
	  this.state = { }
	}
  
	bindChangeEvent = (editor, document) => {
	  document.on('change', () => {
		if (document.differ.getChanges().length > 0 ) {
			this.props.onChange(editor.getData());
		}
	  });
	}
  
	componentDidMount() {
	  ClassicEditor
		.create( document.querySelector( '#editor' ))
		.then(editor => {
		  editor.setData(this.props.data);
		  this.bindChangeEvent(editor, editor.model.document);
		})
		.catch( error => {
			console.error(error);
		});
	}
  
	render() {
	  return (
		<div id={'editor'}></div>
	  )
	}
}

class App extends Component {
	render() {
		return (
			<div className="App">
				<h2>CKEditor 5 using build-classic</h2>
				<CKEditor
					data="<p>Hello from CKEditor 5</p>"
					onChange={ data => console.log( data ) }
				/>
			</div>
		);
	}
}

export default App;	

One of the key differences here is that in the CKEditor quickstart, you’d normally import CKEditor via:

import ClassicEditor from '@ckeditor/ckeditor5-build-classic';

but after adding the external dependency in the webpack config files, you can now import CKEditor via:

import ClassicEditor from 'ClassicEditor';

I’ve also included the onChange property, which is called for every change made in the CKEditor text field, and is the event you can use if you want to store or use the text data.

For the sake of this demo, the the state of the document is being written directly to the console via:

onChange={ data => console.log( data ) }

i.e. if I were to type “Some text” into the CKEditor text field, the following would be the value of ‘data’, and written to the console:

<p>Some text</p>

This is just to show how to fetch the CKEditor content, and pass it to somewhere else in the app. For a real app, the state change would be sent somewhere more useful.

And that’s it

Run a production build via:

npm run build

The output should look something similar to:

> [email protected] build c:\Dev\scratch\ckeditor-integration
> node scripts/build.js                                               
                                                                      
Creating an optimized production build...                             
Compiled successfully.                                                
                                                                      
File sizes after gzip:                                                
                                                                      
  36.81 KB  build\static\js\main.42d36c31.js                          
  299 B     build\static\css\main.c17080f1.css                        
                                                                      
The project was built assuming it is hosted at the server root.       
You can control this with the homepage field in your package.json.    
For example, add this to build it for GitHub Pages:                   
                                                                      
  "homepage" : "http://myname.github.io/myapp",                       
                                                                      
The build folder is ready to be deployed.                             
You may serve it with a static server:                                
                                                                      
  serve -s build                                                      
                                                                      
Find out more about deployment here:                                  
                                                                      
  http://bit.ly/2vY88Kr                                               

Then test your development build via:

npm start

The output should look something like:

Compiled successfully!

You can now view ckeditor-integration in the browser.

  Local:            http://localhost:3000/
  On Your Network:  http://192.168.230.1:3000/

Note that the development build is not optimized.
To create a production build, use npm run build.

Now, if you view ckeditor-integration in the browser, it should look something like:

The thing you'll make

And you’re done with a basic CKEditor integration with React via create-react-app.

What are the tradeoffs when using webpack externals for loading CKEditor?

The tradeoff is that ckeditor.js is not being bundled via webpack. Does this matter? It depends on your curcumstances, and is best explained by another post such as this one.

If you’re working on a personal project, and just wanting to get CKEditor integrated with React, like I am, then it doesn’t really matter that you’re using webpack external dependencies - you’re probably better off not worrying, and just focusing on adding value in your project, rather than spending hours fiddling around with webpack, babel, etc.

Thanks for reading!

I hope you found this post useful. Do you have any questions? Are there any follow-up blog posts you’d like me to write relating to this? Feel free to leave any feedback in the comments below.