🌋 WebIDE 的开发记录其一(前言和概览)
!本篇文章过于久远,其中观点和内容可能已经不准确,请见谅!~
先看效果演示:
在我刚开始入门的时候,当时用
CodeMirror
做了一个前端 Demo
系统,类似 JSFiddle
可以用原生的 html
、css
、js
保存展示代码,然后在博客内引入代码和效果,这是代码编辑器的最初版本。后来看到 vscode 的火热,突发奇想想搬到浏览器端,实现真正的云端开发环境。当时做这个的想法比较初始,还没有现在很多云服务商或者大公司内部提供的在线开发平台,毕竟当时浏览器下虽然有
Eclipse Che
,Cloud9
等方案,但是开发体验确实很差。而现在出现 theia
、code-server
非常优秀而且开源的项目,用户体验和本地开发几乎没差,再加上容器化技术非常成熟的现在,浏览器中随时展开开发环境的轻量级 IDE 已经成为现实了。可惜当时我准备开发的时候是 18 年下半年,这些东西都没有那么成熟或者发布,整个项目做下来,踩了很多坑,不过确实收获是巨大的。
而且其中很多用到的技术,正好当时参加了腾讯旗下
Coding
主办的 CloudIDE
插件比赛,开发了 9 个插件,在140+个插件中拿到了 3 个优胜一等奖和 5 个前五优秀奖,优胜插件甚至被官方内置,真是意外惊喜,还因此提供了好几次工作机会,虽然没去很遗憾。时间久远,当时的设计和想法没有集中整理,但是很值得记录,暂时想到的在此记录下!
还是包含了很多的功能的,编辑器、文件管理器、搜索、GIT、运行环境、终端等,算是比较完整的开发环境了,能做一点简单轻量的开发工作。
后台一个 server 负责承载接口,然后文件查看和操作、git的查看和操作和语言服务器都是统一的 server 负责,docker 中的环境负责提供运行环境和命令行。
刚开始的想法其实更想将文件、git、语言服务都放到 docker 中,但是如果这样每个容器内都要包含一套服务,这也就违背了轻量干净的初衷,所以服务器将这些剥离,容器看起来就是一个干净的容器环境挂载文件而已。
较大型的项目的系统和功能集成真的没有那么简单,整个项目异常复杂,每个功能、机制和界面都花了非常多的精力打磨,对于这类较大项目有很多的收获:
- Typescript
一个复杂大型项目必须要用 Typescript 不然会死人的。其中很多地方反复尝试实现,会涉及到很多次重构,比如一个数据模型的结构变化这样一个常见需求,如果没有类型提示,根本没办法追踪所有的 references 的,尤其是多人协作更是灾难。而使用 TypeScript 后,代码提示、类型限制等对效率的提升非常明显。
不好的地方是旧项目的升级,如果刚开始的时候没有用,后续的改造还是很花精力的,所以无论大小项目,强烈推荐 TS,并且打开
no-any
规则,经历过刚开始不停的定义类型的阵痛后,会非常爽,代码质量也会很自然的更高。- 可推断编程
不知道怎么描述这种模式,例如 this 在前端的历史上是一个很重要的概念,因为表意不明,不同的环境下要仔细分析可能才不会弄错,所以我现在尽量只在 class 里面使用;redux 概念的 action type 字符串类型,虽然可以用常量来避免拼写错误,但是用起来胆战心惊的;egg.js 中自动帮你挂载的路由之类的,我把这叫做不可推断编程。特点是编程工具很难静态的给你代码提示,没办法 goto definition,你需要记忆或者查询才知道自己写的对不对,这很容易造成运行时错误,尤其是重度依赖 typescript 的状态下,这简直像是鞋子里的石子那样硌脚。
约定大于配置是不错的开发模式,但是大型项目和多人协作的时候,约定还是不要太多。不可推断的部分需要做的改动就是将确定的字符串参数,变成显式的方法调用,虽然灵活性降低,但是不会出现运行时错误或者参数问题了
- 数据层
一个单独的数据层是非常重要的,现代前端技术已经可以分为数据和渲染两部分了,一个数据层决定了一个页面是什么样子。如果数据分散、与界面糅杂在一起,项目复杂熵就会越来越大到无法维护。而分离的项目只需要维护状态即可,UI 都甚至是可以重启的。
- 可扩展架构
一个项目可以预见的复杂,那么从最基础的建设开始就要考虑架构了,地基打的好,盖房没烦恼。就这个项目的基础部分刚开始就修改了三四个版本,从能用到可以随意添加扩展扩展功能,从写死糅杂到可以提供标准格式数据和统一的扩展加载调用机制,开发难度从垂直深入到水平扩展,难度降低。
事先预见项目的需求目标、功能需求,然后事先进行产品设计是一个成熟码农的标志,能让你从繁杂的踩坑之旅中找到能够缕清思路的点线。
- 成熟的轮子
设想的时候低估了整个项目复杂度,所以很多地方想自己实现,但是其实有很多坑,如果多花点时间梳理调研就会发现其实业内很多人趟过这条路,花点时间研究透彻比硬啃得收益还是大些的。不过另一方面拿来就用可能会出现很不优雅的实现,还是要考虑到实际,按需引入或者按需变更。
有了想法,然后简单搜索之后思路开阔很多,前端的技术栈完全能够实现云端代码的编辑。当时非常浅的看了 VSC 的一些源码,但是主要想看自己能实现到什么程度,所以并没有深入调研 VSC 的架构或者插件等,很多东西可能走了一些弯路。
第一版的想法是在服务器上制定一个工作目录,获取文件列表,用编辑器编辑修改后再保存,然后终端来实现其他更高级的功能。
用到的技术栈很简单为:
- monaco-editor 作为编辑器,支持多
Model
切换,代码高亮和简单的代码提示 - Xterm.js + node-pty +
websocket
作为前端终端的方案 - Egg.js 作为服务,简单指定文件夹,提供文件的增删改查 REST API,实现树形资源管理器
这一块实现起来比较简单,实际时间花费更多的是基础架构的实现,毕竟后续更多的功能搭建都是基于此。
期间实现了很多有趣的功能:
- 核心的页面布局和数据结构
- 采用
dva
的数据模型,封装数据岛,分模块提供数据状态,聚合数据的操作为单向数据流 - 采用 core + extensions 核心功能加扩展功能架构
- 核心功能使用标准的数据结构渲染出内容页面,提供 action 驱动机制和 UI 插槽
- 每个 extension 挂载独立的数据岛,注册数据和 UI 组件的统一机制
- 采用
- 灵活的视图层布局
- 视图层是提供功能的核心,刚开始想更灵活的自由划分区域,但是实现起来比较重量,而且没有必要,花了很多时间最后还是照着
VSCode
的样子实现了一个核心区域加三边辅助区域的样式,包括 layout -> stack -> tabs + panels 的层级架构。
- 视图层是提供功能的核心,刚开始想更灵活的自由划分区域,但是实现起来比较重量,而且没有必要,花了很多时间最后还是照着
- monaco-editor 的单实例多 model 实现,
- 这个本身其实很简单,editor 实例本身提供 model 和 state 的获取和设置,比较复杂的是一个 editor 是一个界面与提供的 layout/panels 机制不同,最后添加了一个多 tab 公用 panel 的特殊机制解决了。
这些内容中值得聊的都会在后面详说
实现了第一版之后发现很多问题,比如:
- 没办法多项目切换,工作目录只有一个
- 解决办法就是使用多目录的后台支持,前端加参数,后端分逻辑处理,这个不是问题
- 侵入性很强,一段时间后服务器就安装了很多开发工具、服务和依赖,作为服务器不应该有这些
- 解决方案只有一个,就是使用
docker
容器来将工作目录包含进来 - 然后使用
docker exec
接口接入websocket
连接 - 这样容器能提供终端,而且安装的服务和依赖不会影响外部服务器
- 解决方案只有一个,就是使用
此时技术栈:
- 实现一些
docker
开发镜像,通过docker api
管理运行环境,运行的时候挂载项目文件夹 - monaco-editor 作为编辑器,支持多
Model
切换,代码高亮和简单的代码提示 - Xterm.js +
websocket
+ dockerode.exec 作为终端的方案 - 使用 simple-git 获取/操作
git
功能
这个时候 添加了
docker
和 git
之后有了更多想象:dirty-diff
编辑器必备的一个功能,花了很多时间参考vscode
的实现,在monaco-editor
上添加了这个功能。docker
的运行环境,依靠这个干净独立功能,能够提供很多不同的workpad-runner
运行时镜像,比如内置了node
、php
或者Python
的快速开发环境,每次启动新的开发项目指定具体的镜像,这样不同项目可以有固定干净的开发环境了。
至此这个版本比较自洽了,各个模块比较和谐够用,后台实现的架构后续没有较大的改动了。
-> 在 monaco-editor 集成 VSC 中的 dirty-diff 功能可以查看: 🌋 WebIDE 的开发记录其七(DirtyDiff 支持)
第二版的集成花了点时间,实现之后功能确实不错,环境独立、管理方便、功能较全,但是还有不尽如意:
- 高亮的逻辑和预想不同
- 代码提示只有默认和当前文件的,整项目的代码提示做不到
- 没有调试功能
这一块进行了很多尝试,涉及到的算是比较深入,花的时间也很长:
语法和高亮部分
一番搜索发现
monaco-editor
的语言支持使用的是内置的 Monarch 这个语法高亮支持,语言的支持也只有通过 worker
的 js
ts
html
css
json
这些。但是业内更通用、生态更丰富的是 Textmate,包括 VSCode
也是用的 Textmate
。但是可喜的是 Textmate 语法的解析是用的 C 语言支持的,在 node 上尚且可以考虑,但是浏览器端只能另辟蹊径了。-> 详细可以查看后面文章 🌋 WebIDE 的开发记录其五(monaco-editor + textmate)
高级代码提示
一个 IDE 和编辑器很明显的使用区别就是
go to definition
和 go to references
,也就是跳转定义和引用跳转,缺少了这个功能,那么和最简单的记事本有什么区别。可是 monaco-editor 作为一个没有文件系统支持的工具,能做的很有限,最多也只是支持内置 addExtraLib
的概念解析依赖的文件内容。但是功能羸弱,而且没办法扩展到其他的语言中去。不过好在这个领域已经有了通用的解决方案了:LSP
,这个才是一个编辑器核心的东西,这个时候回想之前的考虑真的是太年轻。 -> 什么是
LSP
以及怎么连接语言服务,详细可以查看后面文章 🌋 WebIDE 的开发记录其六(LSP 支持)调试功能
这个和
LSP
的逻辑是相同的,也是有统一标准 DVA
,不过这个暂时还没有实现,后续如果有机会补上。其他功能包括客户端的技术架构、某个技术栈后面再单独文章讲讲。
感谢您的阅读,本文由 Ubug 版权所有。如若转载,请注明出处:Ubug(https://ubug.io/blog/workpad-part-1)