当前位置: 首页 > news >正文

Canvas简历编辑器-Monorepo+Rspack工程实践

Canvas简历编辑器-Monorepo+Rspack工程实践

在之前我们围绕Canvas聊了很多代码设计层面的东西,在这里我们聊一下工程实践。在之前的文中我也提到过,因为是本着学习的态度以及对技术的好奇心来做的,所以除了一些工具类的库例如 ArcoDesignResizeObserveJest 等包之外,关于 数据结构packages/delta、插件化packages/plugin、核心引擎packages/core 等都是手动实现的,所以在这里除了学习了Canvas之外,实际上还做了一些项目工程化的实践。

  • 在线编辑: https://windrunnermax.github.io/CanvasEditor
  • 开源地址: https://github.com/WindrunnerMax/CanvasEditor

关于Canvas简历编辑器项目的相关文章:

  • 掘金老给我推Canvas,我也学习Canvas做了个简历编辑器
  • Canvas图形编辑器-数据结构与History(undo/redo)
  • Canvas图形编辑器-我的剪贴板里究竟有什么数据
  • Canvas简历编辑器-图形绘制与状态管理(轻量级DOM)
  • Canvas简历编辑器-Monorepo+Rspack工程实践
  • Canvas简历编辑器-层级渲染与事件管理能力设计
  • Canvas简历编辑器-选中绘制与拖拽多选交互方案

Pnpm+Monorepo

我们先来聊聊为什么要用monorepo,先举一个我之前踩过的坑作为例子,在之前我的富文本编辑器项目 DocEditor 就是完全写在了独立的单个src目录中,在项目本身的运行过程中是没什么问题的,但是当时我想将编辑器独立出来作为NPM包用,打包的过程是借助了Rollup也没什么问题,问题就出在了引用方上。当时我在简历编辑器中引入文档编辑器的NPM包时,发现有一个模块被错误的TreeShaking了,现在都还能在编辑器中看到这部分兼容。

module: {rules: [{// 对`doc-editor-light`的`TreeShaking`有点问题test: /doc-editor-light\/dist\/tslib.*\.js/,sideEffects: true,},]
}

这个问题导致了我在dev模式下没有什么问题,但是在build之后这部分代码被错误地移除掉了,导致编辑器的wrapper节点出现了问题,列表等元素不能正确添加。当然实际上这不能说明独立包项目不好,只能说整个管理的时候可能并不是那么简单,尤其是打包为NPM包的时候需要注意各个入口问题。那么现在引用我的富文本编辑器包已经变成了4个独立的包分别引用,各司其职,就没再出现过这个问题。

说起来打包的问题,我还踩过一个坑,不知道大家是不是见到过ReactInvalid hook call这个经典报错。之前我将其独立拆包的时候之后,发现会报这个错,但是我在package.json中是标注的peerDependencies "react": ">=16",按理说这里会直接应用安装该包的React,不可能出现版本不一致的问题,至于Rules of Hooks肯定也不可能,因为我之前是好好的,拆完包才出的问题。最后发现是我在rollup中没把peerDependencies这部分解析,导致jsx-runtime被打进了包里,虽然React的版本都是17.0.2但是实际上是运行了两个独立词法作用域的React Hooks,这才导致了这个问题。

Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:1.  You might have mismatching versions of React and the renderer (such as React DOM)
2.  You might be breaking the Rules of Hooks
3.  You might have more than one copy of React in the same app See for tips about how to debug and fix this problem.

接着回到项目本身,当前项目已经抽离出来独立的RspackMonoTemplate,平时开发也会基于这个模版创建仓库。当前简历编辑器项目的结构tree -L 2 -I node_modules --dirsfirst如下:

CanvasEditor
│── packages
│   ├── core
│   ├── delta
│   ├── plugin
│   ├── react
│   └── utils
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── tsconfig.json
  • packages/core: 编辑器核心引擎模块,对于 剪贴板操作、事件管理、状态管理、History模块、Canvas操作、选区操作 等等都封装在这里,相当于实现了基本的Canvas引擎能力。
  • packages/delta: 数据结构模块,设计了基准数据结构,实现了DeltaSet数据结构以及原子化的Op操作,主要用于描述整个编辑器的数据结构以及操作,实现了invert等能力,对于实现History模块有很大的意义。
  • packages/plugin: 插件模块,在packages/delta的基础上设计了插件化的能力,主要为了实现编辑器的功能模块化,例如TextImageRect等插件都是在这里实现的。
  • packages/react: React模块,主要是为了通过实现编辑器的视图层,在这里有比较重要的一点,我们的核心模块是视图框架无关的,如果有必要的话同样可以使用VueAngular等框架来实现视图层。
  • packages/utils: 工具模块,主要是一些工具函数的封装,例如FixedNumberPalette等等,这些工具函数在整个编辑器中都有使用,是作为基础包在整个workspace中引用的。
  • package.json: 整个workspacepackage.json,在这里配置了一些项目的信息,EsLintStyleLint相关的配置也都在这里实现。
  • pnpm-lock.yaml: pnpm的锁文件,用于锁定整个workspace的依赖版本。
  • pnpm-workspace.yaml: pnpmworkspace配置文件,用于配置monorepo的能力。
  • tsconfig.json: 整个workspacetsconfig配置文件,用于配置整个workspaceTypeScript编译配置,在这里是作为基准配置以提供给项目中的模块引用。

pnpm自身是非常优秀的包管理器,通过硬链接和符号链接来节省磁盘空间,每个版本的包只需要存储一次,最重要的是pnpm创建了一个非扁平化的node_modules结构,从而确保依赖与声明严格匹配,严格控制了依赖提升,能够避免依赖升级的意外问题,这提高了项目的一致性和可预测性。

而说回到monorepopnpm不光是非常优秀的包管理器,其还提供了一个开箱即用的monorepo能力。在pnpm中存在一个pnpm-workspace.yaml文件,这个文件是用来配置workspack的,而pnpmworkspace就可以作为monorepo的能力,而我们的配置也非常简单,我们认为在packages目录下的所有目录都作为子项目。

packages:- 'packages/*'

通过monorepo我们可以很方便的管理所有子项目,特别是对于需要发Npm包的项目,将子模块拆分是个不错的选择,特别如果能够做到视图层框架无关的话就显得更加有意义。此外,monorepo对于整个项目的管理也有很多益处,例如在打包整个应用的时候,我们不需要对每个子项目发新的包之后才能打包,而是可以直接将编译过程放在workspace层面,这样就可以保证整个项目的一致性,简化了构建过程和持续集成流程,让所有项目可以共享构建脚本和工具配置。此外所有项目和模块共用同一个版本控制系统,便于进行统一的版本管理和变更跟踪,而且还有助于同步更新这些项目间的依赖关系。

TS+Rspack最佳实践

说了这么多使用pnpm + monorepo管理项目带来的好处,我们再来聊聊我对TSRspack应用于Monorepo的最佳实践,不知道大家是不是遇到过这样的两个问题:

  • 子项目的TS声明更改后不能实时生效,必须要编译一次子项目才可以,而子项目编译的过程中如果将dist等产物包删除,那么在vsc或者其他编辑器中就会报TS找不到引用声明的错误,这个时候就必须要用命令重新Reload TypeScript Project来去掉报错。而如果不将产物包删除的话,就会出现一些隐性的问题,例如原来某个文件命名为a.tsx,此时因为一些原因需要将其移动到同名的a目录并且重新命名为index.tsx,那么执行了这一顿操作之后,发现如果更改此时的index.tsx代码不会更新,必须要重启应用的webpack等编译器才行,因为其还是引用了原来的文件,产生类似的问题虽然不复杂但是排查起来还是需要时间的。
  • 更改子项目的TS代码必须要重新编译子项目,因为项目是monorepo管理的,在package.json中会有workspace引用,而workspace实际上是在node_modules被引用的,所以虽然是子项目但是仍然需要遵循node_modules的规则才可以,那么其通常需要被编译为js才可以被执行,所以每次修改代码都必须要全量执行一遍很是麻烦,当然通常我们可以通过-w命令来观察变动,但是毕竟多了一道步骤,且如果是存在alias的项目可能仅仅使用tsc来编译还不够。此外在monorepo中我们通常会有很多子项目,如果每个子项目都需要这样的话,特别在这种编译时全量编译而不是增量编译的情况下,那么整个项目的编译时间就会变得非常长。

那么在这里我们先来看第一个问题,子项目的TS声明更改后不能实时生效,因为我们也提到了monorepo子项目实际上是通过node_modules来管理和引用的,所以其在默认情况下依然需要遵循node_modules的规则,即packages.jsontypes字段指向的TS声明文件,那么我们有没有什么办法可以修改这个行为呢,当然是有的,我们在整个项目的根tsconfig.json配置path就可以完美解决这个问题。当我们配置好如下的内容之后,通过按住Ctrl加鼠标左键点击的时候,就可以跳转到子项目的根目录声明了。此外这里有个要关注的点是,在项目中不建议配置"baseUrl": ".",在这里会有一些奇奇怪怪的路径引用问题,所以在简历编辑器项目中除了要打包Npmtsconfig.build.json之外,都是直接使用相对路径配置的。

{"compilerOptions": {"...": "...","paths": {"sketching-core": ["./packages/core/src"],"sketching-delta": ["./packages/delta/src"],"sketching-plugin": ["./packages/plugin/src"],"sketching-utils": ["./packages/utils/src"],},},"include": ["packages/*/src"]
}

那么解决了项目的TS声明问题之后,我们再来看编译的问题,这里的问题看起来会复杂一些,因为TS声明就单纯只是类型声明而已,不会影响到项目本身代码的编译,编译类型检查除外。那么在Rspack中应该配置才能让我们的代码直接指向子项目,而不是必须要走node_modules这套规则,实际上这里也很简单,只需要配置resolve.alias就可以了,这样当我们直接修改TS代码时,也能让编辑器立即响应增量编译。

{
// ....resolve: {alias: {"@": path.resolve(__dirname, "./src"),"sketching-core": path.resolve(__dirname, "../core/src"),"sketching-delta": path.resolve(__dirname, "../delta/src"),"sketching-plugin": path.resolve(__dirname, "../plugin/src"),"sketching-utils": path.resolve(__dirname, "../utils/src"),},},
// ....
}

实际上对于Rspack而言其帮我们做了很多事,比如即使是node_modulesTS文件也会编译,而对于一些通过CRA创建的webpack项目来说,这个配置就麻烦一些,当然我们同样也可以借助customize-cra来完成这件事,此外我们还要关闭一些类似于ModuleScopePlugin的插件才可以,下面是富文本编辑器项目 DocEditor 的配置。

const src = path.resolve(__dirname, "src");
const index = path.resolve(__dirname, "src/index.tsx");
const core = path.resolve(__dirname, "../core/src");
const delta = path.resolve(__dirname, "../delta/src");
const plugin = path.resolve(__dirname, "../plugin/src");
const utils = path.resolve(__dirname, "../utils/src");module.exports = {paths: function (paths) {paths.appSrc = src;paths.appIndexJs = index;return paths;},webpack: override(...[// ...addWebpackResolve({alias: {"doc-editor-core": core,"doc-editor-delta": delta,"doc-editor-plugin": plugin,"doc-editor-utils": utils,},}),babelInclude([src, core, delta, plugin, utils]),// ...configWebpackPlugins(),].filter(Boolean)),
};

此外,简历编辑器是纯前端的项目,这样的项目有个很大的优势是可以直接使用静态资源就可以运行,而如果我们借助GitHub Action就可以通过Git Pages在仓库中直接部署,并且可以直接通过GitHub Pages访问,这样在仓库中就能呈现一个完整的DEMO

// .github/workflows/deploy.yml
name: deploy gh-pageson:push:branches:- masterjobs:build-and-deploy:runs-on: ubuntu-lateststeps:- name: checkoutuses: actions/checkout@v2with:fetch-depth: 0persist-credentials: false- name: install node-v16uses: actions/setup-node@v3with:node-version: '16.16.0'- name: install dependenciesrun: |node -vnpm install -g pnpmpnpm config set registry https://registry.npmjs.org/pnpm install --registry=https://registry.npmjs.org/- name: build projectrun: |npm run build:react- name: deploy projectuses: JamesIves/github-pages-deploy-action@releases/v3with:GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}BRANCH: gh-pagesFOLDER: packages/react/build

最后

在这里我们聊了为什么要用Monorepo以及简单聊了一下pnpm workspace的优势,然后解决了在子项目开发中会遇到的TS编译、项目编译的两个实际问题,分别在MonorepoRspackWebpack项目中相关的部分实践了一下,最后还简单聊了一下利用GitHub Action直接在Git Pages部署在线DEMO。那么再往后边的文章中,我们就需要聊一聊如何实现 层级渲染与事件管理 的能力设计。

相关文章:

Canvas简历编辑器-Monorepo+Rspack工程实践

Canvas简历编辑器-MonorepoRspack工程实践 在之前我们围绕Canvas聊了很多代码设计层面的东西,在这里我们聊一下工程实践。在之前的文中我也提到过,因为是本着学习的态度以及对技术的好奇心来做的,所以除了一些工具类的库例如 ArcoDesign、Re…...

uni-app - - - - -vue3使用i18n配置国际化语言

uni-app - - - - -使用i18n配置国际化语言 1. 安装vue-i18n2. 配置文件2.1 创建如下文件2.2 文件配置2.3 main文件导入i18n 3. 页面内使用3.1 template内直接使用3.2 变量接收使用 1. 安装vue-i18n npm install vue-i18n --save2. 配置文件 2.1 创建如下文件 locales文件夹里…...

VSCode好用的插件推荐

1. Chinese 将vscode翻译成简体中文 如果安装了依然是英文,请参考如下方法: ctrlshfitp 2. ESLint 自动检查规范 3. Prettier - Code formatter 可以自动调整代码的缩进、换行和空格,确保代码风格统一。通过配置,Prettier可…...

Linux:八种重定向详解(万字长文警告)

相关阅读Linuxhttps://blog.csdn.net/weixin_45791458/category_12234591.html?spm1001.2014.3001.5482 本文将讨论Linux中的重定向相关问题,在阅读本文前,强烈建议先学习文件描述符的相关内容Linux:文件描述符详解。 重定向分为两类&#x…...

set和map系列容器

前言 学习完二叉搜索树本来是应该直接深化,讲平衡二叉搜索树的。但是在学习它的底层逻辑之前呢,我们先来学学它的应用场面。 set和map的底层不是平衡二叉搜索树而是红黑树,实际上的难度比平衡搜索二叉树大。所以它的底层逻辑会比平衡二叉树更…...

企业告警智策助手 | OPENAIGC开发者大赛企业组AI创作力奖

在第二届拯救者杯OPENAIGC开发者大赛中,涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到,我们特意开设了优秀作品报道专栏,旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者,希望能带给…...

函数组件、Hooks和类组件区别

1. 函数组件(Function Components) 函数组件是接收props并返回React元素的纯JavaScript函数。它们不能拥有自己的状态(state)或生命周期方法,但在React 16.8中引入Hooks之后,这种情况发生了变化。 特点&a…...

在线点餐新体验:Spring Boot 点餐系统

摘 要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势;对于网上点餐系统当然也不能排除在外,随着网络技术的不断成熟,带动了网上点餐系统,它彻底改变了过去传统的…...

WPF中Viewbox的介绍和用法

在 WPF(Windows Presentation Foundation) 中,Viewbox 是一个非常有用的容器控件,主要用于根据其自身大小自动调整子元素的缩放比例,以保持其内容的显示效果。无论窗口如何调整大小,Viewbox 内的内容都会按…...

QMT如何获取股票基本信息?如上市时间、退市时间、代码、名称、是否是ST等。QMT量化软件支持!

获取股票概况 包含股票的上市时间、退市时间、代码、名称、是否是ST等。 #获取合约基础信息数据 该信息每交易日9点更新 #内置Python 提示 旧版本客户端中,函数名为ContextInfo.get_instrumentdetail 调用方法 内置python ContextInfo.get_instrument_detai…...

2024年中国科技核心期刊目录(科普卷)

2024年中国科技核心期刊目录 (科普卷) 序号 期刊名称 1 爱上机器人 2 百科知识 3 保健医…...

[解决]navicat连接mysql成功,但是使用jdbc连接不上

在连接数据库时,最初使用的 JDBC URL 配置如下: jdbc:mysql://192.168.56.100:3306/mzxLiving_manage?useUnicodetrue&characterEncodingUTF-8&serverTimezoneAsia/Shanghai修改之后的JDBC URL为 jdbc:mysql://192.168.56.100:3306/mzxLiving…...

sar信号RD域的距离向傅里叶变换

下面可知,举例傅里叶变换时,posp 距离时间和频率 t不等于ft/K。而方位时间和频率时这种线性关系...

4 html5 web components原生组件详细教程

web components 前面我们已经介绍过,这一期我们就来讲一讲具体用法和这其中的关键只是点: 1 基本使用 如果我们想实现一个封装的原生组件,那就离不开使用js去封装,这里主要就是基于HTMLElement这个类,去创建创建一个…...

nginx+keepalived健康检查案例详解(解决nginx出现故障却不能快速切换到备份服务器的问题)

文章目录 简介配置过程前置环境请看创建健康检查脚本结果测试 简介 在我们通过nginxkeepalived实现高可用后,会发现nginx出现故障的时候keepalived并不会将虚拟ip切换到备份服务器上其原理就是nginx和keepalived是两个独立的服务,Nginx的故障状态不会触…...

什么是AI大模型?

什么是AI大模型? 人工智能(AI)大模型近年来在各个领域掀起了一场技术革命,从语言生成到图像识别,再到自动驾驶和医疗诊断,AI大模型的应用场景越来越广泛。这些模型的表现令人惊叹,但它们的工作原理和背后技…...

建造者模式__c#

目录 调用 指挥者 抽象建造者 建造者 定义具体产品 调用 用指挥者指挥建造者建造产品 在指挥者这里组装成产品 namespace _建造者模式 {internal class Program{static void Main(string[] args){Builder buildernew JiangHuaiBuilder();//建造者Director director new…...

学习MRI处理过程中搜到的宝藏网站

今天浏览网页查到了一些宝藏网站,正好记录一下,后面搜到好东东再接着填充,方便查阅~ (1)牛人网站 这个网站是在搜集seed关键词时发现的,用pdf文档记录,可下载查阅,条理清晰&#xf…...

【C语言】const char*强制类型转换 (type cast)的告警问题

void run_upload(const char *ftp_url) {CircularQueue queue;// 初始化环形队列for (int i = 0; i < QUEUE_SIZE; i++) {queue.items[i].data = malloc(BUFFER_SIZE);if (queue.items[i].data == NULL) {fprintf(stderr, "Failed to allocate memory for queue item %…...

python-比较月亮大小/数组下标/人见人爱a+b

一:比较月亮大小 题目描述 小理是一名出色的狼人。众所周知&#xff0c;狼人只有在满月之夜才会变成狼。 同时&#xff0c;月亮的大小随着时间变化&#xff0c;它的大小变化 3030 天为一循环。 它的变化情况(从第一天开始)为 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,14,13,12,1…...

什么是组态、组态的应用场景介绍

随着计算机技术和工业自动化水平迅速提高&#xff0c;而车间现场种类繁杂的控制设备和过程监控装置使得传统的工业控制软件无法满足用户的各种需求。在“组态”概念出现之前&#xff0c;工程技术人员需要通过编写程序来实现某一任务&#xff0c;不但工作量大、周期长&#xff0…...

Java项目: 基于SpringBoot+mybatis+maven实现的智能推荐卫生健康系统分前后台(含源码+数据库+开题报告+任务书+毕业论文)

一、项目简介 本项目是一套基于SpringBootmybatismaven实现的智能推荐卫生健康系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美…...

本地生活商城开发搭建 同城O2O线上线下推广

同城本地化商城目前如火如荼&#xff0c;不少朋友咨询本地生活同城平台怎么开发&#xff0c;今天商淘云与大家分享同城O2O线上商城的设计和开发。 本地生活商城一般会涉及到区域以及频道类&#xff0c;一般下单需要支持用户定位、商家定位&#xff0c;这样利于用户可以快速找到…...

41. 如何在MyBatis-Plus中实现批量操作?批量插入和更新的最佳实践是什么?

在 MyBatis-Plus 中&#xff0c;实现批量操作&#xff08;如批量插入、批量更新&#xff09;是非常常见的需求。MyBatis-Plus 提供了对批量操作的良好支持&#xff0c;可以通过多种方式实现高效的批量处理。下面详细介绍批量操作的实现方式以及最佳实践。 1. 批量插入 批量插入…...

LlamaIndex 的Node节点后处理器模块介绍

Node 后处理器模块 LlamaIndex 是一个旨在连接大型语言模型&#xff08;LLMs&#xff09;与外部数据的框架&#xff0c;允许开发者构建能够处理和回应复杂查询的应用程序。在这个框架内&#xff0c;NodePostProcessor 扮演着优化数据处理流程的重要角色。为了更好地理解 NodeP…...

Dubbo 如何使用 Zookeeper 作为注册中心:原理、优势与实现详解

Dubbo 是一个高性能的 Java 分布式服务框架&#xff0c;而 Zookeeper 常被用作 Dubbo 的服务注册中心。Zookeeper 提供了分布式一致性和协调服务&#xff0c;Dubbo 通过 Zookeeper 实现服务注册与发现功能&#xff0c;确保在分布式环境下服务实例的动态管理和可靠发现。 下面是…...

Linux:进程间通信之命名管道

Linux&#xff1a;进程间通信-CSDN博客 我们说匿名管道只能用于父子进程这样的关系通信&#xff0c;那么陌生进程怎么通信&#xff1f; 我们之前说父子进程能通信的最关键的地方就在于子进程复制了一份父进程的files_struct&#xff0c;从而通过文件的inode映射同一份文件来通…...

UE4_后期处理七—仿红外线成像效果

效果图展示&#xff1a; 参考文档&#xff1a;https://dev.epicgames.com/documentation/zh-cn/unreal-engine/using-fresnel-in-your-unreal-engine-materials?application_version5.4 二、所用知识点扩充 在创建电影或过场动画时&#xff0c;你常常需要想办法更好地突显角…...

静态路由和默认路由(实验)

目录 一、实验设备和环境 1、实验设备 2、实验环境 &#xff08;1&#xff09;实验拓扑图 &#xff08;2&#xff09;实验命令列表 二、实验记录 1、直连路由与路由表查看 步骤1:建立物理连接并运行超级终端。 步骤2:在路由器上查看路由表。 2、静态路由配置 步骤1:配…...

TCP: Textual-based Class-aware Prompt tuning for Visual-Language Model

文章汇总 存在的问题 原文&#xff1a;具有图像特定知识的图像条件提示符号在提升类嵌入分布方面的能力较差。 个人理解&#xff1a;单纯把"a photo of {class}"这种提示模版作为输入是不利于text encoder学习的 动机 在可学习的提示和每一类的文本知识之间建立…...