91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue-cli@3.0 插件系統簡析

發布時間:2020-09-23 18:09:46 來源:腳本之家 閱讀:288 作者:滴滴WebApp架構組 欄目:web開發

Vue-cli@3.0 是一個全新的 Vue 項目腳手架。不同于 1.x/2.x 基于模板的腳手架,Vue-cli@3.0 采用了一套基于插件的架構,它將部分核心功能收斂至 CLI 內部,同時對開發者暴露可拓展的 API 以供開發者對 CLI 的功能進行靈活的拓展和配置。接下來我們就通過 Vue-cli@3.0 的源碼來看下這套插件架構是如何設計的。

整個插件系統當中包含2個重要的組成部分:

  • @vue/cli,提供 cli 命令服務,例如vue create創建一個新的項目;
  • @vue/cli-service,提供了本地開發構建服務。

@vue/cli-service

當你使用 vue create <project-name> 創建一個新的 Vue 項目,你會發現生成的項目相較于 1.x/2.x 初始化一個項目時從遠程拉取的模板發生了很大的變化,其中關于 webpack 相關的配置以及 npm script 都沒有在模板里面直接暴露出來,而是提供了新的 npm script:

// package.json
"scripts": {
 "serve": "vue-cli-service serve",
 "build": "vue-cli-service build",
 "lint": "vue-cli-service lint"
}

前 2 個腳本命令是項目本地安裝的 @vue/cli-service 所提供的基于 webpack 及相關的插件進行封裝的本地開發/構建的服務。@vue/cli-service 將 webpack 及相關插件提供的功能都收斂到 @vue/cli-service 內部來實現。

這 2 個命令對應于 node_modules/@vue/cli-service/lib/commands 下的 serve.js 和 build/index.js。

在 serve.js 和 build/index.js 的內部分別暴露了一個函數及一個 defaultModes 屬性供外部來使用。 事實上這兩者都是作為 built-in(內置)插件來供 vue-cli-service 來使用的 。

說到這里那么就來看看 @vue/cli-service 內部是如何搭建整個插件系統的。就拿執行 npm run serve 啟動本地開發服務來說,大概流程是這樣的:

Vue-cli@3.0 插件系統簡析

首先來看下 @vue/cli-service 提供的 cli 啟動入口服務(@vue/cli-service/bin/vue-cli-service.js):

#!/usr/bin/env node
const semver = require('semver')
const { error } = require('@vue/cli-shared-utils')
const Service = require('../lib/Service') // 引入 Service 基類
const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd()) // 實例化 service
const rawArgv = process.argv.slice(2)
const args = require('minimist')(rawArgv)
const command = args._[0]
service.run(command, args, rawArgv).catch(err => { // 開始執行對應的 service 服務
 error(err)
 process.exit(1)
})

看到這里你會發現在 bin 里面并未提供和本地開發 serve 相關的服務,事實上在項目當中本地安裝的 @vue/cli-service 提供的不管是內置的還是插件提供的服務都是動態的去完成相關 CLI 服務的注冊。

在 lib/Service.js 內部定義了一個核心的類 Service,它作為 @vue/cli 的運行時的服務而存在。在執行 npm run serve 后,首先完成 Service 的實例化工作:

class Service {
 constructor(context) {
 ...
 this.webpackChainFns = [] // 數組內部每項為一個fn
 this.webpackRawConfigFns = [] // 數組內部每項為一個 fn 或 webpack 對象字面量配置項
 this.devServerConfigFns = []
 this.commands = {} // 緩存動態注冊 CLI 命令
 ...
 this.plugins = this.resolvePlugins(plugins, useBuiltIn) // 完成插件的加載
 this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => { // 緩存不同 CLI 命令執行時所對應的mode值
  return Object.assign(modes, defaultModes)
 }, {}) 
 }
}

在實例化 Service 的過程當中完成了兩個比較重要的工作:

加載插件

將插件提供的不同命令服務所使用的 mode 進行緩存

當 Service 實例化完成后,調用實例上的 run 方法來啟動對應的 CLI 命令所提供的服務。

async run (name, args = {}, rawArgv = []) {
 const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])
 // load env variables, load user config, apply plugins
 // 執行所有被加載進來的插件
 this.init(mode)
 ...
 const { fn } = command
 return fn(args, rawArgv) // 開始執行對應的 cli 命令服務
}
init (mode = process.env.VUE_CLI_MODE) {
 ...
 // 執行plugins
 // apply plugins.
 this.plugins.forEach(({ id, apply }) => {
 // 傳入一個實例化的PluginAPI實例,插件名作為插件的id標識,在插件內部完成注冊 cli 命令服務和 webpack 配置的更新的工作
 apply(new PluginAPI(id, this), this.projectOptions)
 })
 ...
 // apply webpack configs from project config file
 if (this.projectOptions.chainWebpack) {
 this.webpackChainFns.push(this.projectOptions.chainWebpack)
 }
 if (this.projectOptions.configureWebpack) {
 this.webpackRawConfigFns.push(this.projectOptions.configureWebpack)
 }
}

接下來我們先看下 @vue/cli-service 當中的 Service 實例化的過程:通過 resolvePlugins 方法去完成插件的加載工作:

resolvePlugins(inlinePlugins, useBuiltIn) {
 const idToPlugin = id => ({
  id: id.replace(/^.\//, 'built-in:'),
  apply: require(id) // 加載對應的插件
 })
 let plugins
 // @vue/cli-service內部提供的插件
 const builtInPlugins = [
  './commands/serve',
  './commands/build',
  './commands/inspect',
  './commands/help',
  // config plugins are order sensitive
  './config/base',
  './config/css',
  './config/dev',
  './config/prod',
  './config/app'
 ].map(idToPlugin)
 if (inlinePlugins) {
  plugins = useBuiltIn !== false
  ? builtInPlugins.concat(inlinePlugins)
  : inlinePlugins
 } else {
  // 加載項目當中使用的插件
  const projectPlugins = Object.keys(this.pkg.devDependencies || {})
  .concat(Object.keys(this.pkg.dependencies || {}))
  .filter(isPlugin)
  .map(idToPlugin)
  plugins = builtInPlugins.concat(projectPlugins)
 }
 // Local plugins
 if (this.pkg.vuePlugins && this.pkg.vuePlugins.service) {
  const files = this.pkg.vuePlugins.service
  if (!Array.isArray(files)) {
  throw new Error(`Invalid type for option 'vuePlugins.service', expected 'array' but got ${typeof files}.`)
  }
  plugins = plugins.concat(files.map(file => ({
  id: `local:${file}`,
  apply: loadModule(file, this.pkgContext)
  })))
 }
 return plugins
 }

在這個 resolvePlugins 方法當中,主要完成了對于 @vue/cli-service 內部提供的插件以及項目應用(package.json)當中需要使用的插件的加載,并將對應的插件進行緩存。在其提供的內部插件當中又分為兩類:

'./commands/serve'
'./commands/build'
'./commands/inspect'
'./commands/help'

這一類插件在內部動態注冊新的 CLI 命令,開發者即可通過 npm script 的形式去啟動對應的 CLI 命令服務。

'./config/base'
'./config/css'
'./config/dev'
'./config/prod'
'./config/app'

這一類插件主要是完成 webpack 本地編譯構建時的各種相關的配置。@vue/cli-service 將 webpack 的開發構建功能收斂到內部來完成。

插件加載完成,開始調用 service.run 方法,在這個方法內部開始執行所有被加載的插件:

this.plugins.forEach(({ id, apply }) => {
 apply(new PluginAPI(id, this), this.projectOptions)
 })

在每個插件執行的過程中,接收到的第一個參數都是 PluginAPI 的實例,PluginAPI 也是整個 @vue/cli-service 服務當中一個核心的基類:

class PluginAPI {
 constructor (id, service) {
 this.id = id   // 對應這個插件名
 this.service = service // 對應 Service 類的實例(單例)
 }
 ...
 registerCommand (name, opts, fn) { // 注冊自定義 cli 命令
 if (typeof opts === 'function') {
  fn = opts
  opts = null
 }
 this.service.commands[name] = { fn, opts: opts || {}}
 }
 chainWebpack (fn) {  // 緩存變更的 webpack 配置
 this.service.webpackChainFns.push(fn)
 }
 configureWebpack (fn) { // 緩存變更的 webpack 配置
 this.service.webpackRawConfigFns.push(fn)
 }
 ...
}

每個由 PluginAPI 實例化的 api 實例都提供了:

  • 注冊 cli 命令服務( api.registerCommand )
  • 通過 api 形式去更新的 webpack 配置( api.chainWebpack )
  • 通過 raw 配置形式去更新的 webpack 配置( api.configureWebpack ),與 api.chainWebpack 提供的鏈式 api 操作 webpack 配置的方式不同, api.configureWebpack 可接受raw式的配置形式,并通過 webpack-merge 對 webpack 配置進行合并。
  • resolve wepack 配置( api.resolveWebpackConfig ),調用之前通過 chainWebpack 和 configureWebpack 上完成的對于 webpack 配置的改造,并生成最終的 webpack 配置
  • ...

首先我們來看下 @vue/cli-service 提供的關于動態注冊 CLI 服務的插件,拿 serve 服務( ./commands/serve )來說:

// commands/serve
module.exports = (api, options) => {
 api.registerCommand(
 'serve',
 {
  description: 'start development server',
  usage: 'vue-cli-service serve [options] [entry]',
  options: {
  '--open': `open browser on server start`,
  '--copy': `copy url to clipboard on server start`,
  '--mode': `specify env mode (default: development)`,
  '--host': `specify host (default: ${defaults.host})`,
  '--port': `specify port (default: ${defaults.port})`,
  '--https': `use https (default: ${defaults.https})`,
  '--public': `specify the public network URL for the HMR client`
  }
 },
 async function serve(args) {
  // do something
 }
 )
}

./commands/serve 對外暴露一個函數,接收到的第一個參數 PluginAPI 的實例 api,并通過 api 提供的 registerCommand 方法來完成 CLI 命令(即 serve 服務)的注冊。

再來看下 @vue/cli-service 內部提供的關于 webpack 配置的插件( ./config/base ):

module.exports = (api, options) => {
 api.chainWebpack(webpackConfig => {
 webpackConfig.module
  .rule('vue')
  .test(/\.vue$/)
  .use('cache-loader')
  .loader('cache-loader')
  .options(vueLoaderCacheConfig)
  .end()
  .use('vue-loader')
  .loader('vue-loader')
  .options(
  Object.assign(
   {
   compilerOptions: {
    preserveWhitespace: false
   }
   },
   vueLoaderCacheConfig
  )
  )
 })
}

這個插件完成了 webpack 的基本配置內容,例如 entry、output、加載不同文件類型的 loader 的配置。 不同于之前使用的配置式的 webpack 使用方式,@vue/cli-service 默認使用 webpack-chain( 鏈接請戳我 ) 來完成 webpack 配置的修改 。這種方式也使得 webpack 的配置更加靈活,當你的項目遷移至 @vue/cli@3.0,使用的 webpack 插件也必須要使用 API 式的配置,同時插件不僅僅要提供插件自身的功能,同時也需要幫助調用方完成插件的注冊等工作。

@vue/cli-service 將基于 webpack 的本地開發構建配置收斂至內部來實現,當你沒有特殊的開發構建需求的時候,內部配置可以開箱即用,不用開發者去關心一些細節。當然在實際團隊開發當中,內部配置肯定是無法滿足的,得益于 @vue-cli@3.0 的插件構建設計,開發者不需要將內部的配置進行 Eject,而是直接使用 @vue/cli-service 暴露出來的 API 去完成對于特殊的開發構建需求。

以上介紹了 @vue/cli-service 插件系統當中幾個核心的模塊,即:

Service.js 提供服務的基類,它提供了 @vue/cli 生態當中本地開發構建時:插件加載(包括內部插件和項目應用插件)、插件的初始化,它的單例被所有的插件所共享,插件使用它的單例去完成 webpack 的更新。

PluginAPI.js 提供供插件使用的對象接口,它和插件是一一對應的關系。所有供 @vue/cli-service 使用的本地開發構建的插件接收的第一個參數都是 PluginAPI 的實例( api ),插件使用這個實例去完成 CLI 命令的注冊及對應服務的執行、webpack 配置的更新等。

以上就是 @vue/cli-service 插件系統簡單的分析,感興趣的同學可以深入閱讀相關源碼( 鏈接請戳我 )進行學習。

@vue/cli

不同于之前 1.x/2.x 的 vue-cli 工具都是基于遠程模板去完成項目的初始化的工作,它屬于那種大而全的方式,當你需要完成自定義的腳手架工具時,你可能要對 vue-cli 進行源碼級別的改造,或者是在遠程模板里面幫開發者將所有的配置文件初始化完成好。而 @vue/cli@3.0 主要是基于插件的 generator 去完成項目的初始化的工作,它將原來的大而全的模板拆解為現在基于插件系統的工作方式,每個插件完成自己所要對于項目應用的模板拓展工作。

@vue/cli 提供了終端里面的 vue 命令,例如:

vue create <project>
vue ui

當你需要對 vue-cli 進行改造,自定義符合自己開發要求的腳手架的時候,那么你需要通過 開發 vue-cli 插件來對 vue-cli 提供的服務進行拓展來滿足相關的要求 。vue-cli 插件始終包含一個 Service 插件作為其主要導出,且可選的包含一個 Generator 和一個 Prompt 文件。這里不細講如何去開發一個 vue-cli 插件了,大家感興趣的可以閱讀 vue-cli-plugin-eslint

這里主要是來看下 vue-cli 是如何設計整個插件系統以及整個插件系統是如何工作的。

@vue/cli@3.0 提供的插件安裝方式為一個 cli 服務: vue add <plugin> :

install a plugin and invoke its generator in an already created project

執行這條命令后,@vue/cli 會幫你完成插件的下載,安裝以及執行插件所提供的 generator。整個流程的執行順序可通過如下的流程圖去概括:

Vue-cli@3.0 插件系統簡析

我們來看下具體的代碼邏輯:

// @vue/cli/lib/add.js
async function add (pluginName, options = {}, context = process.cwd()) {
 ...
 const packageManager = loadOptions().packageManager || (hasProjectYarn(context) ? 'yarn' : 'npm')
 // 開始安裝這個插件
 await installPackage(context, packageManager, null, packageName)
 log(`${chalk.green('✔')} Successfully installed plugin: ${chalk.cyan(packageName)}`)
 log()
 // 判斷插件是否提供了 generator 
 const generatorPath = resolveModule(`${packageName}/generator`, context)
 if (generatorPath) {
 invoke(pluginName, options, context)
 } else {
 log(`Plugin ${packageName} does not have a generator to invoke`)
 }
}

首先 cli 內部會安裝這個插件,并判斷這個插件是否提供了 generator,若提供了那么去執行對應的 generator。

// @vue/cli/lib/invoke.js
async function invoke (pluginName, options = {}, context = process.cwd()) {
 const pkg = getPkg(context)
 ...
 // 從項目應用package.json中獲取插件名
 const id = findPlugin(pkg.devDependencies) || findPlugin(pkg.dependencies)
 ...
 // 加載對應插件提供的generator方法
 const pluginGenerator = loadModule(`${id}/generator`, context)
 ...
 const plugin = {
 id,
 apply: pluginGenerator,
 options
 }
 // 開始執行generator方法
 await runGenerator(context, plugin, pkg)
}
async function runGenerator (context, plugin, pkg = getPkg(context)) {
 ...
 // 實例化一個Generator實例
 const generator = new Generator(context, {
 pkg
 plugins: [plugin], // 插件提供的generator方法
 files: await readFiles(context), // 將項目當中的文件讀取為字符串的形式保存到內存當中,被讀取的文件規則具體見readFiles方法
 completeCbs: createCompleteCbs,
 invoking: true
 })
 ...
 // resolveFiles 將內存當中的所有緩存的 files 輸出到文件當中
 await generator.generate({
 extractConfigFiles: true,
 checkExisting: true
 })
}

和 @vue/cli-service 類似,在 @vue/cli 內部也有一個核心的類 Generator ,每個 @vue/cli 的插件對應一個 Generator 的實例。在實例化 Generator 方法的過程當中,完成插件提供的 generator 的執行。

// @vue/cli/lib/Generator.js
module.exports = class Generator {
 constructor (context, {
 pkg = {},
 plugins = [],
 completeCbs = [],
 files = {},
 invoking = false
 } = {}) {
 this.context = context
 this.plugins = plugins
 this.originalPkg = pkg
 this.pkg = Object.assign({}, pkg)
 this.imports = {}
 this.rootOptions = {}
 ...
 this.invoking = invoking
 // for conflict resolution
 this.depSources = {}
 // virtual file tree
 this.files = files
 this.fileMiddlewares = []
 this.postProcessFilesCbs = []
 ...
 const cliService = plugins.find(p => p.id === '@vue/cli-service')
 const rootOptions = cliService
  ? cliService.options
  : inferRootOptions(pkg)
 // apply generators from plugins
 // 每個插件對應生成一個 GeneratorAPI 實例,并將實例 api 傳入插件暴露出來的 generator 函數
 plugins.forEach(({ id, apply, options }) => {
  const api = new GeneratorAPI(id, this, options, rootOptions)
  apply(api, options, rootOptions, invoking)
 })
 }
}

和 @vue/cli-service 所使用的插件類似,@vue/cli 插件所提供的 generator 也是向外暴露一個函數,接收的第一個參數 api,然后通過該 api 提供的方法去完成應用的拓展工作。

開發者利用這個 api 實例去完成項目應用的拓展工作,這個 api 實例提供了:

拓展 package.json 配置方法( api.extendPackage )
利用 ejs 渲染模板文件的方法( api.render )
內存中保存的文件字符串全部被寫入文件后的回調函數( api.onCreateComplete )
向文件當中注入 import 語法的方法( api.injectImports )
...

例如 @vue/cli-plugin-eslint 插件的 generator 方法主要是完成了:vue-cli-service cli lint 服務命令的添加、相關 lint 標準庫的依賴添加等工作:

module.exports = (api, { config, lintOn = [] }, _, invoking) => {
 if (typeof lintOn === 'string') {
 lintOn = lintOn.split(',')
 }
 const eslintConfig = require('./eslintOptions').config(api)
 const pkg = {
 scripts: {
  lint: 'vue-cli-service lint'
 },
 eslintConfig,
 devDependencies: {}
 }
 if (config === 'airbnb') {
 eslintConfig.extends.push('@vue/airbnb')
 Object.assign(pkg.devDependencies, {
  '@vue/eslint-config-airbnb': '^3.0.0-rc.10'
 })
 } else if (config === 'standard') {
 eslintConfig.extends.push('@vue/standard')
 Object.assign(pkg.devDependencies, {
  '@vue/eslint-config-standard': '^3.0.0-rc.10'
 })
 } else if (config === 'prettier') {
 eslintConfig.extends.push('@vue/prettier')
 Object.assign(pkg.devDependencies, {
  '@vue/eslint-config-prettier': '^3.0.0-rc.10'
 })
 } else {
 // default
 eslintConfig.extends.push('eslint:recommended')
 }
 ...
 api.extendPackage(pkg)
 ...
 // lint & fix after create to ensure files adhere to chosen config
 if (config && config !== 'base') {
 api.onCreateComplete(() => {
  require('./lint')({ silent: true }, api)
 })
 }
}

以上介紹了 @vue/cli 和插件系統相關的幾個核心的模塊,即:

add.js 提供了插件下載的 cli 命令服務和安裝的功能;

invoke.js 完成插件所提供的 generator 方法的加載和執行,同時將項目當中的文件轉化為字符串緩存到內存當中;

Generator.js 和插件進行橋接,@vue/cli 每次 add 一個插件時,都會實例化一個 Generator 實例與之對應;

GeneratorAPI.js 和插件一一對應,是 @vue/cli 暴露給插件的 api 對象,提供了很多項目應用的拓展工作。

總結

以上是對 Vue-cli@3.0 的插件系統當中兩個主要部分:@vue/cli 和 @vue/cli-service 簡析。

  • @vue/cli 提供 vue cli 命令,負責偏好設置,生成模板、安裝插件依賴的工作,例如 vue create <projectName> 、 vue add <pluginName>
  • @vue/cli-service 作為 @vue/cli 整個插件系統當中的內部核心插件,提供了 webpack 配置更新,本地開發構建服務

前者主要完成了對于插件的依賴管理,項目模板的拓展等,后者主要是提供了在運行時本地開發構建的服務,同時后者也作為 @vue/cli 整個插件系統當中的內部核心插件而存在。在插件系統內部也對核心功能進行了插件化的拆解,例如 @vue/cli-service 內置的基礎 webpack 配置,npm script 命令等。二者使用約定式的方式向開發者提供插件的拓展能力,具體到如何開發 @vue/cli 的插件,請參考官方文檔。

以上所述是小編給大家介紹的Vue-cli@3.0 插件系統簡析,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

鞍山市| 枣强县| 朝阳县| 夹江县| 六盘水市| 灌阳县| 团风县| 漯河市| 鹤峰县| 秭归县| 明水县| 武清区| 永仁县| 武城县| 正定县| 远安县| 郁南县| 左权县| 邵阳市| 乌什县| 多伦县| 寿宁县| 邵阳县| 张家界市| 清镇市| 镇安县| 通州区| 灵丘县| 九江县| 丹阳市| 莱阳市| 铜陵市| 乌兰浩特市| 日喀则市| 绥阳县| 临沭县| 河曲县| 绿春县| 崇阳县| 清水县| 临武县|