CKEditor 5 摸爬滾打(一)—— 從零構建定製化工程項目

最近需要將項目中的編輯器從 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 \
    html-webpack-plugin \
    less \
    less-loader \
    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);
    })
   .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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章