登上 Github 趨勢榜,iMove 原理技術大揭秘!
我認為前端開發(fā)中問題很多,尤其是以下3點。
- 
    
UI老變,導致開發(fā)必須跟緊
 - 
    
邏輯挑戰(zhàn),開發(fā)也必須改代碼,很多后端處理邏輯都在里面
 - 
    
組合接口,這是歷史原因,主要是和后端配合導致的。其實沒有Node BFF層,都由組件來做,會問題非常多。
 
最近我們的開源項目 iMove 一天就漲了 280+ star,一舉登上了 github 趨勢榜第 1 名,取得的成績還是不錯的,說明這個項目定位準確,確確實實解決了開發(fā)者問題。
今天,就通過本篇文章和大家介紹一下 iMove 開源項目,內容包含iMove功能和實現(xiàn)原理、獨創(chuàng)的在線代碼運行能力,以及如何自動解析節(jié)點的npm包依賴,還是有非常多亮點和創(chuàng)新的。
關于 iMove
簡單講,其實我們理想的前端可以做以下4點。
- 
    
邏輯可組裝:其實是接口和UI在最小粒度上的復用。
 - 
    
流程可視化:這些可復用的最小單元,可以通過流程來進行編排,繼而達到讓運營簡化的目的。
 - 
    
運營配置收斂:這是因為多套系統(tǒng)導致運營成本很高導致的,統(tǒng)一放到一起最好。
 - 
    
玩法能力沉淀:促使產品將玩法進行沉淀,變成可復用的能力。
 
對開發(fā)者而言,iMove恰好是可以完成這些目標的理想工具。動動鼠標,寫一下節(jié)點函數(shù),代碼導出,放到具體工程里就可以直接使用,是不是很方便?
那么,什么是 iMove?
- 
    
它是個工具,無侵入性。
 - 
    
雙擊編寫函數(shù),編排后的流程可以導出可執(zhí)行代碼,便于在具體項目里做集成。
 - 
    
測試方便,右鍵直接執(zhí)行,此處有創(chuàng)新。
 - 
    
讓開發(fā)像運營配置一樣完成功能開發(fā),做到復用和Lowcode。
 
舉個栗子
上面這些介紹,也許看著沒什么體感,不容易理解,讓我們一起來看個例子~
假如有天你接到了一個 詳情頁購買按鈕 的需求:
- 
    
獲取詳情頁的商品信息
 - 
    
商品信息包含以下
 
- 
    
- 
        
當前用戶是否已經(jīng)領券
 - 
        
商品領券是需要關注店鋪還是加入會員
 
 - 
        
 
- 
    
根據(jù)返回的商品信息,購買按鈕有以下幾種形態(tài)
 
- 
    
- 
        
如果已經(jīng)領券,按鈕展示 "已領券 + 購買"
 - 
        
如果沒有領券
 
 - 
        
 - 
    
- 
        
- 
            
如果領券需要關注店鋪,按鈕展示 "關注店鋪領券 + 購買"
 - 
            
如果領券需要加入會員,按鈕展示 "加入會員領券 + 購買"
 
 - 
            
 
 - 
        
 - 
    
- 
        
異常情況時,展示兜底樣式
 
 - 
        
 
- 
    
注意:如果用戶未登錄,喚起登錄頁
我們可以將以上這段復雜的業(yè)務邏輯轉化成如下這段示意偽代碼: 
- // 檢查登錄
 - const checkLogin = () => {
 - return requestData('/is/login').then((res) => {
 - const {isLogin} = res || {};
 - return isLogin;
 - }).catch(err => {
 - return false;
 - });
 - };
 - // 獲取詳情頁數(shù)據(jù)
 - const fetchDetailData = () => {
 - return requestData('/get/detail/data').then((res) => {
 - const {
 - hasApplied,
 - needFollowShop,
 - needAddVip,
 - } = res;
 - if(hasApplied) {
 - setStatus('hasApplied');
 - } else {
 - if(needFollowShop) {
 - setStatus('needFollowShop');
 - } else if(needAddVip) {
 - setStatus('needAddVip');
 - } else {
 - setStatus('exception');
 - }
 - }
 - }).catch(err => {
 - setStatus('exception');
 - });
 - };
 - checkLogin().then(isLogin => {
 - if(isLogin) {
 - return fetchDetailData();
 - } else {
 - goLogin();
 - }
 - });
 
誠如上述例子所示,雖然業(yè)務復雜度并不是非常高,但其背后的溝通和理解成本其實并不低,想必大家在各自業(yè)務中遇到的實際場景比這復雜棘手得多。然而,業(yè)務邏輯的復雜度決定了代碼的復雜度,越復雜的代碼越難維護,假如某天你接手了一個邏輯很復雜的他人項目,這其中的維護成本是非常高的。不過這也正是 iMove 所解決的問題之一,面對上述同樣的業(yè)務需求,我們來看看使用 iMove 是如何開發(fā)的。
如上所示,原本晦澀難懂的代碼邏輯通過 iMove 以流程圖的形式表達了出來,現(xiàn)在產品的業(yè)務邏輯一目了然。除此之外, iMove 中的每個節(jié)點都是支持編寫代碼的,而流程圖的走向又決定了圖中節(jié)點的執(zhí)行順序,可以說 "流程可視化即天然的代碼注釋" 。
因此,就從 "易讀性" 和 "可維護性" 上來看: iMove 流程可視化的形式 > 產品經(jīng)理的 PRD 文字描述形式 > 程序代碼形式。
應用場景
前端React組件一般在ComponentDidMount里發(fā)起請求,根據(jù)請求成功的數(shù)據(jù)完成渲染或其他業(yè)務邏輯,這種是完全無UI的Ajax請求處理。除了組件聲明周期,就只有各種交互事件里,這里面一般是UI和Ajax混用的場景。
- 
    
前端流程,比如點擊事件,組件生命周期回調等。
 - 
    
后端流程,比如Node.js或Serverless領域。
 - 
    
前端+后端,比如前端點擊事件,Ajax請求和后端API。
 
優(yōu)勢
上文提到的內容只是 iMove 的冰山一角,我們一起來看下 iMove 都有哪些優(yōu)勢:
1)邏輯可復用
面對頻繁迭代的日常業(yè)務需求,我們一定會遇到許多相似、重復的開發(fā)工作。體現(xiàn)在代碼中它可以是一段通用的 utils 工具方法,也可以是一段通用的業(yè)務邏輯代碼(比如分享),但究其本質它們就是一個 代碼片段 。為了提高代碼的復用,我們往往會將其封裝成一些通用的類或函數(shù),然后在各個項目中拷貝粘貼(做得好一點可以將其封裝成 npm 包,但修改發(fā)布的流程又會稍顯繁瑣)。
而在 iMove 中,每個可復用的代碼片段都可以被封裝成流程圖中的節(jié)點。當想在不同項目中復用邏輯的時候,直接引入對應的節(jié)點/子流程即可,每個節(jié)點還支持參數(shù)配置,進一步提升了節(jié)點的復用性,使用體驗可以說是非常簡單了。
再往后設想一步,假如 iMove 已經(jīng)在某個業(yè)務場景中沉淀了一定量的業(yè)務節(jié)點,當下次再遇到相似的業(yè)務需求時,邏輯部分是否可以直接復用現(xiàn)成的節(jié)點拼裝而成。這可是大大提升了研發(fā)效率,縮短了項目的研發(fā)周期。
2)面向函數(shù)
在節(jié)點的設計方面, iMove 做得比較克制,每個節(jié)點其實就是導出一個函數(shù),因此編碼體驗上幾乎沒有什么上手成本,只要你有JavaScript基礎就能使用。你可以照常 import 其他 npm 包,也不用考慮節(jié)點之間的全局變量命名污染等問題,將它當做一個普通的 js 文件即可。
3)流程可視化
我們將流程可視化的這種開發(fā)方式稱之為 "邏輯編排" ,它的好處(邏輯表達更為直觀,易于理解)前文已經(jīng)介紹過,這里就不再重復贅述。
4)邏輯/UI 解耦
我們在日常業(yè)務開發(fā)中經(jīng)常會遇到: UI 樣式經(jīng)常變化,而業(yè)務邏輯較為穩(wěn)定,甚至還會有 ABTest 需求查看改版效果 。
然而許多開發(fā)者在組件開發(fā)之初并不會設想到將來會有這一步,因此一個業(yè)務組件往往會將 "業(yè)務邏輯" 和 "UI樣式" 耦合在一起。而到了改版的時候才會發(fā)現(xiàn)要想抽離和復用業(yè)務邏輯并不容易,維護成本也大大增加。
不過當你使用 iMove 開發(fā)之后就會發(fā)現(xiàn):組件代碼自然而然就拆成了 "業(yè)務邏輯" + "UI樣式"。而且,不同版本的 UI 可以維護多套,但業(yè)務邏輯部分只要交給 iMove 維護一套即可。這樣的開發(fā)方式不僅可以最大程度地復用業(yè)務邏輯代碼,而且還提高了項目的可維護性。
5)更簡單的代碼測試
為了提升 iMove 的使用體驗,我們實現(xiàn)了 "瀏覽器端在線運行節(jié)點代碼" 的功能。這意味著當完成一個節(jié)點的函數(shù)功能時,你隨時可以在瀏覽器端 mock 各種輸入來測試該節(jié)點的運行結果是否符合你的預期。
也就是說,在無須引入測試框架、脫離上下文環(huán)境的前提下,你就可以單獨對某一個節(jié)點的函數(shù)進行測試,這大大降低了代碼測試的成本和門檻。與此同時,你還可以順手將每次的測試輸入/輸出作為測試用例進行保存,漸而形成一份完備的測試用例,這不但可以保障該節(jié)點的代碼質量,還可以更放心得被引用在其他項目當中。
6)無語言/場景限制
雖然 iMove 本身是一個 JavaScript 工具庫,但在我們的設計中 iMove 并沒有對使用語言和場景加以限制。也就是說,你不僅可以用 iMove 編排前端項目中的 js 代碼,同樣也可以用 iMove 編排后端項目中的 java 代碼,甚至其他場景的其他語言。而這一切,其實最終只取決于 iMove 將流程圖編譯出碼成什么語言而已。
iMove 原理
在對 iMove 的項目背景有了一定了解后,本文接下來將帶大家一起揭開它背后的技術原理~
如何搭建可繪制的流程圖應用?
拋開 iMove 偏開發(fā)的功能不說,大家完全可以把它當做一個流程圖的繪制工具來使用(畫完之后還可以導出圖片保存到本地~)。那么 iMove 又是如何繪制流程圖的呢?想必大家一定對此比較好奇,這里必須得給螞蟻團隊做的 X6 引擎點個贊 :+1: ,真的很好用~
X6 本身并不和 React 或 Vue 捆綁,因此你可以在任何框架內配合 X6 一起使用。除此之外,它提供了一系列開箱即用的交互組件和簡單易用的節(jié)點定制能力,只需簡單調用一些 API 就能快速實現(xiàn)相應的功能。
點擊i
后續(xù)我們會專門出一篇文章介紹 iMove 如何使用 X6 快速搭建一個可繪制的流程圖應用。
imove的核心就是基于x6協(xié)議實現(xiàn)的。
- 
    
有節(jié)點:利用x6的可視化界面,便于復用和編排。
 - 
    
有指向邊:即流程可視化,簡單直觀,邊上還可以帶參數(shù)。
 - 
    
有function和schema2form,支持函數(shù)定義,這是面向開發(fā)者的。支持form,讓每個函數(shù)都可以配置入?yún)?,這部分是基于阿里開源的form-render實現(xiàn)的。
整個項目難度不大,基于x6和form-render進一步整合,將寫法規(guī)范化,將編排工具化,這樣克制的設計使得imove具備小而美的特點,便于開發(fā)使用。 
如何將流程圖編譯成可運行代碼?
相比于繪制流程圖, iMove 更吸引人的是它可以將流程圖編譯成業(yè)務項目中可實際運行的代碼。
在線編譯 vs 本地編譯
首先, iMove 既支持瀏覽器端在線編譯提供 zip 包下載,也支持本地命令行 watch 實時編譯出碼。
1)在線編譯
為了降低 iMove 的上手成本,我們加入了瀏覽器端在線編譯出碼的功能,這樣開發(fā)者無須安裝工具就能直接下載編譯好的代碼。
具體該如何實現(xiàn)呢?經(jīng)過調研,我們發(fā)現(xiàn) jszip 是一個集讀/寫/改 zip 文件于一身的 JavaScript 庫,而且還支持瀏覽器端運行。因此,我們完全可以按照出碼的文件目錄生成一個 json 丟給 jszip 打包,最后再用 file-saver 提供下載即可。
- // key 是"文件/目錄名",value 是對應的"文件內容"
 - {
 - "nodeFns": {
 - "node1.js": "...",
 - "node2.js": "...",
 - "index.js": "..."
 - },
 - "context.js": "...",
 - "dsl.json": "...",
 - "index.js": "...",
 - "logic.js": "..."
 - }
 
2)本地編譯
在線編譯的方式固然簡單,但在項目開發(fā)中會遇到一個問題: 每次修改代碼都需要重新下載 zip 包并解壓到指定目錄,尤其是調試時需要頻繁修改代碼會非常不便 。為了解決這個問題, iMove 提供了本地編譯的方式,通過 watch 流程圖的保存操作,實時地編譯出碼到業(yè)務項目中。
具體該如何實現(xiàn)呢?上述問題的關鍵在于: 本地如何監(jiān)聽瀏覽器端的流程圖保存操作 。但是反過來想,為什么不可以是 流程圖在保存時發(fā)送消息通知本地呢? 這么一來,我們既可以使用 socket.io 等類庫在瀏覽器和本地之間建立 websocket 通信,也可以使用 koa / express 等類庫在本地啟動一個 http 服務器,只要在接收到流程圖保存信號時觸發(fā)編譯出碼即可。
iMove 代碼運行基本原理
解決完 iMove 如何編譯代碼的問題,再來看看 iMove 編譯的代碼又是如何運行的。
要想運行代碼, iMove 需要解決兩個核心問題:
- 
    
如何按流程圖的順序依次執(zhí)行節(jié)點
 - 
    
如何處理數(shù)據(jù)流(比如上一節(jié)點的返回值是下一節(jié)點的輸入)
 
RxJS 看起來是個不錯的選擇,函數(shù)響應式編程似乎天然解決上面的問題。但考慮到它的上手成本無疑會對 iMove 的使用者造成巨大的心智負擔,因此最終我們并沒有采用這套方案。
1)對于第一個順序執(zhí)行問題, iMove 采用了一種低成本的方式來解決:
- 
    
首先,
X6支持導出流程圖的json數(shù)據(jù),我們可以將其精簡處理后保存為一份DSL文件。 - 
    
其次,根據(jù)這份
DSL文件,我們可以從中提取出每個節(jié)點的代碼部分成為單獨的文件,進而構成一個 節(jié)點函數(shù)集 。 - 
    
最后,接下來只要按照 節(jié)點和邊的上下游關系 順序調用相應的節(jié)點函數(shù)即可。
 
2)對于第二個數(shù)據(jù)流問題, iMove 考慮到實際應用中節(jié)點對數(shù)據(jù)操作的各種場景,一共設計了四種數(shù)據(jù)讀寫方式:
- 
    
config:只讀型,每個節(jié)點的投放配置,節(jié)點之間互不干擾。
 - 
    
payload: 只讀型,
logic.invoke觸發(fā)邏輯時,可以傳一個payload值,每個節(jié)點都能讀取該值。 - 
    
pipe:只讀型,上一個節(jié)點的輸出是下一個節(jié)點的輸入。
 - 
    
context: 可讀寫,某一個邏輯流中的公有數(shù)據(jù),如果祖先節(jié)點又通過
setContext設置數(shù)據(jù),那么子孫節(jié)點可以跨節(jié)點通過getContext訪問到該數(shù)據(jù) 
至此, iMove 解決了流程圖代碼運行的問題。如果你有注意 iMove 編譯后的代碼,就能看到如下結構:
理解Flow
Flow的基本概念很簡單,就是一個有向無環(huán)圖(DAG),數(shù)據(jù)在節(jié)點間流動。
- 
    
節(jié)點 Node
 
- 
    
- 
        
節(jié)點是組成流的主要單元,負責對流入節(jié)點的數(shù)據(jù)進行處理,并輸出到后續(xù)節(jié)點進行進一步的處理。
 
 - 
        
 
- 
    
端口 Port
 
- 
    
- 
        
每個節(jié)點擁有輸入和輸出端口,輸入端口負責數(shù)據(jù)流入節(jié)點,輸出端口負責數(shù)據(jù)流出節(jié)點。每個節(jié)點都可能擁有一個或者多個輸入和輸出端口。
 
 - 
        
 
- 
    
連接 Link
 
- 
    
- 
        
一個節(jié)點的輸出端口連接到另一個節(jié)點的輸入端口,節(jié)點處理好的數(shù)據(jù)通過連接流入其后的節(jié)點。
 
 - 
        
 
Flow的實現(xiàn)基本思路就是用一個函數(shù)function實現(xiàn)一個節(jié)點,輸入端口映射為函數(shù)的輸入?yún)?shù)。輸出端口映射為函數(shù)的返回值。
流中有一個節(jié)點被設置為終點節(jié)點(End Node),通過節(jié)點間的連接關系,以終點節(jié)點開始通過連接搜索所有的依賴關系(樹形查找),得到一個節(jié)點運行的棧。例如上圖,我們就可以得到一個 [node1,node2, node3] 這樣的棧。按順序出棧的方式執(zhí)行每一個節(jié)點的功能就可以運行整個流。(注意,這是一個簡單版本的Flow的實現(xiàn),仍然是一個批處理,不是streaming)
需要假定每一個節(jié)點的功能是無狀態(tài)的,這樣就可以利用輸入輸出端口對計算結果進行緩存,但輸入值是已經(jīng)運算過的值的時候,不需要運算,直接返回已經(jīng)計算過的值。
以上是Flow-base programing(FBP)里的Flow概念。其實這和imove里的概念是一樣的。imove基于x6,x6解決了DAG實現(xiàn)和可視化問題,再結合節(jié)點擴展函數(shù)寫法,繼而實現(xiàn)面向開發(fā)者的邏輯編排工具。殊途同歸,就是這么簡單。
如何在線運行節(jié)點代碼?
iMove 有個比較 cool 的功能就是可以在瀏覽器端在線運行節(jié)點函數(shù)代碼,實時看到運行結果。這種 所見即所得 的開發(fā)體驗對使用者來說還是很友好的,不但測試調試的成本大大降低,還能成為測試用例集進一步保障節(jié)點質量。
在 iMove 里編寫代碼,雙擊節(jié)點即可。
右鍵執(zhí)行代碼,即可完成對單個節(jié)點的測試。
打開運行面板,填寫對應參數(shù),即可執(zhí)行具體代碼。
在線運行 iMove 節(jié)點代碼需要解決以下一些問題:
- 
    
瀏覽器端如何直接運行節(jié)點中的
import/export等esm規(guī)范代碼 - 
    
節(jié)點中
import進來的npm包還有可能是cjs規(guī)范,瀏覽器又該如何運行 - 
    
同時選中多個節(jié)點時,又該如何運行代碼
 
iMove 實現(xiàn)的背后原理主要還是借助 http-import ,后面我們會專門出一篇文章介紹它的實現(xiàn)原理,敬請期待~
如何自動解析節(jié)點的 npm 包依賴?
由于每個 iMove 的節(jié)點都支持 import 其他 npm 包,因此每個節(jié)點都有 npm 依賴。但是,如果這項工作讓開發(fā)者手動填寫體驗會非常差,因此我們做了自動解析的功能。
原理其實比較簡單,了解 npm view 命令即可。就拿 npm view lodash.get 舉例來說,查看命令行輸出可以看到:
- $ npm view lodash.get
 - lodash.get@4.4.2 | MIT | deps: none | versions: 13
 - The lodash method `_.get` exported as a module.
 - https://lodash.com/
 - dist
 - .tarball: https://r.cnpmjs.org/lodash.get/download/lodash.get-4.4.2.tgz
 - .shasum: 2d177f652fa31e939b4438d5341499dfa3825e99
 - maintainers:
 - - phated <blaine.bublitz@gmail.com>
 - dist-tags:
 - latest: 4.4.2
 - published over a year ago by jdalton <john.david.dalton@gmail.com>
 
如上所述,該命令成功獲取到 lodash.get 這個包的最新版本。當然, npm view 命令不適合在瀏覽器端執(zhí)行,但究其本質都會走網(wǎng)絡請求查詢。我們再用 npm view lodash.get --verbose 就能看到執(zhí)行過程中其實發(fā)起了請求: https://r.cnpmjs.org/lodash.get 。
接下來就很簡單了,只要根據(jù) import 語法規(guī)則用正則匹配出節(jié)點代碼中的依賴,再調用上面的 api 就可以自動解析出該節(jié)點的包依賴了。
總結
在UI側有imgcook這樣的設計稿轉代碼的工具,應對變化是足夠的。但在邏輯領域,真正能解決問題的又面向開發(fā)者的少之又少,imove算這個方向的一個探索。相信通過上面的講解,大家都能夠感受到它的魅力。
imove的口號是 Move your mouse, generate code from flow chart ,即動動鼠標,寫一下節(jié)點函數(shù),代碼導出,放到具體工程里就可以直接使用。像運營配置一樣開發(fā),這已經(jīng)不是愿望,而是已經(jīng)實現(xiàn)了的。






























 
 
 









 
 
 
 