vue-cli ui設計解析

大隻魚00發表於2018-12-06

前言

vue-cli 3.x帶來了ui控制檯的體驗,讓不熟悉cli命令的開發者能夠更快的上手,同時提供了強大的外掛擴充機制,可以以專案緯度定製自己的ui命令。近期也準備為架構方案提供ui的視覺化配置,對vue-cli中ui的設計邏輯進行了一定的瞭解,特此記錄,歡迎大家進行斧正

整體架構

整體架構圖搬運自vue-cl官網

vue-cli ui設計解析
node端用的apollo-graphql,前端毫無疑問是vue(- -)

執行vue ui可以啟動本地server,server相關引數和服務端啟動細節,不是本文討論範圍,就不作贅述了,啟動程式碼詳見:

github.com/vuejs/vue-c… github.com/Akryum/vue-…

plugin機制

上述架構中承擔至關重要的部分就是plugin,就是因為它暴露的API允許了開發者可以通過增強專案的配置和任務,也可以分享資料和程式間的通訊,下面將從一個進入專案開始解析,外掛是如何載入及應用。

open project

原始碼目錄:cli-ui/apollo-server/connectors/project.js

vue-cli ui是以專案為緯度進行管理的,在建立/匯入專案並進行相應專案後,將獲取專案相關的資訊(path),並儲存專案資訊用作下次預設開啟

// load plugins
...
// Save for next time
context.db.set('config.lastOpenProject', id).write()
複製程式碼

load plugins

原始碼目錄:cli-ui/apollo-server/connectors/plugins.js

獲取一個project的path資訊後,將獲取專案相關的plugins

// Load plugins
await plugins.list(project.path, context)
複製程式碼

ui外掛主要從三個地方獲取:

  1. 從專案package.json裡獲取外掛資訊這裡可以通過vuePlugins.resolveForm指定到其他目錄
let pkgContext = cwd.get()
// Custom package.json location
if (pkg.vuePlugins && pkg.vuePlugins.resolveFrom) {
pkgContext = path.resolve(cwd.get(), pkg.vuePlugins.resolveFrom)
pkg = folders.readPackage(pkgContext, context)
}
pkgStore.set(file, { pkgContext, pkg })

let plugins = []
plugins = plugins.concat(findPlugins(pkg.devDependencies || {}, file))
plugins = plugins.concat(findPlugins(pkg.dependencies || {}, file))
複製程式碼

findPlugins通過正則規則/^(@vue/|vue-|@[\w-]+/vue-)cli-plugin-/來過濾dependencies和devDependencies中的外掛@vue/cli-service作為特殊外掛優先載入

  1. 內建預設ui,位置在cli-ui/apollo-server/ui-default
  2. package資訊中指定的自定義ui外掛
// load custom ui pulgins
const { pkg, pkgContext } = pkgStore.get(file)
  if (pkg.vuePlugins && pkg.vuePlugins.ui) {
    const files = pkg.vuePlugins.ui
    if (Array.isArray(files)) {
      for (const file of files) {
        runPluginApi(pkgContext, pluginApi, context, file)
      }
    }
  }
複製程式碼

拿到plugins後,將開始逐個允許外掛,這裡會涉及到一個重要的api PluginApi,它是整個外掛機制執行的核心,提供hooks供外掛新增配置、任務、檢視等,原始碼位於cli-ui/apollo-server/api/PluginAPI.js

// run plugin api
function runPluginApi(id, pluginApi, context, filename = 'ui') {
    ...
    try{
        // 核心邏輯將pluginApi作為引數傳給各個模組外掛
        module(pluginApi)
    } catch(e) {
        
    }
}
複製程式碼

通過執行三類外掛,使得外掛可以通過pluginApi進行擴充

add client addons

客戶端addons通過pluginApi的addClientAddon新增

//外掛中註冊
module.exports = api => {
    api.addClientAddon({
        id: 'org.vue.webpack.client-addon',
        path: '@vue/cli-ui-addon-webpack/dist'
    })
}
複製程式碼

對應客戶端則會載入/_addon/org.vue.webpack.client-addon/index.js生產環境下,而完成這一邏輯的主要依賴,以下幾部分的設定

// 服務端儲存新增的addon
pluginApi.clientAddons.forEach(options => {
    clientAddons.add(options, context)  //儲存addons資訊 並監聽變化響應介面
})

// 服務端設定,express外掛cli-ui/cli-ui/apollo-server/server.js
module.exports = app => {
  ...
  app.use('/_addon/:id/*', clientAddons.serve)
  ...
}

// 外掛包打包設定,配置vue.config.js
module.exports = {
  ...clientAddonConfig({   // clientAddonConfig為預設開發設定,原始碼在cli-ui/index.js
    id: 'org.vue.webpack.client-addon',  //id作為檔案載入標識
    port: 8096 // port用作開發模式下服務的埠
  })
}
複製程式碼

最終客戶端通過介面載入對應的addon指令碼檔案,現在你可以在檢視中使用addon

api.describeTask({
  /* ... */
  // 額外的檢視 (例如 webpack dashboard)
  // 預設情況下,這是展示終端輸出的 'output' 檢視
  views: [
    {
      // 唯一的 ID
      id: 'org.vue.webpack.views.dashboard',
      // 按鈕文字
      label: 'Dashboard',
      // 按鈕圖示 (material-icons)
      icon: 'dashboard',
      // 載入的動態元件,會用 ClientAddonApi 進行註冊
      component: 'org.vue.webpack.components.dashboard'
    }
  ],
  // 展示任務詳情時預設選擇的檢視 (預設情況下就是 output)
  defaultView: 'org.vue.webpack.views.dashboard'
})
複製程式碼

而這裡的org.vue.webpack.components.dashboard其實就是對應我們載入的addon裡面註冊的vue元件

...
ClientAddonApi.component('org.vue.webpack.components.dashboard', WebpackDashboard)  //ClientAddonApi通過注入到windows作為全域性變數使用,用於元件註冊和載入
...
複製程式碼

add views

// Add views
for (const view of pluginApi.views) {
    await views.add({ view, project }, context)
}
複製程式碼

載入自定義檢視,這裡是指通過api.addView來新增的檢視,具體方式可以參見:cli.vuejs.org/zh/dev-guid…

這裡需要注意addView裡面的id和name均需要通過addClientAddon註冊過

register widget

// Register widgets
  for (const definition of pluginApi.widgetDefs) {
    await widgets.registerDefinition({ definition, project }, context)
  }
複製程式碼

註冊widget,可以為專案dashboard頁面新增自定義的ui外掛,通過registerWidget來進行註冊

module.exports = api => {
    const { registerWidget, onAction, setSharedData } = api.namespace('org.vue.widgets.') 
    
    registerWidget({
        id: 'welcome',
        title: 'org.vue.widgets.welcome.title',
        description: 'org.vue.widgets.welcome.description',
        icon: 'mood',
        component: 'org.vue.widgets.components.welcome',  //這裡的component也必須是已經在addon裡面註冊過的元件
        minWidth: 3,
        minHeight: 4,
        maxWidth: 3,
        maxHeight: 4,
        maxCount: 1
  })
}
複製程式碼

總結

通過各種檢視、wigdet的初始化定義,在客戶端請求資訊介面後,根據獲取到的資訊渲染對應的內容。除了外掛機制外,cli-ui還有許多地方值得借鑑,比如通過node-ipc實現程式間通訊、通過ShareData的設計實現不同server資料和客戶端資料的實時同步,後續有機會將會繼續分享

最後獻上官方cli-ui外掛開發連結:cli.vuejs.org/zh/dev-guid…

相關文章