UI 插件 API

2020-03-13 15:26 更新

這個(gè) cli-ui 暴露一個(gè) API,允許增強(qiáng)項(xiàng)目的配置和任務(wù),也可以分享數(shù)據(jù)和在進(jìn)程間進(jìn)行通信。

UI 插件架構(gòu)

UI 文件

在每個(gè)安裝好的 vue-cli 插件里,cli-ui 都會(huì)嘗試從其插件的根目錄加載一個(gè)可選的 ui.js 文件。注意你也可以使用文件夾 (例如 ui/index.js)。

該文件應(yīng)該導(dǎo)出一個(gè)函數(shù),函數(shù)會(huì)以 API 對(duì)象作為第一個(gè)參數(shù):

module.exports = api => {
  // 在這里使用 API...
}

警告

當(dāng)試圖在“項(xiàng)目插件 (Project plugins)”中獲取插件列表時(shí),這些文件將會(huì)被重新加載。點(diǎn)擊 UI 左側(cè)邊欄導(dǎo)航“項(xiàng)目插件 (Project plugins)”按鈕可以應(yīng)用更改。

這里是一個(gè)使用 UI API 的 vue-cli 插件的文件夾結(jié)構(gòu)示例:

- vue-cli-plugin-test
  - package.json
  - index.js
  - generator.js
  - prompts.js
  - ui.js
  - logo.png

項(xiàng)目本地的插件

如果你需要在項(xiàng)目里訪問插件 API 而不需要?jiǎng)?chuàng)建一個(gè)完整的插件,你可以在 package.json 文件中使用 vuePlugins.ui 選項(xiàng):

{
  "vuePlugins": {
    "ui": ["my-ui.js"]
  }
}

每個(gè)文件都需要暴露一個(gè)函數(shù),將插件 API 作為第一個(gè)參數(shù)攜帶。

開發(fā)模式

當(dāng)構(gòu)建你自己的插件時(shí),你可能想在開發(fā)環(huán)境下運(yùn)行 cli-ui,所以這樣運(yùn)行會(huì)輸出較為實(shí)用的日志:

vue ui --dev

或:

vue ui -D

項(xiàng)目的配置

配置 UI

你可以通過 api.describeConfig 方法添加一個(gè)項(xiàng)目配置。

首先你需要傳入一些信息:

api.describeConfig({
  // 唯一的配置 ID
  id: 'org.vue.eslintrc',
  // 展示名稱
  name: 'ESLint configuration',
  // 展示在名稱下方的描述
  description: 'Error checking & Code quality',
  // “更多信息 (More info)”鏈接
  link: 'https://eslint.org'
})

危險(xiǎn)

請(qǐng)確定為 id 設(shè)置正確的命名空間,因?yàn)樗枰缢胁寮3治ㄒ?。我們推薦使用反向域名記號(hào) (reverse domain name notation)

配置圖標(biāo)

可以是一個(gè) Material 圖標(biāo)代碼或一個(gè)自定義的圖片 (詳見公共靜態(tài)文件):

api.describeConfig({
  /* ... */
  // 配置圖標(biāo)
  icon: 'application_settings'
})

如果你沒有定義圖標(biāo),那就展示該插件可能存在的 logo (詳見 Logo)。

配置文件

默認(rèn)情況下,配置 UI 可能會(huì)讀寫一個(gè)或多個(gè)配置文件,例如 .eslintrc 和 vue.config.js。

你可以提供可能需要在用戶項(xiàng)目中檢測(cè)的文件:

api.describeConfig({
  /* ... */
  // 該配置所有可能的文件
  files: {
    // eslintrc.js
    eslint: {
      js: ['.eslintrc.js'],
      json: ['.eslintrc', '.eslintrc.json'],
      // 會(huì)從 `package.json` 讀取
      package: 'eslintConfig'
    },
    // vue.config.js
    vue: {
      js: ['vue.config.js']
    }
  },
})

支持的類型有:json、yaml、js、package。這個(gè)順序是很重要的:如果這項(xiàng)配置不存在,則會(huì)創(chuàng)建列表中的第一個(gè)文件。

展示配置提示符

使用 onRead 鉤子來返回一個(gè)提示符列表,用以配置展示:

api.describeConfig({
  /* ... */
  onRead: ({ data, cwd }) => ({
    prompts: [
      // 提示符對(duì)象
    ]
  })
})

這些提示符會(huì)展示在配置的詳情面板中。

查閱提示符了解更多信息。

這個(gè) data 對(duì)象包含了每個(gè)配置文件內(nèi)容的 JSON 結(jié)果。

例如,假設(shè)用戶在其項(xiàng)目中的 vue.config.js 有以下內(nèi)容:

module.exports = {
  lintOnSave: false
}

我們?cè)诓寮邢襁@樣聲明配置文件:

api.describeConfig({
  /* ... */
  // 該配置所有可能的文件
  files: {
    // vue.config.js
    vue: {
      js: ['vue.config.js']
    }
  },
})

則這個(gè) data 對(duì)象會(huì)是:

{
  // 文件
  vue: {
    // 文件數(shù)據(jù)
    lintOnSave: false
  }
}

多個(gè)文件的例子:如果我們?cè)谟脩舻捻?xiàng)目中添加以下 eslintrc.js 文件:

module.exports = {
  root: true,
  extends: [
    'plugin:vue/essential',
    '@vue/standard'
  ]
}

那么在我們的插件中將 files 選項(xiàng)改變成為:

api.describeConfig({
  /* ... */
  // 該配置所有可能的文件
  files: {
    // eslintrc.js
    eslint: {
      js: ['.eslintrc.js'],
      json: ['.eslintrc', '.eslintrc.json'],
      // 會(huì)從 `package.json` 讀取
      package: 'eslintConfig'
    },
    // vue.config.js
    vue: {
      js: ['vue.config.js']
    }
  },
})

則這個(gè) data 對(duì)象會(huì)是:

{
  eslint: {
    root: true,
    extends: [
      'plugin:vue/essential',
      '@vue/standard'
    ]
  },
  vue: {
    lintOnSave: false
  }
}

配置選項(xiàng)卡

你可以將這些提示符組織成為幾個(gè)選項(xiàng)卡:

api.describeConfig({
  /* ... */
  onRead: ({ data, cwd }) => ({
    tabs: [
      {
        id: 'tab1',
        label: 'My tab',
        // 可選的
        icon: 'application_settings',
        prompts: [
          // 提示符對(duì)象們
        ]
      },
      {
        id: 'tab2',
        label: 'My other tab',
        prompts: [
          // 提示符對(duì)象們
        ]
      }
    ]
  })
})

保存配置變更

使用 onWrite 鉤子將數(shù)據(jù)寫入配置文件 (或者執(zhí)行任何 Node.js 代碼):

api.describeConfig({
  /* ... */
  onWrite: ({ prompts, answers, data, files, cwd, api }) => {
    // ...
  }
})

參數(shù):

  • prompts: 當(dāng)前提示符們的運(yùn)行時(shí)對(duì)象 (詳見下方)
  • answers: 來自用戶輸入的回答數(shù)據(jù)
  • data: 從配置文件讀取的只讀的初始化數(shù)據(jù)
  • files: 被找到的文件的描述器 ({ type: 'json', path: '...' })
  • cwd: 當(dāng)前工作目錄
  • api: onWrite API (詳見下方)

提示符的運(yùn)行時(shí)對(duì)象:

{
  id: data.name,
  type: data.type,
  name: data.short || null,
  message: data.message,
  group: data.group || null,
  description: data.description || null,
  link: data.link || null,
  choices: null,
  visible: true,
  enabled: true,
  // 當(dāng)前值 (未被過濾的)
  value: null,
  // 如果用戶修改過了則為 true
  valueChanged: false,
  error: null,
  tabId: null,
  // 原始的 inquirer 提示符對(duì)象
  raw: data
}

onWrite API:

  • assignData(fileId, newData): 在寫入前使用 Object.assign 來更新配置文件。
  • setData(fileId, newData): newData 的每個(gè) key 在寫入之前都將會(huì)被深設(shè)置在配置數(shù)據(jù)上 (或當(dāng)值為 undefined 時(shí)被移除)。
  • async getAnswer(id, mapper): 為一個(gè)給定的提示符 id 獲取答復(fù)并通過可能提供了的 mapper 函數(shù) (例如 JSON.parse) 進(jìn)行 map 處理。

示例 (來自 ESLint 插件):

api.describeConfig({
  // ...

  onWrite: async ({ api, prompts }) => {
    // 更新 ESLint 規(guī)則
    const result = {}
    for (const prompt of prompts) {
      result[`rules.${prompt.id}`] = await api.getAnswer(prompt.id, JSON.parse)
    }
    api.setData('eslint', result)
  }
})

項(xiàng)目的任務(wù)

任務(wù) UI

任務(wù)是從項(xiàng)目 package.json 文件的 scripts 字段生成的。

因?yàn)橛?nbsp;api.describeTask 方法,你可以為任務(wù)“增強(qiáng)”額外的信息和鉤子:

api.describeTask({
  // 用于匹配腳本命令的 RegExp 對(duì)象,來選擇要被描述的任務(wù)
  match: /vue-cli-service serve/,
  description: 'Compiles and hot-reloads for development',
  // “More info”鏈接
  link: 'https://github.com/vuejs/vue-cli/blob/dev/docs/cli-service.md#serve'
})

任務(wù)圖標(biāo)

可以是一個(gè) Material 圖標(biāo)代碼或一個(gè)自定義的圖片 (詳見公共靜態(tài)文件):

api.describeTask({
  /* ... */
  // 任務(wù)圖標(biāo)
  icon: 'application_settings'
})

如果你沒有定義圖標(biāo),那就展示該插件可能存在的 logo (詳見 Logo)。

任務(wù)參數(shù)

你可以添加提示符來修改命令參數(shù)。它們會(huì)展示在一個(gè)“參數(shù)”模態(tài)框中。

Example:

api.describeTask({
  // ...

  // 選填參數(shù) (inquirer 提示符)
  prompts: [
    {
      name: 'open',
      type: 'confirm',
      default: false,
      description: 'Open browser on server start'
    },
    {
      name: 'mode',
      type: 'list',
      default: 'development',
      choices: [
        {
          name: 'development',
          value: 'development'
        },
        {
          name: 'production',
          value: 'production'
        },
        {
          name: 'test',
          value: 'test'
        }
      ],
      description: 'Specify env mode'
    }
  ]
})

詳見提示符。

任務(wù)鉤子

有一些鉤子是可用的:

  • onBeforeRun
  • onRun
  • onExit

例如,你可以將 (上述) 提示符的回答作為一個(gè)新參數(shù)添加到命令上:

api.describeTask({
  // ...

  // 鉤子
  // 在這里修改參數(shù)
  onBeforeRun: async ({ answers, args }) => {
    // 參數(shù)
    if (answers.open) args.push('--open')
    if (answers.mode) args.push('--mode', answers.mode)
    args.push('--dashboard')
  },
  // 任務(wù)運(yùn)行之后立即執(zhí)行
  onRun: async ({ args, child, cwd }) => {
    // child: Node 子進(jìn)程
    // cwd: 進(jìn)程所在目錄
  },
  onExit: async ({ args, child, cwd, code, signal }) => {
    // code: 退出碼
    // signal: 可能會(huì)被使用的殺進(jìn)程信號(hào)
  }
})

任務(wù)視圖

你可以在任務(wù)詳情面板中使用 ClientAddon API 展示自定義視圖:

api.describeTask({
  // ...

  // 額外的視圖 (例如 webpack 儀表盤)
  // 默認(rèn)情況下,這里是展示終端輸出的 `output` 視圖
  views: [
    {
      // 唯一 ID
      id: 'vue-webpack-dashboard-client-addon',
      // 按鈕文字
      label: 'Dashboard',
      // 按鈕圖標(biāo)
      icon: 'dashboard',
      // 要加載的動(dòng)態(tài)組件 (詳見下述“客戶端 addon”章節(jié))
      component: 'vue-webpack-dashboard'
    }
  ],
  // 展示任務(wù)詳情時(shí)默認(rèn)選擇的視圖 (默認(rèn)是 `output`)
  defaultView: 'vue-webpack-dashboard-client-addon'
})

詳見客戶端 addon。

新增任務(wù)

你也可以不使用 api.describeTask,而是通過 api.addTask 添加一個(gè) package.json 腳本中沒有的全新任務(wù)。這些任務(wù)只會(huì)出現(xiàn)在 cli UI 中。

你需要提供一個(gè) command 選項(xiàng)替代掉 match 選項(xiàng)。

示例:

api.addTask({
  // 必填
  name: 'inspect',
  command: 'vue-cli-service inspect',
  // 選填
  // 其余部分類似 `describeTask` 但是沒有 `match` 選項(xiàng)
  description: '...',
  link: 'https://github.com/vuejs/vue-cli/...',
  prompts: [ /* ... */ ],
  onBeforeRun: () => {},
  onRun: () => {},
  onExit: () => {},
  views: [ /* ... */ ],
  defaultView: '...'
})

警告

command 將會(huì)運(yùn)行一個(gè) Node 上下文。也就是說你可以像在 package.json 腳本中一樣調(diào)用 Node 的 bin 命令。

提示符

提示符對(duì)象必須是合法的 inquirer 對(duì)象。

不過你也可以添加下列額外的字段 (只會(huì)被 UI 使用的可選項(xiàng)):

{
  /* ... */
  // 用來將提示符按章節(jié)分組
  group: 'Strongly recommended',
  // 附加描述
  description: 'Enforce attribute naming style in template (`my-prop` or `myProp`)',
  // “More info”鏈接
  link: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/attribute-hyphenation.md',
}

支持的 inquirer 類型有:checkbox、confirm、input、password、list、rawlist。

此外,UI 還支持了僅在這里工作的特殊類型:

  • color:展示一個(gè)取色器。

Switch 示例

{
  name: 'open',
  type: 'confirm',
  default: false,
  description: 'Open the app in the browser'
}

Select 示例

{
  name: 'mode',
  type: 'list',
  default: 'development',
  choices: [
    {
      name: 'Development mode',
      value: 'development'
    },
    {
      name: 'Production mode',
      value: 'production'
    },
    {
      name: 'Test mode',
      value: 'test'
    }
  ],
  description: 'Build mode',
  link: 'https://link-to-docs'
}

Input 示例

{
  name: 'host',
  type: 'input',
  default: '0.0.0.0',
  description: 'Host for the development server'
}

Checkbox 示例

展示多個(gè) switch。

{
  name: 'lintOn',
  message: 'Pick additional lint features:',
  when: answers => answers.features.includes('linter'),
  type: 'checkbox',
  choices: [
    {
      name: 'Lint on save',
      value: 'save',
      checked: true
    },
    {
      name: 'Lint and fix on commit' + (hasGit() ? '' : chalk.red(' (requires Git)')),
      value: 'commit'
    }
  ]
}

取色器示例

{
  name: 'themeColor',
  type: 'color',
  message: 'Theme color',
  description: 'This is used to change the system UI color around the app',
  default: '#4DBA87'
}

提示符的改進(jìn)

在 vue-cli 插件中,你可能已經(jīng)有一個(gè) prompts.js 文件,在 (用 CLI 或 UI) 安裝該插件的時(shí)候詢問用戶一些問題。你可以向那些提示符對(duì)象額外添加只支持 UI 的上述字段,這樣的話如果用戶使用 UI 的話可以看到更多的信息。

警告

目前,那些不支持的 inquirer 類型不會(huì)在 UI 中正常工作。

客戶端 addon

客戶端 addon 是一個(gè)動(dòng)態(tài)加載到 cli-ui 中的 JS 包。用于加載自定義組件和路由。

創(chuàng)建一個(gè)客戶端 addon

推薦的創(chuàng)建一個(gè)客戶端 addon 的方式是通過 vue cli 創(chuàng)建一個(gè)新項(xiàng)目。你也可以在插件的子目錄或不同的 npm 包中這樣做。

作為開發(fā)依賴安裝 @vue/cli-ui。

然后添加一個(gè) vue.config.js 文件并附帶以下內(nèi)容:

const { clientAddonConfig } = require('@vue/cli-ui')

module.exports = {
  ...clientAddonConfig({
    id: 'org.vue.webpack.client-addon',
    // 開發(fā)環(huán)境端口 (默認(rèn)值 8042)
    port: 8042
  })
}

這個(gè) clientAddonConfig 方法將會(huì)生成需要的 vue-cli 配置。除此之外,它會(huì)禁用 CSS extraction 并將代碼輸出到在客戶端 addon 目錄的 ./dist/index.js。

危險(xiǎn)

請(qǐng)確定為 id 設(shè)置正確的命名空間,因?yàn)樗枰缢胁寮3治ㄒ?。我們推薦使用反向域名記號(hào) (reverse domain name notation)

然后修改 .eslintrc.json 文件以添加一些允許的全局對(duì)象:

{
  // ...
  "globals": {
    "ClientAddonApi": false,
    "mapSharedData": false,
    "Vue": false
  }
}

你現(xiàn)在可以在開發(fā)環(huán)境下運(yùn)行 serve 腳本,也可以在準(zhǔn)備發(fā)布時(shí)運(yùn)行 build 腳本。

ClientAddonApi

在客戶端 addon 資源中打開 main.js 文件并刪除所有代碼。

警告

別在客戶端 addon 源文件總導(dǎo)入 Vue ,請(qǐng)從瀏覽器 window 使用全局的 Vue 對(duì)象。

這里是一個(gè) main.js 的示例代碼:

import VueProgress from 'vue-progress-path'
import WebpackDashboard from './components/WebpackDashboard.vue'
import TestView from './components/TestView.vue'

// 你可以安裝額外的 Vue 插件
// 使用全局的 'Vue' 變量
Vue.use(VueProgress, {
  defaultShape: 'circle'
})

// 注冊(cè)一個(gè)自定義組件
// (工作原理類似 'Vue.component')
ClientAddonApi.component('org.vue.webpack.components.dashboard', WebpackDashboard)

// 在 vue-router 中為 /addon/<id> 添加子路由。
// 例如,addRoutes('foo', [ { path: '' }, { path: 'bar' } ])
// 將會(huì)向路由器添加 /addon/foo/ 和 /addon/foo/bar。
// 我們?cè)诖擞?'test-webpack-route' 名稱創(chuàng)建一個(gè)新的 '/addon/vue-webpack/' 路由
ClientAddonApi.addRoutes('org.vue.webpack', [
  { path: '', name: 'org.vue.webpack.routes.test', component: TestView }
])

// 你可以翻譯插件組件
// (通過使用 vue-i18n) 加載語言文件
const locales = require.context('./locales', true, /[a-z0-9]+\.json$/i)
locales.keys().forEach(key => {
  const locale = key.match(/([a-z0-9]+)\./i)[1]
  ClientAddonApi.addLocalization(locale, locales(key))
})

危險(xiǎn)

請(qǐng)確定為 id 設(shè)置正確的命名空間,因?yàn)樗枰缢胁寮3治ㄒ?。我們推薦使用反向域名記號(hào) (reverse domain name notation)。

cli-ui 在 window 作用域內(nèi)注冊(cè)了 Vue 和 ClientAddonApi 作為全局變量。

你可以在自己的組件里使用 @vue/ui 和 @vue/cli-ui 所有的組件和 CSS class 以保持樣式和體驗(yàn)的一致性。你也可以用內(nèi)置的 vue-i18n 翻譯字符串。

注冊(cè)客戶端 addon

回到 ui.js 文件,使用 api.addClientAddon 方法并帶一個(gè)指向構(gòu)建后的文件夾的 require 字符串:

api.addClientAddon({
  id: 'org.vue.webpack.client-addon',
  // 包含構(gòu)建出來的 JS 文件的文件夾
  path: '@vue/cli-ui-addon-webpack/dist'
})

這會(huì)使用 Node.js 的 require.resolve API 查找文件夾并為從客戶端 addon 構(gòu)建的文件 index.js 啟動(dòng)一個(gè)服務(wù)器。

或者當(dāng)開發(fā)插件時(shí)指定一個(gè) URL (理想中你需要在 Vue 的測(cè)試項(xiàng)目的 vue-cli-ui.js 中做這些)

// 用于開發(fā)環(huán)境
// 如果已經(jīng)在插件中定義過,則會(huì)覆寫路徑
api.addClientAddon({
  id: 'org.vue.webpack.client-addon',
  // 使用你之前配置過低同樣的端口
  url: 'http://localhost:8042/index.js'
})

使用客戶端 addon

現(xiàn)在你可以在這些視圖中使用客戶端 addon 了。例如,你可以在一個(gè)被描述的任務(wù)中指定一個(gè)視圖:

api.describeTask({
  /* ... */
  // 額外的視圖 (例如 webpack dashboard)
  // 默認(rèn)情況下,這是展示終端輸出的 'output' 視圖
  views: [
    {
      // 唯一的 ID
      id: 'org.vue.webpack.views.dashboard',
      // 按鈕文字
      label: 'Dashboard',
      // 按鈕圖標(biāo) (material-icons)
      icon: 'dashboard',
      // 加載的動(dòng)態(tài)組件,會(huì)用 ClientAddonApi 進(jìn)行注冊(cè)
      component: 'org.vue.webpack.components.dashboard'
    }
  ],
  // 展示任務(wù)詳情時(shí)默認(rèn)選擇的視圖 (默認(rèn)情況下就是 output)
  defaultView: 'org.vue.webpack.views.dashboard'
})

這是一個(gè)客戶端 addon 代碼,注冊(cè)了 `'org.vue.webpack.components.dashboard' 組件 (像我們之前看到的一樣):

/* 在 `main.js` 中 */
// 導(dǎo)入組件
import WebpackDashboard from './components/WebpackDashboard.vue'
// 注冊(cè)自定義組件
// (工作原理類似 'Vue.component')
ClientAddonApi.component('org.vue.webpack.components.dashboard', WebpackDashboard)

任務(wù)視圖示例

自定義視圖

你可以使用 api.addView 方法在標(biāo)準(zhǔn)的“Project plugins”、“Project configuration”和“Project tasks”之下添加一個(gè)新的視圖:

api.addView({
  // 唯一的 id
  id: 'org.vue.webpack.views.test',

  // 路由名稱 (來自 Vue Router)
  // 使用 'ClientAddonApi.addRoutes' 方法中相同的名字 (詳見之前的客戶端 addon 章節(jié))
  name: 'org.vue.webpack.routes.test',

  // 按鈕圖標(biāo) (material-icons)
  icon: 'pets',
  // 你也可以指定一個(gè)自定義圖片 (詳見之前的公共靜態(tài)文件章節(jié)):
  // icon: 'http://localhost:4000/_plugin/%40vue%2Fcli-service/webpack-icon.svg',

  // 按鈕的提示文字
  tooltip: 'Test view from webpack addon'
})

這里是注冊(cè)了 'org.vue.webpack.routes.test' 的客戶端 addon 里的代碼 (之前已經(jīng)見過了):

/* 在 `main.js` 里 */
// 導(dǎo)入組件
import TestView from './components/TestView.vue'
// 在 vue-router 中為 /addon/<id> 添加子路由
// 例如,addRoutes('foo', [ { path: '' }, { path: 'bar' } ])
// 將為 Vue Router 添加 /addon/foo/ 和 /addon/foo/bar 路由。
// 我們這里創(chuàng)建一個(gè)新的 '/addon/vue-webpack/' 路由,并命名為 'test-webpack-route'。
ClientAddonApi.addRoutes('org.vue.webpack', [
  { path: '', name: 'org.vue.webpack.routes.test', component: TestView }
])

自定義視圖示例

共享的數(shù)據(jù)

一種簡(jiǎn)易的自定義組件之間通過共享的數(shù)據(jù)互通信息的方式。

例如,webpack 儀表盤在 UI 客戶端和 UI 服務(wù)端之間通過這個(gè) API 共享了構(gòu)建的統(tǒng)計(jì)信息。

在插件 ui.js (Node.js) 中:

// 設(shè)置或更新
api.setSharedData('com.my-name.my-variable', 'some-data')

// 獲取
const sharedData = api.getSharedData('com.my-name.my-variable')
if (sharedData) {
  console.log(sharedData.value)
}

// 移除
api.removeSharedData('com.my-name.my-variable')

// 偵聽變化
const watcher = (value, id) => {
  console.log(value, id)
}
api.watchSharedData('com.my-name.my-variable', watcher)
// 取消偵聽
api.unwatchSharedData('com.my-name.my-variable', watcher)

// 帶命名空間的版本
const {
  setSharedData,
  getSharedData,
  removeSharedData,
  watchSharedData,
  unwatchSharedData
} = api.namespace('com.my-name.')

危險(xiǎn)

請(qǐng)確定為 id 設(shè)置正確的命名空間,因?yàn)樗枰缢胁寮3治ㄒ弧N覀兺扑]使用反向域名記號(hào) (reverse domain name notation)。

在其自定義組件中:

// Vue 組件
export default {
  // 同步共享的數(shù)據(jù)
  sharedData () {
    return {
      // 你可以在模板中使用 `myVariable`
      myVariable: 'com.my-name.my-variable'
      // 也可以映射帶命名空間的共享數(shù)據(jù)
      ...mapSharedData('com.my-name.', {
        myVariable2: 'my-variable2'
      })
    }
  },

  // 手動(dòng)方法
  async created () {
    const value = await this.$getSharedData('com.my-name.my-variable')

    this.$watchSharedData(`com.my-name.my-variable`, value => {
      console.log(value)
    })

    await this.$setSharedData('com.my-name.my-variable', 'new-value')
  }
}

如果你使用了 sharedData 選項(xiàng),共享的數(shù)據(jù)就可以一個(gè)相應(yīng)的屬性被賦值時(shí)進(jìn)行更新。

<template>
  <VueInput v-model="message"/>
</template>

<script>
export default {
  sharedData: {
    // 將會(huì)在服務(wù)端同步 'my-message' 共享的數(shù)據(jù)
    message: 'com.my-name.my-message'
  }
}
</script>

例如在創(chuàng)建一個(gè)設(shè)置組件時(shí),這個(gè)特性是非常有用的。

插件的 action

插件的 action 就是在 cli-ui (瀏覽器) 和插件 (Node.js) 直接的調(diào)用。

例如,你可能有一個(gè)自定義組件里的按鈕 (詳見客戶端 addon),這個(gè)按鈕會(huì)通過這個(gè) API 向服務(wù)端調(diào)用一些 Node.js 代碼。

在插件 (Node.js) 的 ui.js 文件里,你可以從 PluginApi 使用兩個(gè)方法:

// 調(diào)用一個(gè) action
api.callAction('com.my-name.other-action', { foo: 'bar' }).then(results => {
  console.log(results)
}).catch(errors => {
  console.error(errors)
})
// 監(jiān)聽一個(gè) action
api.onAction('com.my-name.test-action', params => {
  console.log('test-action called', params)
})

危險(xiǎn)

請(qǐng)確定為 id 設(shè)置正確的命名空間,因?yàn)樗枰缢胁寮3治ㄒ?。我們推薦使用反向域名記號(hào) (reverse domain name notation)。

你可以通過 api.namespace 使用帶命名空間的版本 (類似共享的數(shù)據(jù)):

const { onAction, callAction } = api.namespace('com.my-name.')

在客戶端 addon 組件 (瀏覽器) 中,你可以訪問 $onPluginActionCalled、$onPluginActionResolved 和 $callPluginAction:

// Vue 組件
export default {
  created () {
    this.$onPluginActionCalled(action => {
      // 當(dāng) action 被調(diào)用時(shí)
      // 且在運(yùn)行之前
      console.log('called', action)
    })
    this.$onPluginActionResolved(action => {
      // 當(dāng) action 運(yùn)行完畢之后
      console.log('resolved', action)
    })
  },

  methods: {
    testPluginAction () {
      // 調(diào)用一個(gè)插件的 action
      this.$callPluginAction('com.my-name.test-action', {
        meow: 'meow'
      })
    }
  }
}

進(jìn)程間通信 (IPC)

IPC 就是進(jìn)程間通信 (Inter-Process Communication) 的縮寫。該系統(tǒng)允許你輕松的從子進(jìn)程 (例如任務(wù)) 發(fā)送消息,并且輕量快速。

為了在 webpack 儀表盤 UI 上展示數(shù)據(jù),@vue/cli-service 的 serve 和 build 命令會(huì)在 --dashboard 參數(shù)被傳入時(shí)向 cli-ui Node.js 服務(wù)器發(fā)送 IPC 消息。

在進(jìn)程代碼中 (可以是一個(gè) webpack 插件或一個(gè) Node.js 的任務(wù)腳本),你可以使用 @vue/cli-shared-utils 中的 IpcMessenger 類:

const { IpcMessenger } = require('@vue/cli-shared-utils')

// 創(chuàng)建一個(gè)新的 IpcMessenger 實(shí)例
const ipc = new IpcMessenger()

function sendMessage (data) {
  // 發(fā)送一條消息給 cli-ui 服務(wù)器
  ipc.send({
    'com.my-name.some-data': {
      type: 'build',
      value: data
    }
  })
}

function messageHandler (data) {
  console.log(data)
}

// 監(jiān)聽消息
ipc.on(messageHandler)

// 不再監(jiān)聽
ipc.off(messageHandler)

function cleanup () {
  // 從 IPC 網(wǎng)絡(luò)斷開連接
  ipc.disconnect()
}

手動(dòng)連接:

const ipc = new IpcMessenger({
  autoConnect: false
})

// 這條消息會(huì)被放入隊(duì)列
ipc.send({ ... })

ipc.connect()

閑時(shí)自動(dòng)斷開連接 (在沒有任何消息一段時(shí)間之后):

const ipc = new IpcMessenger({
  disconnectOnIdle: true,
  idleTimeout: 3000 // 默認(rèn)值
})

ipc.send({ ... })

setTimeout(() => {
  console.log(ipc.connected) // false
}, 3000)

連接到另一個(gè) IPC 網(wǎng)絡(luò):

const ipc = new IpcMessenger({
  networkId: 'com.my-name.my-ipc-network'
})

在一個(gè) vue-cli 插件的 ui.js 文件中,你可以使用 ipcOn、ipcOff 和 ipcSend 方法:

function onWebpackMessage ({ data: message }) {
  if (message['com.my-name.some-data']) {
    console.log(message['com.my-name.some-data'])
  }
}

// 監(jiān)聽任何 IPC 消息
api.ipcOn(onWebpackMessage)

// 不監(jiān)聽任何消息
api.ipcOff(onWebpackMessage)

// 向所有已連接的 IpcMessenger 實(shí)例發(fā)送一條消息
api.ipcSend({
  webpackDashboardMessage: {
    foo: 'bar'
  }
})

本地存儲(chǔ)

一個(gè)插件可以從 UI 服務(wù)器本地的 lowdb 數(shù)據(jù)庫保存和加載數(shù)據(jù)。

// 向本地的數(shù)據(jù)庫存入一個(gè)值
api.storageSet('com.my-name.an-id', { some: 'value' })

// 從本地的數(shù)據(jù)庫取回一個(gè)值
console.log(api.storageGet('com.my-name.an-id'))

// 完整的 lowdb 實(shí)例
api.db.get('posts')
  .find({ title: 'low!' })
  .assign({ title: 'hi!'})
  .write()

// 帶命名空間的輔助函數(shù)
const { storageGet, storageSet } = api.namespace('my-plugin.')

危險(xiǎn)

請(qǐng)確定為 id 設(shè)置正確的命名空間,因?yàn)樗枰缢胁寮3治ㄒ?。我們推薦使用反向域名記號(hào) (reverse domain name notation)。

Notification

你可以基于用戶操作系統(tǒng)的通知系統(tǒng)展示通知:

api.notify({
  title: 'Some title',
  message: 'Some message',
  icon: 'path-to-icon.png'
})

這里有一些內(nèi)建的圖標(biāo);

  • 'done'
  • 'error'

進(jìn)度界面

你可以用一些文字或進(jìn)度條來展示進(jìn)度界面:

api.setProgress({
  status: 'Upgrading...',
  error: null,
  info: 'Step 2 of 4',
  progress: 0.4 // 從 0 到 1, -1 表示隱藏進(jìn)度條
})

移除進(jìn)度界面:

api.removeProgress()

鉤子

鉤子可以用來響應(yīng)某些 cli-ui 的事件。

onProjectOpen

當(dāng)插件在當(dāng)前項(xiàng)目中第一次被加載時(shí)觸發(fā)。

api.onProjectOpen((project, previousProject) => {
  // 重置數(shù)據(jù)
})

onPluginReload

當(dāng)插件被重新加載時(shí)觸發(fā)。

api.onPluginReload((project) => {
  console.log('plugin reloaded')
})

onConfigRead

當(dāng)一個(gè)配置界面被打開或刷新時(shí)觸發(fā)。

api.onConfigRead(({ config, data, onReadData, tabs, cwd }) => {
  console.log(config.id)
})

onConfigWrite

當(dāng)用戶在保存界面里保存時(shí)觸發(fā)。

api.onConfigWrite(({ config, data, changedFields, cwd }) => {
  // ...
})

onTaskOpen

當(dāng)用戶打開一項(xiàng)任務(wù)的詳情面板時(shí)觸發(fā)。

api.onTaskOpen(({ task, cwd }) => {
  console.log(task.id)
})

onTaskRun

當(dāng)用戶運(yùn)行一項(xiàng)任務(wù)時(shí)觸發(fā)。

api.onTaskRun(({ task, args, child, cwd }) => {
  // ...
})

onTaskExit

當(dāng)一項(xiàng)任務(wù)退出時(shí)觸發(fā)。不論任務(wù)成功或失敗它都會(huì)觸發(fā)。

api.onTaskExit(({ task, args, child, signal, code, cwd }) => {
  // ...
})

onViewOpen

當(dāng)用戶打開一個(gè)視圖 (如 'Plugins'、'Configurations' 或 'Tasks') 時(shí)觸發(fā)。

api.onViewOpen(({ view, cwd }) => {
  console.log(view.id)
})

建議

這里的建議是指為用戶提議執(zhí)行 action 的按鈕。它們展示在界面的頂欄上。例如我們可以放一個(gè)按鈕,在應(yīng)用里沒有檢測(cè)到 Vue Router 包的時(shí)候建議將其安裝。

api.addSuggestion({
  id: 'com.my-name.my-suggestion',
  type: 'action', // 必填 (未來會(huì)加入更多類型)
  label: 'Add vue-router',
  // 該消息會(huì)展示在一個(gè)詳情模態(tài)框里
  message: 'A longer message for the modal',
  link: 'http://link-to-docs-in-the-modal',
  // 可選的圖片
  image: '/_plugin/my-package/screenshot.png',
  // 當(dāng)該項(xiàng)建議被用戶激活時(shí)調(diào)用的函數(shù)
  async handler () {
    // ...
    return {
      // 默認(rèn)移除這個(gè)按鈕
      keep: false
    }
  }
})

危險(xiǎn)

請(qǐng)確定為 id 設(shè)置正確的命名空間,因?yàn)樗枰缢胁寮3治ㄒ弧N覀兺扑]使用反向域名記號(hào) (reverse domain name notation)。

UI 建議

之后你可以移除這項(xiàng)建議:

api.removeSuggestion('com.my-name.my-suggestion')

你也可以給建議附帶 actionLink,當(dāng)用戶激活它時(shí),會(huì)換做打開一個(gè)頁面:

api.addSuggestion({
  id: 'com.my-name.my-suggestion',
  type: 'action', // Required
  label: 'Add vue-router',
  // 打開一個(gè)新標(biāo)簽
  actionLink: 'https://vuejs.org/'
})

通常情況下,你會(huì)選擇適當(dāng)?shù)纳舷挛挠勉^子來展示建議:

const ROUTER = 'vue-router-add'

api.onViewOpen(({ view }) => {
  if (view.id === 'vue-project-plugins') {
    if (!api.hasPlugin('vue-router')) {
      api.addSuggestion({
        id: ROUTER,
        type: 'action',
        label: 'org.vue.cli-service.suggestions.vue-router-add.label',
        message: 'org.vue.cli-service.suggestions.vue-router-add.message',
        link: 'https://router.vuejs.org/',
        async handler () {
          await install(api, 'vue-router')
        }
      })
    }
  } else {
    api.removeSuggestion(ROUTER)
  }
})

在這個(gè)例子中,如果 Vue Router 沒有安裝好,我們只會(huì)在插件視圖中展示安裝 Vue Router 的建議。

注意

addSuggestion 和 removeSuggestion 可以通過 api.namespace() 指定命名空間。

其它方法

hasPlugin

如果項(xiàng)目使用了該插件則返回 true。

api.hasPlugin('eslint')
api.hasPlugin('apollo')
api.hasPlugin('vue-cli-plugin-apollo')

getCwd

獲取當(dāng)前工作目錄。

api.getCwd()

resolve

在當(dāng)前工程下解析一個(gè)文件:

api.resolve('src/main.js')

getProject

得出當(dāng)前打開的工程。

api.getProject()

公共靜態(tài)文件

你可能需要在 cli-ui 內(nèi)建的 HTTP 服務(wù)器上暴露一些靜態(tài)文件 (通常是為自定義視圖指定圖標(biāo))。

在插件包根目錄里可選的放置一個(gè) ui-public 文件夾,這個(gè)文件夾里的任何文件都會(huì)暴露至 /_plugin/:id/* 的 HTTP 路由。

例如,如果你將 my-logo.png 文件放置到 vue-cli-plugin-hello/ui-public/ 文件夾,那么 cli-ui 加載插件的時(shí)候可以通過 /_plugin/vue-cli-plugin-hello/my-logo.png 這個(gè) URL 來訪問它。

api.describeConfig({
  /* ... */
  // 自定義圖片
  icon: '/_plugin/vue-cli-plugin-hello/my-logo.png'
})


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)