帶你7天玩轉可視化建站平臺

前言


對於互聯網公司而言,活動運營頁面發佈頻率高,每次開發又要人力成本不划算。所以須要有一套平臺系統來知足運營產品自我快速建站。此類軟件產品最先追溯到QQ空間,凡客建站等。html

此項目旨在用最少的代碼實現可視化搭建、發佈、預覽、調試等核心功能。並對每個關鍵原理進行說明。
在此以前,但願你對Javascript(ES2015,React hooks)、nodejs、webpack等基礎知識有所瞭解,即可輕鬆設計開發屬於你的建站平臺(也許只須要你花費一週的時間ヽ( ̄▽ ̄)ノ)。前端

首先咱們來看看效果:
可視化編輯器面板node

目前支持的功能

編輯器相關:
  • 拖拽菜單組件放入畫布,能放置的位置標綠框,不能放的標紅框;選中錨點、移入高亮。
  • 鼠標按住拖動畫布內組件四周改變寬高,拖動中心改變定位
  • 屬性面板輸入樣式、自定義屬性配置,實時更新畫布預覽
  • 頁面編輯快捷鍵操做,包括:保存(Ctrl+S),撤銷(Ctrl+Z),恢復(Ctrl+Y),刪除(DEL),複製(Ctrl+C),剪切(Ctrl+X),粘貼(Ctr+V),上移節點(↑),下移節點(↓),縮放移動畫布(空格按下+左鍵拖動+滾輪縮放)
  • 直接拖動左下方樹結構批量移動節點
服務層相關:
  • 提供平臺前端頁面(編輯器、預覽頁)的請求接口與路由模板
  • 打包構建:對組件倉庫的分包,對編輯器SDK的打包
  • 開發組件調試模式的命令行腳本
預覽相關:
  • 將頁面搭建配置建立React組件樹,動態加載所需組件JS文件
  • 組件懶加載功能

實現原理


主要劃分爲4個部分:編輯器、預覽頁、服務端、組件倉庫
用戶在編輯器內拖入組件倉庫已開發的組件,設置樣式與自定義屬性,爲頁面生成JSON配置,將配置提交服務端保存。預覽頁請求返回配置,預覽頁根據配置動態下載組件文件並渲染。
總體流程react

1、頁面JSON配置與渲染的關係

不論在編輯器內仍是預覽頁,頁面都是根據JSON配置來遞歸渲染webpack

{
        "name": "View",
        "style": {
          "position": "relative",
          "width": "1089px",
          "height": "820px"
        },
        "props": {
          "lazy": true
        },
        "el": "wc12",
        "children": [
             //{ ...}
        ]
  }

這是一個簡單的佈局組件所映射的JSON結構,其中包括了該組件的樣式,傳入組件的props屬性,以及其惟一的key(el)值,還有他的子組件children數組,數組裏的內容就是其包裹組件的JSON結構。經過這樣的嵌套關係,能夠將其映射成組件樹。
compile.jsgit

當頁面JSON配置發生變化時,依靠react單向數據流會從新渲染,此時咱們須要一個通用方法,來遞歸的建立組件的佔位DIV,可是須要注意的是,首次建立的只是一個空殼,return的子組件爲null。
global.js
與此同時咱們調用異步加載組件js的方法,等該組件下載好後自動注入到這個殼裏。這個方法的特色在於,咱們每次加載新的組件會優先從window.comp下找是否有已緩存的組件對象,若是爲undefined,說明這是一個全新的組件,就請求對應的JS下載,而且將window.comp下的這個組件標識爲正在請求的Promise,這樣若是相同組件併發調用此方法,會awati同一個Promise不會重複請求,並且組件緩存後也能夠直接用await拿到組件對象。
compile.js -> CompBoxgithub

經過上述的幾個方法,咱們已經可以將JSON配置渲染爲頁面DOM,而且動態加載組件JS文件了~web

2、編輯器內的操做

既然咱們的頁面是根據JSON配置來渲染的,那麼對頁面任何的增刪改查,均可以抽象爲對JSON樹內某個節點的數據結構修改。咱們須要一個通用的搜索方法,來搜索JSON樹,並傳入一個標識,來指明此次操做的類型

common.js
searchTree是全部操做的通用方法,本質上是對JSON配置樹的BFS搜索,只要找到對應的key節點,根據EnumEdit中的枚舉類型操做數據後返回修改後的結果樹,dispatch新的配置樹通知react從新渲染。數據庫

除此以外,具體操做這裏涉及到各類鍵盤、鼠標事件的綁定,這部分暫不作贅述,可自行查詢MDN文檔。express

3、樣式、自定義屬性的注入

咱們在右側編輯區填寫的內容,都會在渲染時注入到對應的組件裏,樣式style會注入在包裹組件的殼裏,自定義屬性會當作prop傳入子組件,在組件開發中,咱們能夠從props中拿到編輯器內填寫的屬性值。
compile.js -> CompBox

4、編輯歷史記錄管理

歷史記錄爲一個隊列的數據結構,若是咱們保存1000條記錄,每修改一次JSON配置,就將其入隊,每次入隊時發現記錄大於1000,就將隊列頭部拋棄。
當前頁面顯示的配置爲一個指針,指向隊列中某條記錄,撤銷就指針後移,恢復就指針前移。每次觸發compile時,將新的配置樹計入隊列,不須要手動記錄。利用hooks自帶的緩存機制很是容易實現。

record.js

5、畫布的縮放處理

在搭建使用程中,咱們寄但願於畫布設計尺寸永遠爲1920(移動端則爲750),可是視口顯然沒那麼大,因此咱們要將畫布以左上角爲縮放焦點transform-origin: 0 0,拖動導航slide或按下空格利用滾輪縮放。這個過程當中不斷改變transform: scale來刷新視圖。
須要注意的是,scale的改變爲瀏覽器重繪,並不會改變原有的DOM佔位尺寸,所以縮小畫布會有很大的空白區域,爲了解決這個問題,咱們須要在畫布外再包一層div,每次畫布改變縮放後,利用getBoundingClientRect()來獲取縮放後畫布實際的寬高,並將這個數值定義在外層div上,外層div設置爲overflow: hidden,這樣窗口滾動的距離就會依據外層容器來出滾動條。

畫布的高度計算時,要計算出一個min-height,爲當前搭建區域的offsetHeight,保證畫布內沒有組件撐開時,也可以鋪滿一個屏幕。

此外對畫布根節點要設置一個padding-bottom: 300px,做用是保證底部永遠有一個空白區域,可以讓搭建者拖入新的組件到根節點下。

6、組件的開發

每個組件都的固有結構,index.jsconfig.json是必須存在的(服務層會根據此文件構建,稍後會提到):
comp/Image
入口文件即爲業務代碼,配置爲一個JSON文件,決定了編輯器內所能編輯的自定義選項:

{
    "name": "圖片",
    "staticProps": [
        {
            "name": "點擊連接",
            "prop": "link",
            "size": "long"
        },
        {
            "name": "是否在新窗口打開連接",
            "prop": "blank",
            "type": "switch",      // 配置類型,目前支持`text`默認,`select`,`switch`,`color`
            "size": "long"          // 配置是否佔滿編輯器一行
        },
        {
            "name": "圖片地址",
            "prop": "src",
            "size": "long"
        }
    ],
    "defaultStyles": {        // 拖入組件到畫布時默認的樣式
        "position": "relative",
        "width": "180px",
        "height": "180px",
        "marginTop": "0px"
    },
    "defaultProps": {      // 拖入組件到畫布時默認的自定義屬性
        "src": "http://r.photo.store.qq.com/psb?/V14dALyK4PrHuj/h50SMf97hSy.BJlJw31fagrw.NUaJD83gvydmoGN77w!/r/dLgAAAAAAAAA",
        "blank": true
    },
    "hasChild": false,        // 是否容許有子組件,若是不容許拖拽的時候移入會標紅,提示當前節點不能被注入
    "canResizeByMouse": true       // 是否容許經過拖動九宮格蒙版來修改組件的寬高位置
}

上述這樣的一個圖片組件,在編輯器內對應的配置項即爲:

7、組件的構建打包

這是構建階段很是重要的一環,咱們上面說過,每個組件對應一個JS文件,那麼咱們就須要在頁面生成前將當前全部組件都構建好。
webpack.config.comp.js
這裏首先找出倉庫中的組件,加入打包的entry入口,而後利用webpack的library,libraryTarget配置,將組件打包到window[name]下,name爲組件名(好比Image,View),咱們來看看打包後的組件代碼:

果不其然 ,組件js下載執行後直接被掛載到window下了,此時此刻你能夠回頭看開頭提到的loadAsync加載組件的方法,是否恍然大悟了呢。
這裏你可能又發現一個問題,組件都依賴react庫,那每一個組件單獨打包,豈不是都要加載一遍,那包得多大?

從JS體積能夠看出,實際上根本沒有打包這些通用庫,只包含了業務代碼而已。這裏一樣利用webpack的externals屬性,能夠指定某些依賴直接從window下取:
webpack.config.comp.js
那麼是何時將react注入window下的呢?
global.js
在編輯器或者預覽頁面,加載全局配置,也就是SDK初始化以前,就將組件所依賴的全局對象注入好了,這樣後續組件異步下載後就能夠直接執行。
關於編輯器和預覽頁的打包不作特別說明,就是普通的webpack配置打包,記得抽出公共模塊就好。

7、組件的代理調試

平臺開發好了,這個時候咱們要往裏開發業務組件了,那麼如何調試呢。
經過npm run dev:comp debug=XXX,YYY命令(XXX爲組件名)來執行調試腳本

腳本首先經過process.argv傳入的參數獲取要調試的組件
debugComp.js
而後使用node API來調用webpack-dev-server
須要注意的是,這裏僅僅是在本地建立了組件的代理,還須要在組件資源加載上區分哪些組件須要請求本地調試地址,詳情可見上方loadAsync方法,咱們經過在預覽頁和編輯器後方加入debug_comp=XXX參數來告訴此方法該組件要請求本地調試地址
server/index.js
最後記得若是當前用戶請求的URL是調試模式,在node express服務的ejs模板接口裏加上webpack-dev-server的代碼script標籤,

8、服務端對頁面配置的管理

由於此項目爲演示項目,並無對頁面配置用id進行區分,每次提交都是存取同一個配置文件page.json
opPageJSON.js
生產環境下須要鏈接數據庫,將每一份配置生產一個ID,在打開編輯時取對應的請求ID返回配置。
要額外注意的一點,咱們在返回配置接口數據時,要去搜索當前構建文件夾中存在的js與哈希值的映射,這樣保證前端頁面能正確的加載最新構建的js地址

項目結構


├─config.js            // 先後端通用配置
├─comp                // 組件倉庫
│  ├─Image                   // 組件名
│  │      config.json            // 組件配置    
│  │      index.js                // 組件入口
│  │      index.less            // 組件樣式
│  │      
│  ├─Text
│  │    ...
│  │      
│  └─View
│       ...
│          
├─script                // 配置腳本
│      debugComp.js                    // 組件調試腳本
│      webpack.config.comp.js        // 組件打包配置
│      webpack.config.edit.js        // 編輯器打包配置
│      webpack.config.page.js        // 預覽頁打包配置
│      
├─server                // 建站平臺服務端
│  │  getCompUrlHook.js        // 生成組件js文件哈希映射
│  │  getCompJSONconfig.js        // 查詢組件倉庫內當前全部存在的組件配置
│  │  index.js            // 服務端總入口 
│  │  opPageJSON.js        // 存取頁面對應的配置JSON樹
│  │  
│  └─template            // 模板
│          index.ejs            // html渲染模板
│          page.json            // 頁面配置JSON樹
│          
└─src                // 建站平臺前端SDK
    │  context.js            // 全局狀態對象
    │  global.js            // 全局配置依賴
    │  reducer.js            // 全局狀態管理
    │  
    ├─edit                // 編輯器
    │  │  compile.js        // 編譯配置樹爲組件樹
    │  │  board.js            // 編輯器可視區域面板
    │  │  index.js            // 編輯器總入口
    │  │  menu.js            // 編輯器組件菜單
    │  │  option.js            // 編輯器屬性操做面板
    │  │  record.js            // 操做歷史記錄管理
    │  │  tree.js            // 搭建樹層級展現
    │  │  search.js        // 搜索頁面配置樹方法
    │  │  
    │  └─style            // 編輯器樣式
    │          
    └─page            // 預覽頁
        compile.js    // 渲染組件配置
        index.js        // 預覽頁總入口

結語


此項目對可視化建站的總體先後端流程有一個完整實現。基於此基礎上,能夠根據須要拓展定製化的編輯器功能、頁面渲染功能等。

因篇幅緣由文中縮減了不少代碼片斷,可是自己代碼量也很少,我儘量在每個方法都標有詳細的註釋說明。更多詳情能夠下載此項目,直接npm start根據指引操做

項目地址:https://github.com/yukilzw/web_channel
相關文章
相關標籤/搜索