站在巨人的肩膀上--用VUE3试试搞个在线IDE吧!

语言: CN / TW / HK

前言

单位近日难的清闲

然,生那受苦的命,闲不住啊,领下军令状,重构单位单位的组件库使用的在线代码编辑IDE

在尝试重构之前,但是使用的是 CodeSandbox 魔改版本

说白了就是给这个开源项目改点字和接口,基本原封不动的搬过来,这样一来导致几个问题

  • 1、拓展费劲,有新功能加入时,开源的这个编辑器晦涩难懂,无法下手
  • 2、项目体积过大,报错较多,还不知缘由,项目体量更是巨大,启动修改困难,而且无用代码较多
  • 3、bug更改困难,定位问题费劲,开发效率奇底

针对以上原因,就一拍脑袋,领下军令状,

这一领坏了,没有提前做调研,殊不知困难重重,几乎猝死

自己说的话含着泪也要干完,这就是男人,一个吐沫一个钉

image.png

今天版本1.0 也算完成,写个文章记录实现思路,以慰我这累掉的几百根头发,

也为后来人提供一个实现类似需求的借鉴思路,不能说是最佳实践,但是也算是有一个能跑就行(要不我跑,要不代码跑)

更为了告诫大家,没事不要瞎折腾,躺平,摆烂把钱赚也挺好

前期调研

相信大家干一个事情之前都是雄心壮志,更是踌躇满志

me to 我也一样,在刚开始的时候,我一看这功能,这有啥难的,重写一个就完事了

于是我就开始撸codesandbox-client的源码

在这里先简单的介绍一下这个玩意

这是一个浏览器端的沙盒运行环境,支持多种流行的构建模板,例如 create-react-app、 vue-cli、parcel等等

这就是一个在浏览器实现了一个编辑器,加打包器,再加渲染器

就是vscode + webpack + 浏览器

到这,我就知道,这项目不是那么简单,直到我查到,这个项目是一群人,用时四年干出来的

我勒个去,我瞬间石化了

image.png

我的满腔热血,凉了半截,但是军令状背了,代码不跑,我就得跑啊! 干!!!!!

撸了三天的源码,梳理了一下源码中整体的脉络

  • 1、核心代码为react开发
  • 2、编辑器部分使用monaco-editor
  • 3、包含独立的浏览器打包渲染包sandbox (可以抄)
  • 4、使用lerna构建整个项目但是整体分包不是很明确,可读性差(也可能是我水平不行)
  • 5、自己实现文件系统
  • 6、ui组件风格自己实现
  • 7、Packager 包管理实现自己实现
  • 8、视图展示层使用iframe,并且和编辑器和文件系统之间使用postMessage通信,实现响应式
  • 9、服务端CodeSandbox 自己搭建了一套,用于存储用户信息,以及模板信息
  • 10、源码中包含了大量的编译器,比如vue3编译器等

行动方案

有了这么些,预备资料,我们就可以将真个系统的开发分为三步走策略

首先他真个在线IDE我们可以分为五大块

  • 1、文件系统
  • 2、编辑器
  • 3、渲染器
  • 4、ui呈现
  • 5、通用数据结构设计

文件系统

接下来我们一步步解决首先文件系统,所谓文件系统,在呈现方面来说,就是个树形列表,由于,源码中的react 移植,奈何代码逻辑山路十八弯,算了,准备使用 element-uitree组件代替

然,总是差点意思,干脆自己来吧! 借鉴了一个vue2的库--vue-tree-list将他移植到了vue3上

他的原理其实也很简单,主要就是递归当前组件,这里遇见一个问题,就是v-bind="$attrs" 失效问题

用过$attrs 的都知道,在vue3中 $attrs 可以很方便的做到属性以及事件的透传,如此一来,就能避免中间承上启下的组件的代码复杂度。

我们来看个例子 ```js

这是第一个组件

```

组件b 承上启下

```js

```

如此,就能实现祖孙组件的通信 ```js

```

但是到了递归组件,不灵了!!,就必须走老路,我也上了github 看了吗,官方未解决issues

由于我们使用的数据沿用了CodeSandbox 的数据结构

image.png

他将文件和目录分开了,分别在modulesdirectories中,于是我们终于用上了面试时候用到的算法 将数组转为tree 通过递归解决

```js export const setCatalogue = (currentSandbox): any[] => {

const arr = _.cloneDeep([...currentSandbox.directories, ...currentSandbox.modules])
function loop(parId?) {
    return arr.reduce((acc, cur) => {
        if (cur.directory_shortid == parId) {
            cur.children = loop(cur.id)
            acc.push(cur)
        }
        return acc
    }, [])
}
return loop()

} ```

文件系统就这么解决了

编辑器

codesandbox 的编辑器用的是monaco-editor 也就是vscode 的前身 但是,翻遍源码,他的调用方式跟monaco-editor

不能说是相似,简直可以说是不同,并且monaco-editor 的文档也是一塌糊涂,我猜他们魔改了这个编辑器

image.png

甚至官方都让我们直接从类型定义文件里面去猜,

巨硬爸爸,要不您就别开源了!开源咱也看不懂啊

image.png

无奈之下,另辟蹊径吧

找了个呼声高,功能相似,文档齐全的codemirror5

东西找好了,开干吧,写个通用的编辑器组件

```js

```

渲染器

渲染器,其实就是整个右边的视图。你一说原理,头头是道,我看了文章也能明白,他是怎么处理的,

然而,光说不练假把式, 你一到落地,可不是这么简单,给我急的嘬牙发子

要解决渲染器的问题,除了要理解原理之外,我们还要解决几个难点

一个个来,先说原理,一句话就能概括,造个web版npm 造个web版webpack

image.png

原理如盗图

Sandbox 在一个单独的 iframe 中运行, 负责代码的转译(Transpiler)和运行

其实就是一个浏览器端的webpck

Packager类似于yarn和npm,负责拉取和缓存 npm 依赖

接下来就是难点

  • 1、web版本webpack 虽说有源码能抄,但是它是通过iframe 嵌入的,所以本质上他必须是个服务,我们怎样给他独处理成一个项目,源码中都揉一块了,我们从那入手呢
  • 2、Packager包管理,虽然开源了,但是也没提供文档,我们在移植或者,直接搬过来部署也相当困难
  • 3、这块最难,移植过来需要多少时间,工作量无法估计

好在CodeSandbox 良心啊,他们直接独立了一个渲染器将编译和npm 包拉取这一块独立出来 sandpack-client,并且开源了

他的代码非常简单,就是创建一个iframe,并且调用CodeSandbox 官方的打包服务,这样所有的渲染层的核心代码就不会在我们这边了,全部是codesandbox的服务

使用方式也非常简单

``js import { SandpackClient } from "packages/SandpackClient"; // 数据源 const VUE_TEMPLATE_3 = { files: { "/src/App.vue": { code:

, }, "/src/main.js": { code:import { createApp } from 'vue' import App from './App.vue'

createApp(App).mount('#app')
`, }, }, dependencies: { "core-js": "^3.6.5", vue: "^3.0.0-0", "@vue/cli-plugin-babel": "4.5.0", }, entry: "/src/main.js", environment: "vue-cli", }; // 初始化 const SandpackClientStore = new SandpackClient( el, VUE_TEMPLATE_3, { showOpenInCodeSandbox: false } ); // 代码跟新

SandpackClientStore.updatePreview(VUE_TEMPLATE_3);

```

思考再三,首先由于渲染层不涉及单位业务,并且如果自己开发不一定比官方的服务好

干脆,拿来主义,用人人家的得了

ui呈现

ui 方面,源码中使用的是他们自己封装的组件,以及自己开发的一些样式

到我们这一切从简,功能实现即可,element-ui代替在家自己开发个别样式即可

通用数据结构设计

由于,文件系统,编辑器,渲染层。三大块需要实现联动,那么你必须要上vuex了,来管理和连接这三个区块的状态以及数据

在最开始,我设想的跟开源的CodeSandbox  一样,设计很多状态,比如currentSandbox 元数据 currentCode选中数据 catalogueStructure 文件目录数据 project 项目代码

然后在项目中通过 mutationsactions 来通知状态以及数据变更

后来,发现,不行,太乱,到处都是commitdispatch,后期维护根本摸不着头绪

我们就追本溯源,我们说,本质整个页面上的所有数据,都围绕着currentSandbox 元数据来操作的

由于vue 的响应式特性,我们所有的数据都需要根据currentSandbox 变更而来,我们使用getters 得到即可 代码如下:

```js import { createStore } from 'vuex' import { data } from './dome' import { SandpackClient } from "packages/SandpackClient"; import { setCatalogue, conversionCode } from 'utils/index' let SandpackClientStore = null // 创建一个新的 store 实例 export default createStore({ state() { return { currentSandbox: data, currentCode: { title: '', code: '' }, } }, mutations: { SETCURRENTCODE(state: any, code) { state.currentCode = code }, SETCURRENTSANDBOX(state) { const modules = state.currentSandbox.modules modules.find((item) => { if (item.id == state.currentCode.id) { item.code = state.currentCode.code return }

        })
    }
},
actions: {
    setCurrentCode({ commit }, code) {
        commit('SETCURRENTCODE', code)
    },
    setClient({ getters }, el) {
        SandpackClientStore = new SandpackClient(
            el,
            getters.project,
            {
                showOpenInCodeSandbox: false
            }
        );
    },
    setUpdatePreview({ commit, getters }) {
        commit('SETCURRENTSANDBOX')
        SandpackClientStore.updatePreview(getters.project);
    },

},
getters: {
    // 入口文件
    entryFile(state) {
        return state.currentSandbox.entry.split('/')
    },
    // 文件目录
    catalogueStructure(state) {
        return setCatalogue(state.currentSandbox)
    },
    // 项目代码
    project(state) {
        return conversionCode(state.currentSandbox)
    }
}

}) ```

这样一来,整个由于响应式的特性,我们只需要修改currentSandbox的数据结构即可 ,简单了不少

源码

1.0捡漏版本实现了,目前只实现了联动,但是还没有和服务端联动,当然配置服务端的内容估计是不能开源了

源码如下: yys-Codesandbox

说点话

经历两周,算是简单的实现了破产版的Codesandbox,功能简陋,层次低廉,技术粗鄙,难登大雅(实现方式确实简单)

但是还是想给实现方式,以及实现思路,发出来:

  • 1、为了实现类似需求的朋友们一个思路上的借鉴,不能说是完全正确,但总能给点参考,毕竟花了两周呢
  • 2、将所有踩过的坑,思考的过程记录一下,这都是安身立命的财富啊
  • 3、分析是分析是,落地是落地,高大上的技术,实现方式,虽然深奥,但是别忘了,我们是站在巨人的肩膀上,其实相当简单,鼓励大家迎难而上!

参考文章

搭建一个属于自己的在线 IDE

闲谈Monaco Editor-基本使用

CodeSandbox 浏览器端的webpack是如何工作的? 上篇

CodeSandbox-client源码解析系列之项目目录结构及本地调试初体验

我在做前端构建过程中的思考