ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GrapesJS Storage Manager 项目持久化完全指南:本地/远程存储、自定义 Storage 与常见实战方案

GrapesJS Storage Manager 项目持久化完全指南:本地/远程存储、自定义 Storage 与常见实战方案 GrapesJS Storage Manager 项目持久化完全指南本地/远程存储、自定义 Storage 与常见实战方案【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs导读GrapesJS 的 Storage Manager存储管理器是内置在编辑器中的持久化模块负责把项目数据样式、页面、组件结构等自动保存到本地或远程服务器并在编辑器初始化时自动恢复。本文以 GrapesJS 仓库中的 Storage 模块文档 为主线结合 storage_manager 源码 逐项拆解storageManager配置、projectData数据模型、自动保存策略、远程 REST API 对接方式以及如何通过editor.Storage.add()自定义、扩展和替换存储实现帮助你在自己的应用中快速落地“草稿自动保存”“多项目隔离”“服务端持久化”“表单内联读写”等场景。前置要求本文涉及的内容需要 GrapesJS v0.19.* 或更高版本详见 Storage.md。Storage Manager 是什么Storage Manager 是 GrapesJS 内置模块负责项目数据的持久化。它的职责很简单把编辑器中的项目状态序列化为 JSON通过一个“存储实现”storage写出去在编辑器初始化时再通过同样的存储实现把 JSON 读回来并还原到编辑器中。从源码结构看整个模块由以下几部分组成storage_manager 目录config/config.ts全部配置项的 TypeScript 类型定义与默认值model/LocalStorage.ts基于浏览器localStorage的内置存储实现model/RemoteStorage.ts基于fetch的内置远程存储实现model/IStorage.ts所有存储实现必须满足的接口约定index.ts模块本体负责store/load流程编排、事件分发、recovery 逻辑。在编辑器初始化时模块会默认注册local与remote两个存储并把当前存储类型设置为local对应 index.ts 中的构造函数。这一点也在 storage_manager 的单元测试 中得到验证默认getCurrent()返回locallocal与remote均已注册。模块配置storageManager 配置项详解修改默认配置的方式是在grapesjs.init()时传入storageManager对象const editor grapesjs.init({ // ... // 默认配置 storageManager: { type: local, // 存储类型内置可选local | remote autosave: true, // 是否自动保存 autoload: true, // 初始化时是否自动加载已存数据 stepsBeforeSave: 1, // 启用 autosave 时积累多少次变更后才触发保存 // ... // 各存储类型的默认选项 options: { local: { /* ... */ }, remote: { /* ... */ }, } }, });如果完全不需要持久化可以把模块整体关掉const editor grapesjs.init({ // ... storageManager: false, });配置项完整清单来自源码默认值结合 config/config.ts 中的默认值storageManager支持以下配置配置项类型默认值说明typelocal \| remote \| stringlocal当前使用的存储类型传入自定义类型时需先用Storage.add注册autosavebooleantrue是否在变更达到阈值后自动保存autoloadbooleantrue编辑器初始化时是否自动加载已存储数据stepsBeforeSavenumber1autosave开启时累积多少次结构变更后触发保存远程存储下调大该值可显著减少网络请求recoveryboolean \| functionfalse远程存储失败时的本地数据恢复策略见下文“恢复策略”onStore(data, editor) datadata data保存前回调可异步用于在写入前加工项目数据onLoad(data, editor) datadata data加载后回调可异步用于在还原前加工项目数据options.localLocalStorageConfig{ key: gjsProject, checkLocal: true }本地存储选项options.remoteRemoteStorageConfig见下文远程存储选项其中id默认gjs-是历史遗留的 key 前缀配置源码中标注为deprecated主要用于加载时剥离数据 key 前缀__clearKeys方法按^${config.id}正则清理新项目不建议依赖它。本地存储选项options.local定义于 model/LocalStorage.ts配置项类型默认值说明keystringgjsProject数据在localStorage中使用的键名checkLocalbooleantrue是否在读写前检查浏览器是否支持localStorage不支持时会抛出localStorage not available远程存储选项options.remote定义于 model/RemoteStorage.ts默认值如下配置项类型默认值说明headersobject{}自定义请求头urlStorestring保存项目数据的目标 URLurlLoadstring加载项目数据的 URLcontentTypeJsonbooleantrue是否以application/json传输为false时改用FormData提交fetchOptionsstring \| (opts) opts传给fetch的自定义选项可传对象也可传函数按当前请求动态返回credentialsRequestCredentialsincludefetch的 credentials 选项默认携带 Cookie 等凭证onStore(data, editor) datadata data保存前改写请求 body用于对齐服务端 API 结构onLoad(result, editor) dataresult result加载后从响应中提取项目数据项目数据Project DataJSON 才是持久化的唯一可靠载体项目数据是一个 JSON 对象包含编辑器还原项目所需的全部信息styles、pages、assets 等是 Storage Manager 所有方法读写的最小单元。官方文档给出了手动获取与加载的方式// 获取当前项目数据 const projectData editor.getProjectData(); // ... // 加载项目数据 editor.loadProjectData(projectData);从 editor/index.ts 的源码可以看到getProjectData()实际调用em.storeData()loadProjectData(data)实际调用em.loadData(data)两者是编辑器对外暴露的高层封装。为什么只依赖 JSON而不是 HTML/CSS文档特别强调了一个danger级别的注意事项你只应依赖 JSON 项目数据来正确加载项目。编辑器确实能解析 HTML/CSS 代码你可以把 HTML/CSS 作为项目初始化的输入但永远不要把它当作加载项目的持久化层——因为大量信息会在转换中被剥离。原因很直观HTML/CSS 只是组件树渲染后的“快照”组件类型、自定义属性、数据绑定、样式规则元信息等都会丢失。因此官方推荐的做法是HTML/CSS 仅用于“导入已有模板”这种一次性初始化一旦用户开始编辑就只依赖getProjectData()返回的 JSON。存储策略自动保存的触发与手动控制项目数据会在变更计数达到阈值时自动保存。具体规则Storage.md每次变更使editor.getDirtyCount()递增当getDirtyCount()达到editor.Storage.getStepsBeforeSave()时触发保存保存成功后调用editor.clearDirtyCount()重置计数器。对应源码位于 editor/model/Editor.ts 的updateChanges方法当stm.isAutosave() changes stm.getStepsBeforeSave()成立时会调用this.store()并把异常交给logError兜底同时结合noticeOnUnload决定是否在卸载前弹出未保存提示。需要时也可以随时手动触发// 手动保存 const storedProjectData await editor.store(); // 手动加载 const loadedProjectData await editor.load();editor.store()成功后会重置变更计数见 editor/index.ts 的注释与实现。恢复策略recovery远程失败时的本地兜底在远程存储模式下可以在配置中开启recovery当远程保存失败时数据会被写入local存储每次远程保存成功后本地副本会被清除。若之后远程加载失败且本地存在恢复数据编辑器会弹出恢复确认对话框允许用户找回上次的数据。源码层面由 storage_manager/index.ts 实现store()的 try/catch 分支决定失败时是否写入getRecoveryStorage()即localload()会先读取 recovery 数据再通过__askRecovery()弹出默认confirm或调用自定义回调。recovery可配置为布尔值或函数storageManager: { type: remote, // 启用默认确认对话框 recovery: true, // 或自定义对话框 recovery: (accept, cancel, editor) { confirm(恢复数据) ? accept() : cancel(); }, }实战一配置本地存储localStorageGrapesJS 默认使用内置的local存储其底层就是浏览器localStorageAPI。对大多数应用来说唯一需要关心的选项是存储键key——如果你的应用允许用户加载不同项目就必须用项目 ID 区分存储键避免互相覆盖// 获取你的项目 ID例如从路由中取得 const projectId getProjectId(); const editor grapesjs.init({ // ... storageManager: { type: local, options: { local: { key: gjsProject-${projectId} } } }, });对应实现见 model/LocalStorage.tsstore用localStorage.setItem(key, JSON.stringify(data))写入load用JSON.parse(localStorage.getItem(key) || {})读取并在checkLocal开启时对不支持localStorage的环境抛出异常。实战二配置远程存储REST API多数生产场景要求把项目数据保存到服务端数据库、文件等因此需要配置服务端 API 来对接 store/load 调用。用 json-server 搭建本地假 API为了演示方便文档建议用 json-server 搭建一个假 REST 服务mkdir my-server cd my-server npm init npm i json-server echo {projects: [ {id: 1, data: {assets: [], styles: [], pages: [{component: divInitial content/div}]} } ]} db.json npx json-server --watch db.json这会启动一个本地服务在http://localhost:3000/projects/1暴露一个项目所有数据更新都会写回db.json。远程存储配置示例const projectID 1; const projectEndpoint http://localhost:3000/projects/${projectID}; const editor grapesjs.init({ // ... storageManager: { type: remote, stepsBeforeSave: 3, options: { remote: { urlLoad: projectEndpoint, urlStore: projectEndpoint, // remote 存储保存时默认使用 POST而 json-server 要求 PATCH fetchOptions: opts (opts.method POST ? { method: PATCH } : {}), // API 以 {id: 1, data: projectData} 格式存放项目 // 因此保存前要改写 body加载后要从响应中提取 data onStore: data ({ id: projectID, data }), onLoad: result result.data, } } } });服务端约定Server setup服务端实现因业务而异但内置remote存储遵循简单的 REST 约定项目数据以 JSONContent-Type: application/json交换加载GET响应中应直接返回 JSON 项目数据若响应包含其他元数据用options.remote.onLoad提取保存POST编辑器不关心响应内容只要求服务端返回合法响应状态码200。底层请求构造逻辑见 model/RemoteStorage.ts 的__props方法默认携带X-Requested-With: XMLHttpRequest与Content-Type: application/json; charsetutf-8两个请求头有数据时用POST并序列化 body无数据时用GETfetchOptions可以是对象也可以是接收当前RequestInit并返回新选项的函数上面的 PATCH 改写正是利用了这一点。警告务必在服务端正确配置 CORS。json-server 仅用于开发演示它会默认放开所有跨域限制不能用于生产环境。Storage API定义、扩展与替换存储Storage Manager 模块提供了一整套 API完整列表见 storage_manager 的 API 文档核心是editor.Storage上的add/get/getStorageOptions/setCurrent等方法。所有存储实现都必须满足 model/IStorage.ts 定义的接口interface IStorageT extends StorageOptions {} { load: (options: T) PromiseProjectData; store: (data: ProjectData, options: T) Promiseany; }即两个异步方法load(options)读取并返回项目数据store(data, options)写入项目数据。定义一个新的存储定义新存储只需向editor.Storage.add传入两个异步方法。下面的示例基于sessionStorageAPI 实现了一个session存储const sessionStoragePlugin (editor) { // sessionStorage 不是异步 API这里 async 关键字可以省略 editor.Storage.add(session, { async load(options {}) { return JSON.parse(sessionStorage.getItem(options.key)); }, async store(data, options {}) { sessionStorage.setItem(options.key, JSON.stringify(data)); } }); }; const editor grapesjs.init({ // ... plugins: [sessionStoragePlugin], storageManager: { type: session, options: { session: { key: myKey } } }, });注意add方法storage_manager/index.ts返回this以支持链式调用getStorageOptions(session)会从config.options.session取出{ key: myKey }传给上面两个方法。扩展组合多个存储如果需要组合已有存储实现更复杂的逻辑可以用Storage.get()取出既有存储并自行编排。例如“远程失败时回退到本地”的双写存储const { Storage } editor; Storage.add(remote-local, { async store(data) { const remoteStorage Storage.get(remote); try { await remoteStorage.store(data, Storage.getStorageOptions(remote)); } catch (err) { // 远程出错时数据保存到本地 const localStorage Storage.get(local); await localStorage.store(data, Storage.getStorageOptions(local)); } }, async load() { // ... }, });这里Storage.getStorageOptions(remote)负责把options.remote中的配置注入到存储方法的第二个参数与模块内部__exec的调用方式一致见 index.ts。替换内置存储向Storage.add传入与内置存储相同的类型名即可覆盖原实现。例如把默认基于localStorage的local换成可扩展性更好的IndexedDB或者用你熟悉的 HTTP 客户端如 axios由它统一处理 CSRF token、会话等请求头替换默认remoteeditor.Storage.add(remote, { async load() { return await axios.get(projects/${projectId}); }, async store(data) { return await axios.patch(projects/${projectId}, { data }); }, });替换后模块内部通过getCurrentStorage()获取当前存储并调用其load/store对上层完全透明。常见使用场景场景一跳过初始化时的远程加载使用remote存储时如果项目数据已在服务端渲染阶段拿到例如打印在 HTML 中可以给grapesjs.init直接传入projectData从而跳过初始远程加载// 在初始化编辑器之前获取数据例如服务端打印出来 const projectData {...}; // ... grapesjs.init({ // ... // 未定义 projectData 时可提供初始数据 projectData: projectData || { pages: [ { component: div classtestInitial content/div style.test { color: red }/style } ] }, storageManager: { type: remote, // ... }, })一旦projectData被定义初始化时的存储加载会被自动跳过。该配置项定义于 editor/config/config.ts默认值为undefined。场景二保存项目数据的同时输出 HTML/CSS项目数据本身不包含页面 HTML/CSS——它的职责是收集还原编辑器所需的必要信息。如果你还有强需求要在保存时联动执行其他逻辑例如把 HTML/CSS 结果部署到测试环境可以通过远程配置的onStore对保存调用做“增强”grapesjs.init({ // ... storageManager: { type: remote, options: { remote: { // 增强保存调用 onStore: (data, editor) { const pagesHtml editor.Pages.getAll().map((page) { const component page.getMainComponent(); return { html: editor.getHtml({ component }), css: editor.getCss({ component }), }; }); return { id: projectID, data, pagesHtml }; }, // 加载时仍只取项目数据部分 onLoad: (result) result.data, }, }, }, });onStore的签名(data, editor)与模块级storageManager.onStore一致见 RemoteStorage.ts两者在__exec中会依次执行先执行存储级onStore再执行模块级onStore。场景三无服务端的内联存储表单读写如果编辑器不需要连接任何后端只是把数据读写到表单的隐藏输入框中可以自定义一个inline存储form idmy-form input idproject-html typehidden / input idproject-data typehidden value{pages: [{component: divInitial content/div}]} / div idgjs/div button typesubmitSubmit/button /form script // 提交时展示数据 document.getElementById(my-form).addEventListener(submit, (event) { event.preventDefault(); const projectDataEl document.getElementById(project-data); const projectHtmlEl document.getElementById(project-html); alert(HTML: ${projectHtmlEl.value}\n------\nDATA: ${projectDataEl.value}); }); // 内联存储 const inlineStorage (editor) { const projectDataEl document.getElementById(project-data); const projectHtmlEl document.getElementById(project-html); editor.Storage.add(inline, { load() { return JSON.parse(projectDataEl.value || {}); }, store(data) { const component editor.Pages.getSelected().getMainComponent(); projectDataEl.value JSON.stringify(data); projectHtmlEl.value html head style${editor.getCss({ component })}/style /head ${editor.getHtml({ component })} html; }, }); }; // 初始化编辑器 grapesjs.init({ container: #gjs, height: 500px, plugins: [inlineStorage], storageManager: { type: inline }, }); /script这个例子用两个隐藏输入框分别存放项目 JSON 数据与渲染后的 HTML/CSS前者随表单提交给服务端做持久化后者可用于导出静态页面。由于load/store都是同步的这里省略了async关键字完全符合IStorage接口的约束。事件EventsStorage Manager 在存储流程的关键节点会触发事件便于你扩展业务逻辑如埋点、UI 状态、错误上报。全部事件定义于 storage_manager/types.ts分发逻辑见 index.ts 中的 onStart/onAfter/onEnd/onError事件触发时机回调参数storage:start存储请求开始(type, data)storage:start:store保存请求开始可修改传入的项目 JSON(data)storage:start:load加载请求开始(data)storage:load项目加载完成(data, res)storage:store项目保存完成(data, res)storage:after请求完成紧随storage:load/storage:store之后触发(type)storage:after:store保存完成after 阶段(data, res)storage:after:load加载完成after 阶段(data, res)storage:end请求结束出错时也会触发(type, data, res?)storage:end:store保存结束(data, res?)storage:end:load加载结束(data, res?)storage:error存储请求出错(err, type)storage:error:store保存出错(err)storage:error:load加载出错(err)典型用法editor.on(storage:start, (type) { console.log(Storage start); }); editor.on(storage:error, (err, type) { console.log(Storage error, err, type); });总结GrapesJS 的 Storage Manager 把“项目数据”与“存储介质”解耦local和remote只是默认实现任何满足{ load, store }接口的对象都能通过editor.Storage.add()接入。实战中建议记住三条核心原则持久化只依赖 JSON 项目数据getProjectData()/loadProjectData()HTML/CSS 仅用于一次性模板导入远程场景下合理调大stepsBeforeSave以减少网络请求并优先开启recovery做本地兜底用projectData初始化跳过首屏远程加载用onStore/onLoad对齐服务端 API 结构用storage:*事件感知完整的读写生命周期。如需进一步了解editor.Storage的完整方法签名add、get、getStorageOptions、setCurrent、store、load等可查阅 Storage Manager API 文档 与 storage_manager 模块源码相关的行为断言默认local存储、stepsBeforeSave默认 1、自定义存储注册等可在 storage_manager 测试用例 中找到对应验证。【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表