vue invoke
在官方文檔中提到的內容不多,不少同窗應該會對它比較陌生javascript
輸入以下命令:前端
vue invoke
pwa
以後,它到底作了啥呢?vue
先拋一句命令行的 descriptionjava
invoke the
generator
of a plugin in an already created project
咱們來從源碼設計角度看看它多作了什麼?node
首先它也是 vue 的一個子命令,在 @vue/cli/bin/vue.js
git
這裏是咱們屢次提到的命令行必備工具包 commander
github
const program = require('commander')
設置 command
、description
、allowUnknownOption
和 action
vue-cli
program
.command('invoke <plugin> [pluginOptions]')
.description('invoke the generator of a plugin in an already created project')
.allowUnknownOption()
.action((plugin) => {
require('../lib/invoke')(plugin, minimist(process.argv.slice(3)))
})
咱們看一下 @vue/cli/lib/invoke.js
json
接受 3
個參數微信
async function invoke (pluginName, options = {},
context = process.cwd()) {
}
先查看項目根目錄下的 package.json
中是否認義了插件依賴
:
const pkg = getPkg(context)
getPkg 的源碼
以下:
function getPkg (context) {}
這裏的 fs
是來自工具包 fs-extra
const fs = require('fs-extra')
先獲取 package.json
文件路徑:
const path = require('path')
const pkgPath = path.resolve(context, 'package.json')
文件不存在(fs.existsSync
)會拋錯,代碼以下
if (!fs.existsSync(pkgPath)) {
throw new Error(package.json not found in ${chalk.yellow(context)}
)
}
核心經過 fs.readJsonSync
讀取對應文件路徑:
const pkg = fs.readJsonSync(pkgPath)
同時會查看 vuePlugins.resolveFrom
if (pkg.vuePlugins && pkg.vuePlugins.resolveFrom) {
return getPkg(path.resolve(context, pkg.vuePlugins.resolveFrom))
}
return pkg
經過函數 findPlugin 從 devDependencies 和 dependencies 中查找:
const id = findPlugin(pkg.devDependencies) ||
findPlugin(pkg.dependencies)
這裏返回的 id 爲:@vue/cli-plugin-pwa
findPlugin
的源碼設計,接受一個函數
const findPlugin = deps => {}
先找官方的 plugin:
if (!deps) return
let name
// official
if (deps[(name =@vue/cli-plugin-${pluginName}
)]) {
return name
}// full id, scoped short, or default short
if (deps[(name = resolvePluginId(pluginName))]) {
return name
}
resolvePluginId
處理 3 種狀況:
const {
resolvePluginId
} = require('@vue/cli-shared-utils')
內容以下:定義一個正則:
const pluginRE = /^(@vue/|vue-|@[w-]+/vue-)cli-plugin-/
exports.resolvePluginId = id => {}
一、處理全的 id,如:vue-cli-plugin-foo、@vue/cli-plugin-fo 以及 @bar/vue-cli-plugin-foo
// already full id
// e.g. vue-cli-plugin-foo, @vue/cli-plugin-foo, @bar/vue-cli-plugin-foo
if (pluginRE.test(id)) {
return id
}
二、charAt
判斷以 @
開頭的
定義一個正則:
const scopeRE = /^@[w-]+//
具體以下:
// scoped short // e.g. @vue/foo, @bar/foo if (id.charAt(0) === '@') {}
const scopeMatch = id.match(scopeRE) if (scopeMatch) { const scope = scopeMatch[0] const shortId = id.replace(scopeRE, '') return `${scope}${scope === '@vue/' ? `` : `vue-`}cli-plugin-${shortId}` }
三、默認狀況
// default short // e.g. foo return `vue-cli-plugin-${id}`
發現沒有定義就會提示
if (!id) { throw new Error( `Cannot resolve plugin ${chalk.yellow(pluginName)} from package.json. ` + `Did you forget to install it?`) }
因此咱們在 package.json 定義了 devDependencies:
"devDependencies": {
"@vue/cli-plugin-pwa": "latest"
}
後面會查找它的 generator 若是不存在也會拋錯
const pluginGenerator = loadModule(`${id}/generator`, context) if (!pluginGenerator) { throw new Error(`Plugin ${id} does not have a generator.`) }
咱們看一下 @vue/cli-plugin-pwa 的目錄:
cli-plugin-pwagenerator
template
看看 loadModule,代碼目錄以下: @vue/cli-shared-utils/lib/module.js
const { loadModule } = require('@vue/cli-shared-utils')
接受 3
個參數:
exports.loadModule = function (request, context, force = false) {}
函數內部:
const resolvedPath = exports.resolveModule(request, context) if (resolvedPath) { if (force) { clearRequireCache(resolvedPath) } return require(resolvedPath) }
當命令行沒有傳入參數的時候,會默認作處理:
if (!Object.keys(options).length) {}
看一下 prompts 目錄,目前官方的 plugin 中 cli-plugin-eslint
有 prompts.js 文件
地址:https://github.com/vuejs/vue-...
let pluginPrompts = loadModule(
${id}/prompts
, context)
若是像 eslint 同樣存在,判斷對應的類型,最後調用工具包 inquirer
const inquirer = require('inquirer')
if (pluginPrompts) { if (typeof pluginPrompts === 'function') { pluginPrompts = pluginPrompts(pkg) } if (typeof pluginPrompts.getPrompts === 'function') { pluginPrompts = pluginPrompts.getPrompts(pkg) } options = await inquirer.prompt(pluginPrompts) }
-------------- 重點關注 vue add 的部分會重點關注 -----------------
runGenerator 函數:接受 3 個參數:
函數結構以下:
async function runGenerator (context, plugin, pkg = getPkg(context)) {}
依賴 @vue/cli/lib/Generator.js
const Generator = require('./Generator')
傳入 5 個參數:
const generator = new Generator(context, { pkg, plugins: [plugin], files: await readFiles(context), completeCbs: createCompleteCbs, invoking: true })
依賴 readFiles 函數,代碼以下:這裏加載了工具包 globby
const globby = require('globby')
readFiles 函數結構:
async function readFiles (context) {}
readFiles 函數內部:
const files = await globby(['**'], { cwd: context, onlyFiles: true, gitignore: true, ignore: ['**/node_modules/**', '**/.git/**'], dot: true })
建立一個對象,同時對應 key 的值經過 fs.readFileSync 讀取內容
const res = {} for (const file of files) { const name = path.resolve(context, file) res[file] = isBinary.sync(name) ? fs.readFileSync(name) : fs.readFileSync(name, 'utf-8') } return normalizeFilePaths(res)
這裏的 isBinary 使用了工具包 isbinaryfile:
const isBinary = require('isbinaryfile')
normalizeFilePaths 函數來自 util/normalizeFilePaths.js:
const normalizeFilePaths = require('./util/normalizeFilePaths')
調用工具包 slash
const slash = require('slash')
依賴的 normalizeFilePaths
函數以下:接受一個參數
module.exports = function normalizeFilePaths (files) {}
經過 Object.keys 進行循環
Object.keys(files).forEach(file => { const normalized = slash(file) if (file !== normalized) { files[normalized] = files[file] delete files[file] } }) return files
-------------- 重點關注 --------------
調用 generate
函數,接受 2 個參數:
await generator.generate({ extractConfigFiles: true, checkExisting: true })
定義一個 class Generator
module.exports = class Generator { async generate ({ } = {}) { } }
重置 package.json:
this.files['package.json'] = JSON.stringify(this.pkg, null, 2) + 'n'
writeFileTree 函數
const writeFileTree = require('./util/writeFileTree')
依賴的工具包:
const fs = require('fs-extra')
const path = require('path')
源碼結構以下:接受 3 個參數
module.exports = async function writeFileTree (dir, files, previousFiles) {}
if (previousFiles) { await deleteRemovedFiles(dir, files, previousFiles) }
核心是 fs.ensureDir
和 fs.writeFile
return Promise.all(Object.keys(files).map(async (name) => { const filePath = path.join(dir, name) await fs.ensureDir(path.dirname(filePath)) await fs.writeFile(filePath, files[name]) }))
deleteRemovedFiles
函數結構以下:
function deleteRemovedFiles (directory, newFiles, previousFiles) {}
經過 Object.keys 找出要刪除的文件 filesToDelete
// get all files that are not in the new filesystem and are still existing const filesToDelete = Object.keys(previousFiles) .filter(filename => !newFiles[filename])
經過 fs.unlink
刪除
// delete each of these files return Promise.all(filesToDelete.map(filename => { return fs.unlink(path.join(directory, filename)) }))
本文來自微信公衆號:前端新視野