在 Laravel 中通过 ZIP 自托管集成 CKEditor 5:完整配置与实战指南
CKEditor 5 是一个模块化架构的富文本编辑器框架,它没有提供官方的 Laravel 集成包,但由于编辑器本质上是纯 JavaScript/TypeScript 库,可以在任何能托管静态资源的 Web 环境中运行——包括 PHP 生态的 Laravel。本文以 Laravel 官方集成指南 为骨架,完整讲解如何将自托管的 ZIP 构建包放入 Laravel 项目的 public 静态目录,并通过 import map 在 Blade 模板中加载与初始化编辑器,同时结合仓库源码补充配置项、许可证与数据交互的底层细节。读完本文,你将掌握一套无需 npm 打包工具、可离线部署的 CKEditor 5 + Laravel 集成方案。
集成方式选型:ZIP 自托管与 CDN 的区别
Laravel 集成 CKEditor 5 有两条主流路线,本文聚焦的是 ZIP 自托管方案:
- ZIP 自托管:从 CDN 下载包含编辑器全部插件的压缩包,解压后放入项目静态目录,由你自己的服务器对外提供这些资源。适合无法依赖外部 CDN、有离线部署或内网环境要求的场景。相关文档见 从 ZIP 安装 CKEditor 5。
- CDN 加载:直接在页面中通过
<script>与<link>引入cdn.ckeditor.com上的资源,同样不需要 npm 构建工具链,但需要网络可达 CKEditor CDN,且云分发需要商业许可证。见 Laravel 集成(CDN 版)。
两条路线的共同前提是:CKEditor 5 是纯 JS/TS 库,Laravel(PHP)只负责把这些静态资源"喂"给浏览器,编辑器在前端完成初始化与渲染。
前置准备:准备一个 Laravel 项目
本文假设你已经拥有一个可运行的 Laravel 项目。如果还没有,可以使用 Composer 创建基础项目:
composer create-project laravel/laravel example-app
关于框架本身的安装与配置,请查阅 Laravel 官方安装文档(对应 Laravel 10.x 版本)。本文后续所有操作均基于 Laravel 默认目录结构,即 public/ 为 Web 服务器文档根目录。
第一步:下载并放置 ZIP 构建包
ZIP 自托管的第一步是获取构建产物。CKEditor 5 官方 CDN 提供了随版本的 ZIP 归档(具体下载地址可参考 quick-start 指南 中的链接)。下载并解压后,归档内包含以下关键文件:
index.html—— 一个可直接运行的编辑器示例页;ckeditor5/ckeditor5.js—— 推荐使用的 ESM 构建,内含编辑器与全部开源插件;ckeditor5/ckeditor5.umd.js—— 备选的 UMD 构建;ckeditor5/*.css—— 编辑器样式表,绝大多数场景使用ckeditor5.css(样式文件的更多说明见 Editor and content styles 指南);translations/—— 编辑器 UI 翻译文件(用于 设置 UI 语言);README.md与LICENSE.md。
现在将解压得到的 ckeditor5.js 与 ckeditor5.css 复制到 Laravel 项目的 public/assets/vendor/ 目录中(目录不存在则自行创建)。完成后的项目目录结构应类似:
├── app
├── bootstrap
├── config
├── database
├── public
│ ├── assets
│ │ └── vendor
│ │ ├── ckeditor5.js
│ │ └── ckeditor5.css
│ ├── .htaccess
│ ├── favicon.ico
│ ├── index.php
│ └── robots.txt
├── resources
│ ├── views
│ │ ├── welcome.blade.php
│ │ └── ...
├── routes
└── ...
将资源放在 public/ 下是 Laravel 的静态资源约定——只有该目录中的文件才能被 Web 服务器直接访问,这也决定了后续模板中 import map 与 CSS 链接的路径写法。
第二步:在 Blade 模板中引入并初始化编辑器
ZIP 归档内的 index.html 已经包含了全部所需的标记(markup),我们可以把它复制到 resources/views/welcome.blade.php 中。关键点在于:模板中的 CSS 链接与 import map 路径必须与你的目录结构一致。由于本示例中编辑器文件位于 resources/views/welcome.blade.php 的上两级目录 public/assets/vendor/,因此使用 ../../assets/vendor/ 前缀。
修改后的模板示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CKEditor 5 - Quick start ZIP</title>
<link rel="stylesheet" href="../../assets/vendor/ckeditor5.css">
<style>
.main-container {
width: 795px;
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<div class="main-container">
<div id="editor">
<p>Hello from CKEditor 5!</p>
</div>
</div>
<script type="importmap">
{
"imports": {
"ckeditor5": "../../assets/vendor/ckeditor5.js",
"ckeditor5/": "../../assets/vendor/"
}
}
</script>
<script type="module">
import {
ClassicEditor,
Essentials,
Paragraph,
Bold,
Italic,
Font
} from 'ckeditor5';
ClassicEditor
.create( {
attachTo: document.querySelector( '#editor' ),
licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
plugins: [ Essentials, Paragraph, Bold, Italic, Font ],
toolbar: [
'undo', 'redo', '|', 'bold', 'italic', '|',
'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor'
]
} )
.then( editor => {
window.editor = editor;
} )
.catch( error => {
console.error( error );
} );
</script>
</body>
</html>
模板中的关键机制
<script type="importmap">:浏览器原生支持 import map,它将裸模块标识符ckeditor5映射到实际文件路径。"ckeditor5": "../../assets/vendor/ckeditor5.js"指定主入口,"ckeditor5/": "../../assets/vendor/"用于解析插件内部相对导入的其余模块。<script type="module">:使用 ES Module 语法从ckeditor5标识符按需导入编辑器类与插件。ClassicEditor来自 classiceditor.ts,Essentials、Paragraph、Bold、Italic、Font等插件来自ckeditor5主包(源码位于 packages/ckeditor5-core 及对应插件包)。attachTo: document.querySelector( '#editor' ):指定编辑器挂载到的 DOM 元素。该配置项定义在 editorconfig.ts,属于根配置RootConfig的一部分,替代了旧版的"传入 DOM 元素作为create()第一参数"的写法。plugins与toolbar:plugins数组声明编辑器加载的功能插件,toolbar数组声明工具栏上显示的按钮。二者的对应关系是:导入插件 → 加入plugins→ 把该插件提供的按钮名加入toolbar。更系统的配置方法见 配置编辑器功能。.then()/.catch():create()返回 Promise。then回调中拿到编辑器实例(示例里挂到window.editor便于调试),catch捕获初始化错误。
关于 licenseKey(44.0.0 起必填)
模板中的 licenseKey 不是可选占位符。自 CKEditor 5 44.0.0 版本起,licenseKey 属性是使用编辑器的必要条件。对于从 ZIP 自托管的场景,只有两种合规选择:
- 符合 GPL 开源协议的使用条件,配置值写
'GPL'; - 购买 自托管分发的商业许可证,配置值为真实密钥。
licenseKey 是 EditorConfig 的顶层配置项,定义于 editorconfig.ts。需要注意的是:'GPL' 密钥只适用于 npm 或 ZIP 自托管分发,云 CDN 分发不接受 'GPL' 密钥。如需评估商业版,可申请免费试用;试用密钥有效期为 14 天,仅供评估用途。
第三步:启动服务并验证
在 Laravel 项目根目录执行:
php artisan serve
然后访问 http://localhost:8000 即可看到编辑器。
有一点必须强调:使用 import map 加载 ES Module 时,必须通过 HTTP 服务器访问页面。直接以 file:// 协议双击打开 HTML 会触发浏览器的 CORS 安全策略(模块必须从同源加载),导致编辑器无法运行。php artisan serve 已经满足了这一前提;在生产环境,Nginx、Caddy 等 Web 服务器同样可以胜任。
深入实战:编辑器数据与 Laravel 后端的交互
编辑器渲染完成只是第一步,真正有价值的场景是把编辑内容提交给 Laravel 后端保存。核心 API 是 getData() 与 setData(),其实现在 editor.ts,完整指南见 获取与设置数据。
通过 getData() 主动获取内容
需要保存数据时(例如点击"保存"按钮后通过 fetch/Axios 发送),调用编辑器实例的 getData():
let editor;
ClassicEditor
.create( {
attachTo: document.querySelector( '#editor' ),
licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
plugins: [ /* ... */ ],
toolbar: [ /* ... */ ]
} )
.then( newEditor => {
editor = newEditor;
} )
.catch( error => {
console.error( error );
} );
// 假设页面中有 <button id="submit">提交</button>
document.querySelector( '#submit' ).addEventListener( 'click', () => {
const editorData = editor.getData();
fetch( '/articles', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify( {
content: editorData,
// 记得带上 Laravel 的 CSRF token
_token: document.querySelector( 'meta[name="csrf-token"]' ).getAttribute( 'content' )
} )
} );
} );
利用表单自动同步(Classic 编辑器 + <textarea>)
另一种更"经典"的集成方式是把 <textarea> 作为编辑器的挂载元素。该方式仅在 Classic 编辑器且挂载元素为 <textarea> 时生效:用户提交表单时,CKEditor 会自动把编辑器内容写回 <textarea> 的 value,无需任何额外 JS 即可把数据随表单 POST 到服务端。在 PHP 端直接读取即可:
<?php
$editor_data = $_POST[ 'content' ];
?>
从数据库读取内容回填到 <textarea> 时,务必使用 htmlspecialchars() 转义,防止 HTML 内容破坏页面结构:
<?php
$data = htmlspecialchars("<p>Hello, world!</p>", ENT_QUOTES, 'UTF-8');
?>
<textarea name="content" id="editor"><?= $data ?></textarea>
这样 <p> 会以 <p> 的形式安全地出现在文本域中,避免文本中的 < 或 <IMPORTANT> 这类内容丢失。
用 setData() 动态替换内容
当需要以编程方式(例如异步加载数据后)替换编辑器内容时,使用 setData():
editor.setData( '<p>Some text.</p>' );
初始化内容的两种方式
- 默认方式:编辑器会使用挂载 DOM 元素内部的 HTML 作为初始内容(如上文模板中
<div id="editor"><p>Hello from CKEditor 5!</p></div>); root.initialData:若不便修改服务端输出的 HTML,或数据需通过 JS 异步加载,可在配置中通过root.initialData指定初始数据,它会覆盖 DOM 内的内容。
进阶:进一步定制编辑器
按需增减功能
CKEditor 5 采用插件化架构,功能按需加载。例如在 plugins 中加入 Heading、Link、List、BlockQuote 等,并在 toolbar 中列出对应按钮。移除功能则用 removePlugins:
ClassicEditor
.create( {
licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
removePlugins: [ 'Heading' ],
toolbar: [ 'bold', 'italic', 'bulletedList', 'numberedList', 'blockQuote' ]
} )
.catch( error => {
console.log( error );
} );
注意插件之间存在依赖关系——例如 Autolink 依赖 Link,只移除 Link 会抛出 plugincollection-required 错误,需要把依赖它的插件一并移除。这一点同样适用于 ZIP 构建,因为 ZIP 内是包含全部插件的完整构建,控制权在你手中。
服务端视角的注意事项
- 富文本内容属于不可信输入,保存前应在服务端做 XSS 过滤与白名单校验,前端过滤不能作为唯一防线;
public/assets/vendor/中的静态文件建议按版本命名目录(如vendor/ckeditor5/44.x/),以便升级时正确刷新浏览器缓存;- 若生产环境部署在子路径下,模板中的
../../assets/vendor/...相对路径需要相应调整。
下一步学习路径
- 深入编辑器数据读写与自动保存机制:获取和设置数据;
- 全面了解配置项(工具栏、功能参数等):配置编辑器功能;
- 按功能模块学习各插件的用法:功能索引;
- 许可证密钥的获取、类型与激活流程:许可证密钥与激活;
- 若不希望自托管、可接受 CDN 分发,可参考 Laravel 集成(CDN 版) 的写法。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.26 K641- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python870
SlideSCIPPT插件,支持素材库、AI助手、一键添加图片标题,复制粘贴位置、一键图片对齐、一键插入Markdown(加粗、超链接等行内样式、代码块、LaTeX等块级样式)、便捷导出图片!C#701
Agent-Reach给你的 AI Agent 一键装上互联网能力。13 个平台(网页/GitHub/YouTube/小红书/B站/Twitter/Reddit 等)多后端路由,当下最稳的接入方式替你选好、装好、体检好。GitHub 主仓库同步镜像。Python1384
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go23646
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java37751