最近需要將項目中的編輯器從 CKeditor 4 升級到 CKeditor 5
原以為只是換個內核,然后稍微調整一下自定義插件的代碼,沒想到進了一個大坑
在經過一個月的摸爬滾打之后,終於完成升級內核的工作,同時也算摸清了 CK5 的基本玩法
為方便后面的同學來接手,打算新起一個項目,記錄一下 CK5 的定制化開發過程
PS. 結合官方文檔食用更香
一、我想做一個這樣的編輯器
CK5 提供了五種類型的編輯器,可以根據自己的需求選擇
如果沒有定制化開發的需求,可以直接引用,或者通過在線生成器刪減不必要的插件
但如果不滿足既有功能,想結合自己的需求做一些調整,哪怕只是改個圖標,都需要自己打包
FAQ: How to customize the CKEditor 5 icons?
而我需要的正是一個高度定制的編輯器,它需要在 CK5 Classic Editor 的基礎上做以下擴展:
1. 替換所有圖標;
2. 簡化插入超鏈接的交互;
3. 自定義上傳圖片、視頻、音頻的彈窗,以及響應的 DOM 結構;
4. 添加新功能,如選中內容統計字數。
5. 不依賴 JQuery、Vue、React 等第三方庫,可在所有 JS 項目中使用。
明確以上需求之后,可以看出最終的編輯器只會引入 CK5 的內核,其他的插件都需要自己開發
千里之行,始於足下,那就開始吧!
二、搭建基本結構
先創建一個空的項目目錄,然后創建 package.json
經過上面的分析之后,這個項目需要做成多包項目,即游戲本體加DLC基礎編輯器和插件分別打包
所以項目的 package.json 是這樣的:
{ "name": "root", "private": true, "workspaces": [ "packages/*" ], "engines": { "node": ">=12.0.0", "npm": ">=5.7.1" } }
// CKEditor 5 需要 Node.js 12.0.0+
然后創建 packages 目錄,需要開發的包都放在這個目錄下
比如馬上要開發的編輯器 my-editor
還可以根據自己的需要添加 .gitignore、.editorconfig 等文件,這里就先略過
接下來先不管根目錄,進到編輯器目錄 /packages/my-editor
創建編輯器的 package.json 以及源碼目錄 src
my-editor 需要在一個頁面上運行,所以在根目錄下創建一個不參與打包的 example 目錄,作為開發頁面
在 example 目錄下的 index.js 是這個測試頁面的入口文件,主要功能是引入 my-editor 並實例化
而 index.html 是開發頁面的基礎模板,可以根據自己的需要開發,貼一下我自己的代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>My CKEditor 5</title>
</head>
<style> * { padding: 0; margin: 0;
} html, body, .container { width: 100%; height: 100%; background: rgba(247, 247, 247, 1);
} header { height: 60px; text-align: center; line-height: 60px; background-color: #3c9ef3; font-size: 20px; color: white; font-weight: 700;
} main { padding: 24px;
} .editor-container { width: 800px; margin: 0 auto;
}
</style>
<body>
<header>My CKEditor 5</header>
<main>
<div class="editor-container">
<textarea id="editor-area"></textarea>
</div>
</main>
</body>
</html>
項目的基本結構就是這樣,接下來安裝必要依賴並完善 webpack 打包
三、完善打包配置
這個多包項目基於 yarn workspace 實現,所以必須使用 yarn 安裝依賴
首先在 my-editor 目錄下安裝編輯器插件
yarn add \ @ckeditor/ckeditor5-editor-classic \ @ckeditor/ckeditor5-essentials \ @ckeditor/ckeditor5-paragraph \ @ckeditor/ckeditor5-basic-styles \ @ckeditor/ckeditor5-theme-lark \ @ckeditor/ckeditor5-list \ @ckeditor/ckeditor5-link \ @ckeditor/ckeditor5-heading \ @ckeditor/ckeditor5-block-quote
然后回到根目錄安裝開發需要的基本軟件包
yarn add -D -W \
@ckeditor/ckeditor5-dev-utils \
clean-webpack-plugin@3 \
css-loader@1 \
html-webpack-plugin \
less@3 \
less-loader@4 \
postcss-loader@3 \
raw-loader@3 \
style-loader@1 \
webpack@4 \
webpack-cli@3 \
webpack-dev-server@3
接下來在根目錄創建 webpack 的配置文件 webpack.config.js
// webpack.config.js
"use strict"; const HtmlWebpackPlugin = require("html-webpack-plugin"); const path = require("path"); const { styles } = require("@ckeditor/ckeditor5-dev-utils"); const port = 8000; module.exports = { entry: "./example/index.js", output: { path: path.resolve(__dirname, "dist"), filename: "bundle.js", }, devtool: "source-map", performance: { hints: false }, devServer: { clientLogLevel: 'warning', hot: true, compress: true, host: 'localhost', port: port, publicPath: '/', after (app) { console.log(`Your application is running here: http://localhost:${port}`)
}, quiet: true // necessary for FriendlyErrorsPlugin
}, module: { rules: [ { test: /\.less$/, use: [ { loader: "style-loader", }, { loader: "css-loader", }, { loader: "less-loader", }, ], }, { test: /\.svg$/, use: ["raw-loader"], }, { test: /\.css$/, use: [ { loader: "style-loader", options: { injectType: "singletonStyleTag", attributes: { "data-cke": true, }, }, }, { loader: "postcss-loader", options: styles.getPostCssConfig({ themeImporter: { themePath: require.resolve("@ckeditor/ckeditor5-theme-lark"), }, minify: true, }), }, ], }, ], }, plugins: [ new HtmlWebpackPlugin({ title: "Example", template: "example/index.html", }), ], };
這里將項目的入口指向了 /example/index.js
最后在根目錄的 package.json 中添加啟動指令 dev
{ ... "scripts": { "dev": "webpack-dev-server --mode development --config webpack.config.js"
} ... }
萬事俱備,只欠一個編輯器了
四、啟動編輯器
回到 my-editor 目錄下,先寫一個簡單的 CKEditor 編輯器:
// packages/my-editor/src/index.js
import ClassicEditor from "@ckeditor/ckeditor5-editor-classic/src/classiceditor"; import Essentials from "@ckeditor/ckeditor5-essentials/src/essentials"; import Paragraph from "@ckeditor/ckeditor5-paragraph/src/paragraph"; import Bold from "@ckeditor/ckeditor5-basic-styles/src/bold"; import Italic from "@ckeditor/ckeditor5-basic-styles/src/italic"; import BlockQuote from "@ckeditor/ckeditor5-block-quote/src/blockquote"; import Heading from "@ckeditor/ckeditor5-heading/src/heading"; import Link from "@ckeditor/ckeditor5-link/src/link"; import List from "@ckeditor/ckeditor5-list/src/list"; export default class MyEditor { constructor(props) { Object.assign( this, { id: "editor", }, props ); this.render(); } render() { ClassicEditor.create(document.querySelector(`#${this.id}`), { plugins: [ Essentials, Paragraph, Bold, Italic, BlockQuote, Heading, Link, List, ], toolbar: [ "heading", "|", "bold", "italic", "link", "bulletedList", "numberedList", "|", "blockQuote", "undo", "redo", ], }) .then((editor) => { console.log("Editor was initialized", editor); }) .catch((error) => { console.error(error.stack); }); } }
這里用到 ClassicEditor.create() 函數,這是一個 Promise,用於創建 CKEditor 編輯器
它可以接收兩個參數,分別是:用於渲染編輯器的 DOM 元素和配置項 Config
其中完整的 Config 可以查看官網的說明,我這里只用到了 plugins 和 toolbar
plugins: 加載插件,由插件對象構成的數組。
toolbar: 配置工具欄,由工具欄名稱組成的字符串數組,工具欄的名稱需要在插件中定義。
全都准備好了,回到根目錄,yarn run dev 啟動項目吧!
如果啟動失敗,根據錯誤提示,對照上文,看下是哪一步出錯
啟動成功之后,打開瀏覽器訪問 localhost:8000,應該能看到這個頁面:
如果頁面能訪問,但編輯器沒有渲染,檢查一下控制台的報錯,根據錯誤信息進行修復
五、調試與打包
CKEditor 提供了一個用於調試編輯器的插件 CKEditor 5 inspector
在 my-editor 目錄下安裝它:
yarn add --dev @ckeditor/ckeditor5-inspector
然后在 index.js 中引入,在 create 函數的 then 回調中啟用調試器
import CKEditorInspector from '@ckeditor/ckeditor5-inspector'; export default class MyEditor { ... ClassicEditor.create() .then((editor) => { CKEditorInspector.attach(editor); this.editor = editor; }) .catch(); ... }
重新啟動項目,就能在頁面底部看到調試器了
PS. 這個調試器其實是以 DOM 的形式插到頁面中的
好了,只剩下打包編輯器了
在 my-editor 目錄下新增 webpack.config.js 文件
"use strict"; const path = require("path"); const { styles } = require("@ckeditor/ckeditor5-dev-utils"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); module.exports = { mode: "production", entry: "./src/index.js", output: { path: path.resolve(__dirname, "dist"), filename: "my-editor.min.js", libraryTarget: "umd", }, optimization: { minimizer: [ new TerserPlugin({ terserOptions: { output: { // Preserve CKEditor 5 license comments.
comments: /^!/, }, }, extractComments: false, }), ], }, module: { rules: [ { test: /\.less$/, use: [ { loader: "style-loader", }, { loader: "css-loader", }, { loader: "less-loader", }, ], }, { test: /\.svg$/, use: ["raw-loader"], }, { test: /\.css$/, use: [ { loader: "style-loader", options: { injectType: "singletonStyleTag", attributes: { "data-cke": true, }, }, }, { loader: "postcss-loader", options: styles.getPostCssConfig({ themeImporter: { themePath: require.resolve("@ckeditor/ckeditor5-theme-lark"), }, minify: true, }), }, ], }, ], }, plugins: [ new CleanWebpackPlugin() ], performance: { hints: false }, };
然后在 package.json 文件中添加打包命令 build
{ ... "scripts": { "build": "webpack --mode production --config webpack.config.js" }, ... }
這樣在當前目錄下就能 yarn run build 打包代碼
如果希望在根目錄也能通過 build 命令打包,就在 package.json 中添加這樣一行命令:
{ ... "scripts": { "build": "yarn workspace my-editor run build" }, ... }
執行該命令的時候,會找到當前工作空間 packages 下的 my-editor 目錄,並執行 run build 命令
打包完成后,還可以在開發頁面 example/index.js 中,將編輯器的路徑改為打包后的路徑(my-editor/dist/my-editor.min),以此來驗證打包后的代碼是否正確
yarn 目前還不支持在根目錄批量構建 workspace 中的項目,如果有這個需求,可以借助 lerna 來實現
lerna run --stream --sort build
也可以通過 lerna 來批量發布
lerna publish from-package
編輯器的項目已經搭建好了,萬里長征邁出了第一步
接下來會用一個簡單的加粗插件來介紹 CKEditor 5 的設計和插件開發,to be continue