vue invoke
在官方文檔中提到的內(nèi)容很少照卦,很多同學(xué)應(yīng)該會(huì)對(duì)它比較陌生
輸入如下命令:
vue invoke
pwa
之后,它到底做了啥呢土砂?
先拋一句命令行的 description
invoke the
generator
of a plugin in an already created project
我們來(lái)從源碼設(shè)計(jì)角度看看它多做了什么?
首先它也是 vue 的一個(gè)子命令祝谚,在 @vue/cli/bin/vue.js
這里是我們多次提到的命令行必備工具包 commander
const program = require('commander')
設(shè)置 command
、description
们豌、allowUnknownOption
和 action
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
接受 3
個(gè)參數(shù)
pluginName
options 默認(rèn) {}
context 默認(rèn) process.cwd()
async function invoke (pluginName, options = {},
context = process.cwd()) {
}
先查看項(xiàng)目根目錄下的 package.json
中是否定義了插件依賴
:
const pkg = getPkg(context)
getPkg 的源碼
如下:
function getPkg (context) {}
這里的 fs
是來(lái)自工具包 fs-extra
const fs = require('fs-extra')
先獲取 package.json
文件路徑:
const path = require('path')
const pkgPath = path.resolve(context, 'package.json')
文件不存在(fs.existsSync
)會(huì)拋錯(cuò),代碼如下
if (!fs.existsSync(pkgPath)) {
throw new Error(package.json not found in ${chalk.yellow(context)}
)
}
核心通過(guò) fs.readJsonSync
讀取對(duì)應(yīng)文件路徑:
const pkg = fs.readJsonSync(pkgPath)
同時(shí)會(huì)查看 vuePlugins.resolveFrom
if (pkg.vuePlugins && pkg.vuePlugins.resolveFrom) {
return getPkg(path.resolve(context, pkg.vuePlugins.resolveFrom))
}
return pkg
通過(guò)函數(shù) findPlugin 從 devDependencies 和 dependencies 中查找:
const id = findPlugin(pkg.devDependencies) ||
findPlugin(pkg.dependencies)
這里返回的 id 為:@vue/cli-plugin-pwa
findPlugin
的源碼設(shè)計(jì)浅妆,接受一個(gè)函數(shù)
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')
內(nèi)容如下:定義一個(gè)正則:
const pluginRE = /^(@vue/|vue-|@[\w-]+/vue-)cli-plugin-/
exports.resolvePluginId = id => {}
1望迎、處理全的 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
}
2辩尊、charAt
判斷以 @
開(kāi)頭的
定義一個(gè)正則:
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}`
}
3、默認(rèn)情況
// default short
// e.g. foo
return `vue-cli-plugin-${id}`
發(fā)現(xiàn)沒(méi)有定義就會(huì)提示
if (!id) {
throw new Error(
`Cannot resolve plugin ${chalk.yellow(pluginName)} from package.json. ` +
`Did you forget to install it?`)
}
所以我們?cè)?package.json 定義了 devDependencies:
"devDependencies": {
"@vue/cli-plugin-pwa": "latest"
}
后面會(huì)查找它的 generator 如果不存在也會(huì)拋錯(cuò)
const pluginGenerator = loadModule(`${id}/generator`, context)
if (!pluginGenerator) {
throw new Error(`Plugin ${id} does not have a generator.`)
}
我們看一下 @vue/cli-plugin-pwa 的目錄:
cli-plugin-pwa
generator
template
看看 loadModule康辑,代碼目錄如下: @vue/cli-shared-utils/lib/module.js
const {
loadModule
} = require('@vue/cli-shared-utils')
接受 3
個(gè)參數(shù):
- request
- context
- force 默認(rèn) false
exports.loadModule = function (request, context, force = false) {}
函數(shù)內(nèi)部:
const resolvedPath = exports.resolveModule(request, context)
if (resolvedPath) {
if (force) {
clearRequireCache(resolvedPath)
}
return require(resolvedPath)
}
當(dāng)命令行沒(méi)有傳入?yún)?shù)的時(shí)候摄欲,會(huì)默認(rèn)做處理:
if (!Object.keys(options).length) {}
看一下 prompts 目錄,目前官方的 plugin 中 cli-plugin-eslint
有 prompts.js 文件
地址:https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-plugin-eslint/prompts.js
let pluginPrompts = loadModule(
${id}/prompts
, context)
如果像 eslint 一樣存在疮薇,判斷對(duì)應(yīng)的類型胸墙,最后調(diào)用工具包 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)
}
-------------- 重點(diǎn)關(guān)注 vue add 的部分會(huì)重點(diǎn)關(guān)注 -----------------
runGenerator 函數(shù):接受 3 個(gè)參數(shù):
- context
- plugin
- pkg 默認(rèn) getPkg(context)
函數(shù)結(jié)構(gòu)如下:
async function runGenerator (context, plugin, pkg = getPkg(context)) {}
依賴 @vue/cli/lib/Generator.js
const Generator = require('./Generator')
傳入 5 個(gè)參數(shù):
- pkg
- plugins
- files
- completeCbs
- invoking
const generator = new Generator(context, {
pkg,
plugins: [plugin],
files: await readFiles(context),
completeCbs: createCompleteCbs,
invoking: true
})
依賴 readFiles 函數(shù),代碼如下:這里加載了工具包 globby
const globby = require('globby')
readFiles 函數(shù)結(jié)構(gòu):
async function readFiles (context) {}
readFiles 函數(shù)內(nèi)部:
const files = await globby(['**'], {
cwd: context,
onlyFiles: true,
gitignore: true,
ignore: ['**/node_modules/**', '**/.git/**'],
dot: true
})
創(chuàng)建一個(gè)對(duì)象按咒,同時(shí)對(duì)應(yīng) key 的值通過(guò) fs.readFileSync 讀取內(nèi)容
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 函數(shù)來(lái)自 util/normalizeFilePaths.js:
const normalizeFilePaths = require('./util/normalizeFilePaths')
調(diào)用工具包 slash
const slash = require('slash')
依賴的 normalizeFilePaths
函數(shù)如下:接受一個(gè)參數(shù)
module.exports = function normalizeFilePaths (files) {}
通過(guò) Object.keys 進(jìn)行循環(huán)
Object.keys(files).forEach(file => {
const normalized = slash(file)
if (file !== normalized) {
files[normalized] = files[file]
delete files[file]
}
})
return files
-------------- 重點(diǎn)關(guān)注 --------------
調(diào)用 generate
函數(shù)迟隅,接受 2 個(gè)參數(shù):
- extractConfigFiles 默認(rèn) false
- checkExisting 默認(rèn) false
await generator.generate({
extractConfigFiles: true,
checkExisting: true
})
定義一個(gè) class Generator
module.exports = class Generator {
async generate ({
} = {}) {
}
}
重置 package.json:
this.files['package.json'] = JSON.stringify(this.pkg, null, 2) + '\n'
writeFileTree 函數(shù)
const writeFileTree = require('./util/writeFileTree')
依賴的工具包:
const fs = require('fs-extra')
const path = require('path')
源碼結(jié)構(gòu)如下:接受 3 個(gè)參數(shù)
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
函數(shù)結(jié)構(gòu)如下:
function deleteRemovedFiles (directory, newFiles, previousFiles) {}
通過(guò) Object.keys 找出要?jiǎng)h除的文件 filesToDelete
// get all files that are not in the new filesystem and are still existing
const filesToDelete = Object.keys(previousFiles)
.filter(filename => !newFiles[filename])
通過(guò) fs.unlink
刪除
// delete each of these files
return Promise.all(filesToDelete.map(filename => {
return fs.unlink(path.join(directory, filename))
}))
本文來(lái)自微信公眾號(hào):前端新視野