指南Node.js 中的进阶编辑器捆绑

本文介绍了如何在 Node.js 中创建 CKEditor 5 编辑器捆绑包。它是从 CKEditor 5 包(从 CKEditor 5 Builder 下载)创建编辑器捆绑包的分步指南,但可以轻松地适应各种设置。CKEditor 云服务需要编辑器捆绑包来启用 文档存储导入和导出 以及 连接优化 功能。

# 捆绑包要求

CKEditor 云服务可以使用的那一款编辑器捆绑包必须满足某些要求

  • 它需要被构建成单个 .js 文件。
  • 插件应添加到 builtinPlugins 属性中,以包含在编辑器捆绑包中。您不能使用 config.plugins 选项将插件添加到编辑器。
  • webpack 配置中的 output.library 属性应设置为 CKEditorCS 值。
  • 编辑器捆绑包中使用的插件不能执行外部 HTTP 请求。
  • 编辑器实例需要是编辑器捆绑包中的默认导出。

官方支持的 CKEditor 5 插件在 CKEditor 云服务将它们用于编辑器捆绑包时,已经满足了不执行外部请求的要求。如果您的自定义插件发送任何外部请求,那么您有两个选择

  • 如果此插件不修改编辑器的模型或编辑器的输出,则您可以在编辑器捆绑包上传期间从编辑器配置中将其删除。
  • 如果此插件修改了编辑器的模型,则可以将其重构为 2 个插件。其中一个将负责编辑部分,而另一个将发出 HTTP 请求。这样,您就可以在不影响输出数据的情况下,在编辑器捆绑包上传期间删除其中一个插件。

有关从编辑器配置中删除插件的更多信息,请参阅 上传编辑器捆绑包 部分。

# 创建捆绑包

以下示例作为如何准备编辑器捆绑包配置的模板。假设您拥有基本的 CKEditor 5 文件 - package.jsonmain.jsindex.htmlstyle.css 文件,这些文件是使用 CKEditor 5 Builder 和“自托管(npm)”集成方法的结果。

# 依赖项

此示例使用以下依赖项

  • ckeditor5
  • ckeditor5-premium-features
  • webpack
  • webpack-cli
  • mini-css-extract-plugin

如果您按照 Builder 设置进行操作,则 ckeditor5* 包应该已经存在。对于其余的运行

npm install -D webpack webpack-cli mini-css-extract-plugin

之后,您的 package.json 依赖项部分应该如下所示

{
  "dependencies": {
    "ckeditor5": "^42.0.0",
    "ckeditor5-premium-features": "^42.0.0"
  },
  "devDependencies": {
    "mini-css-extract-plugin": "^2.9.0",
    "webpack": "^5.92.1",
    "webpack-cli": "^5.1.4"
  }
}

# 编辑器设置

强烈建议在创建捆绑包和将 CKEditor 5 与前端层集成时使用相同的编辑器设置。这样,更容易将前端和后端之间的所有内容保持同步和一致。这需要对上述设置进行一些调整,方法是更改编辑器各个部分的定义和使用方式。

两种设置的共同部分是编辑器类、插件列表和编辑器配置。我们将 main.js 文件拆分为 3 个文件

  • 将定义共享部分的 main.js 文件。
  • main-fe.js 文件,它将负责前端应用程序中的编辑器设置。
  • main-be.js 文件,将从中生成编辑器捆绑包。它将是捆绑器入口文件。

第一步是调整 main.js 文件。

// main.js

import {
    ClassicEditor,
    Essentials,
    Paragraph,
    Bold,
    Italic
} from 'ckeditor5';

import {
    RealTimeCollaborativeEditing
} from 'ckeditor5-premium-features';

// 1. Remove style imports. Those will be used in main-fe.js.
// import 'ckeditor5/ckeditor5.css';
// import 'ckeditor5-premium-features/ckeditor5-premium-features.css';

// import './style.css';

// 2. Create a list of plugins which will be exported.
const pluginList = [
    Essentials,
    Paragraph,
    Bold,
    Italic,
    RealTimeCollaborativeEditing
];

// 3. Adjust 'editorConfig' to utilize 'pluginList'.
const editorConfig = {
    plugins: pluginList,
    ...
}

// 4. Export shared parts, instead of initializing editor.
// ClassicEditor.create(document.querySelector('#editor'), editorConfig);

export {
    ClassicEditor,
    pluginList,
    editorConfig
}

接下来,我们将创建将在前端使用的 main-fe.js

// main-fe.js

import { ClassicEditor, editorConfig } from './main.js';

// 1. Move style imports from main.js here.
import 'ckeditor5/ckeditor5.css';
import 'ckeditor5-premium-features/ckeditor5-premium-features.css';

import './style.css';

// 2. Initialize editor.
ClassicEditor.create(document.querySelector('#editor'), editorConfig);

之后,应该创建用于捆绑的 main-be.js 文件。

// main-be.js

import { ClassicEditor, pluginList } from './main.js';

class CKEditorCS extends ClassicEditor {}

// 1. Assign plugins to be used in bundle.
CKEditorCS.builtinPlugins = pluginList;

// 2. Export editor class.
export default CKEditorCS;

# 前端构建

进行上述更改后,您的应用程序可以像以前一样构建和捆绑。唯一需要调整的地方是更改导入的文件。

<!doctype html>
<html lang="en">
    <head>...</head>
    <body>
        ...
        <!-- <script type="module" src="./main.js"></script> -->
        <script type="module" src="./main-fe.js"></script>
    </body>
</html>

# 捆绑器配置

接下来,您需要准备 webpack 配置以生成有效的捆绑包。

const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' );

module.exports = {
    entry: './main-be.js', // Your editor build configuration.
    output: {
        filename: 'editor.bundle.js', // Content of this file is required to upload to CKEditor Cloud Services.
        library: 'CKEditorCS', // It is required to expose you editor class under the "CKEditorCS" name!

        libraryTarget: 'umd',
        libraryExport: 'default',
        clean: true
    },
    plugins: [
        new MiniCssExtractPlugin()
    ],
    module: {
        rules: [
            {
                test: /\.css$/i,
                use: [MiniCssExtractPlugin.loader, 'css-loader']
            }
        ]
    },
    performance: { hints: false }
};

# 生成捆绑包

然后,在 CKEditor 5 包文件夹中运行以下命令

npx webpack --mode production

此命令将生成 ./dist/editor.bundle.js 文件。此捆绑包文件应随后 上传到 CKEditor 云服务服务器

# 使用 TypeScript 构建编辑器捆绑包

上面介绍的示例在编辑器源文件中使用了 JavaScript。使用 TypeScript 构建编辑器捆绑包也是可能的。要开始使用 TypeScript,请执行以下步骤

  1. 将 TypeScript 集成到 webpack 中。有关更多详细信息,请参阅 本指南
  2. 将编辑器源文件的扩展名更改为 .ts。调整 webpack.config.js 中的入口路径。
  3. 请参阅 使用 TypeScript 指南,并在需要时调整导入和配置设置。

# 从传统配置创建捆绑包

本节涉及从 传统配置 捆绑编辑器。传统配置是使用编辑器预设、DLL 或 @ckeditor/ckeditor5-* 导入的配置。

有关最新说明,请参阅上面的 创建捆绑包部分

以下示例作为如何准备编辑器构建和捆绑器配置的模板。假设您拥有一个包含 ckeditor.jspackage.jsonwebpack.config.js 文件的现有 CKEditor 5 包。

# 依赖项

此示例使用以下依赖项

  • @ckeditor/ckeditor5-editor-classic
  • @ckeditor/ckeditor5-basic-styles
  • @ckeditor/ckeditor5-essentials
  • @ckeditor/ckeditor5-paragraph
  • @ckeditor/ckeditor5-real-time-collaboration
  • @ckeditor/ckeditor5-dev-utils
  • @ckeditor/ckeditor5-theme-lark
  • webpack
  • webpack-cli
  • postcss-loader
  • raw-loader
  • style-loader

package.json 文件依赖项部分应该类似于以下部分。

包列表及其版本可能会有所不同,具体取决于编辑器预设/构建以及创建它的时间。

{
  "dependencies": {
    "@ckeditor/ckeditor5-basic-styles": "41.4.2",
    "@ckeditor/ckeditor5-editor-classic": "41.4.2",
    "@ckeditor/ckeditor5-essentials": "41.4.2",
    "@ckeditor/ckeditor5-paragraph": "41.4.2",
    "@ckeditor/ckeditor5-real-time-collaboration": "41.4.2"
  },
  "devDependencies": {
    "@ckeditor/ckeditor5-dev-utils": "^32.1.2",
    "@ckeditor/ckeditor5-theme-lark": "41.4.2",
    "postcss-loader": "^4.3.0",
    "raw-loader": "^4.0.2",
    "style-loader": "^2.0.0",
    "webpack": "^5.91.0",
    "webpack-cli": "^4.10.0"
  }
}

# 编辑器构建配置

此文件展示了一个编辑器构建配置示例。捆绑器将其用作入口文件。此 ckeditorcs.js 文件应基于您的 ckeditor.js 文件创建。

请记住将以下配置适应您的需求,因为示例保持在最低限度。请参阅 CKEditor 5 构建的 高级设置 文档,以根据您的要求调整捆绑包创建过程。

// ckeditorcs.js

// The editor base creator to use.
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';

// All plugins that you would like to use in your editor.
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 RealTimeCollaborativeEditing from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativeediting';

class CKEditorCS extends ClassicEditor {}

// Load all plugins you would like to use in your editor this way.
// This is the only way to load plugins into the editor which will then be used in CKEditor Cloud Services.
CKEditorCS.builtinPlugins = [
    Essentials,
    Paragraph,
    Bold,
    Italic,
    RealTimeCollaborativeEditing
];

// Export your editor.
export default CKEditorCS;

# 捆绑器配置

此文件展示了一个捆绑器配置示例。这里使用 webpack 作为捆绑器。

// webpack.config.js

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

module.exports = {
    entry: './ckeditorcs.js', // Your editor build configuration.
    output: {
        filename: 'editor.bundle.js', // Content of this file is required to upload to CKEditor Cloud Services.
        library: 'CKEditorCS', // It is required to expose you editor class under the "CKEditorCS" name!

        libraryTarget: 'umd',
        libraryExport: 'default'
    },
    module: {
        rules: [
            {
                test: /\.svg$/,
                use: [ 'raw-loader' ]
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'style-loader',
                        options: {
                            injectType: 'singletonStyleTag'
                        }
                    },
                    {
                        loader: 'postcss-loader',
                        options: styles.getPostCssConfig( {
                            themeImporter: {
                                themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
                            },
                            minify: true
                        } )
                    }
                ]
            }
        ]
    },
    performance: { hints: false }
};

这里最重要的部分是 output.library 字段。没有它,捆绑包将无法与 CKEditor 云服务服务器一起使用。

module.exports = {
    // ...
    output: {
        library: 'CKEditorCS', // It is required to expose you editor class under the "CKEditorCS" name!
        // ...
    },
    // ...
};

# 生成捆绑包

在 CKEditor 5 包文件夹中运行以下命令

npm install
npx webpack --mode production

此命令将生成 ./dist/editor.bundle.js 文件。此捆绑包文件应随后 上传到 CKEditor 云服务服务器

您的 webpack output.* 配置可能略有不同。对于 CKEditor 5,将构建工件存储在 ./build/ 文件夹中也很常见。在这种情况下,捆绑包将是 ./build/editor.bundle.js 文件。

# 带有 Watchdog、上下文或“超级构建”的编辑器捆绑包

云服务期望捆绑文件中 Editor 类作为默认导出。如果您使用的是带有 Watchdog上下文 的编辑器捆绑包,或者您将多个编辑器构建为 “超级构建”,则需要采取额外的步骤才能上传这些编辑器。

您可以从多个源文件构建多个编辑器捆绑包,用于不同的目的。然后,可以使用单个 webpack 构建来捆绑所有这些捆绑包。借助这种方法,您将拥有一个与云服务兼容的编辑器捆绑包,并且您仍然可以使用另一个编辑器捆绑包并利用“超级构建”、watchdog 或上下文。

# 使用 Watchdog 创建传统编辑器捆绑包

假设您拥有一个 ckeditor.js 源文件,它导出带有 watchdog 的编辑器

import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js';
import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog';
// Other plugins imports.

class Editor extends ClassicEditor {}

Editor.builtinPlugins = [
    // Imported plugins.
];

const watchdog = new EditorWatchdog( Editor );
export default watchdog;

您现在可以创建 ckeditorcs.js 源文件,其内容与上面的文件相同。唯一的区别是此文件中的 export。您应该导出 Editor 实例,而不是导出 watchdog:export default Editor;

有了这两个源文件,您可以调整 webpack 配置,以在单个构建步骤中捆绑这两个编辑器

'use strict';

const path = require( 'path' );
const webpack = require( 'webpack' );
const { bundler, styles } = require( '@ckeditor/ckeditor5-dev-utils' );
const { CKEditorTranslationsPlugin } = require( '@ckeditor/ckeditor5-dev-translations' );
const TerserWebpackPlugin = require( 'terser-webpack-plugin' );

const config = {
    devtool: 'source-map',
    performance: { hints: false },
    optimization: {
        minimizer: [
            new TerserWebpackPlugin( {
                sourceMap: true,
                terserOptions: {
                    output: {
                        comments: /^!/
                    }
                },
                extractComments: false
            } )
        ]
    },
    plugins: [
        new CKEditorTranslationsPlugin( {
            language: 'en',
            additionalLanguages: 'all'
        } ),
        new webpack.BannerPlugin( {
            banner: bundler.getLicenseBanner(),
            raw: true
        } )
    ],
    module: {
        rules: [
            {
                test: /\.svg$/,
                use: [ 'raw-loader' ]
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'style-loader',
                        options: {
                            injectType: 'singletonStyleTag',
                            attributes: {
                                'data-cke': true
                            }
                        }
                    },
                    {
                        loader: 'css-loader'
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: styles.getPostCssConfig( {
                                themeImporter: {
                                    themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
                                },
                                minify: true
                            } )
                        }
                    }
                ]
            }
        ]
    }
};

module.exports = [
    // The first bundle will have the editor with watchdog and can be used in your application.
    {
        ...config,
        entry: path.resolve( __dirname, 'src', 'ckeditor.js' ),
        output: {
            library: 'Watchdog',
            path: path.resolve( __dirname, 'build' ),
            filename: 'ckeditor.js',
            libraryTarget: 'umd',
            libraryExport: 'default'
        }
    },
    // The second bundle will be ready to be uploaded to the Cloud Services server.
    {
        ...config,
        entry: path.resolve( __dirname, 'src', 'ckeditorcs.js' ),
        output: {
            library: 'CKEditorCS',
            path: path.resolve( __dirname, 'build' ),
            filename: 'ckeditorcs.js',
            libraryTarget: 'umd',
            libraryExport: 'default'
        }
    }
];

类似地,如果您使用上下文或“超级构建”,您可以构建多个捆绑包。在使用上下文捆绑包的情况下,请确保云服务的捆绑包包含上下文编辑器具有的所有插件。对于超级构建,您可以简单地创建源文件的副本,并导出您要上传到云服务的单个编辑器实例。

# 使用 TypeScript 构建编辑器捆绑包

上面介绍的示例在编辑器源文件中使用了 JavaScript。使用 TypeScript 构建编辑器捆绑包也是可能的。要开始使用 TypeScript,请执行以下步骤

  1. 将 TypeScript 集成到 webpack 中。有关更多详细信息,请参阅 本指南
  2. 将编辑器源文件的扩展名更改为 .ts。调整 webpack.config.js 中的入口路径。
  3. 请参阅 使用 TypeScript 指南,并在需要时调整导入和配置设置。