Using CKEditor 5 with React via create-react-app
- May 20, 2018
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:
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:
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:
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.