[Vue CLI 3] 源码系列之useTaobaoRegist
通过下列方式可以安装最新版本的 Vue CLI(注释:sudo 自行选择)
sudo npm install -g @vue/cli
然后通过下列命令创建项目:
vue create demo
这时候,会询问你是否使用 taobao 的 registry
Your connection to the default npm registry seems to be slow.
Use https://registry.npm.taobao.org for faster installation?
然后选择 Yes 后,发现在用户的根目录中出现了一个 .vuerc
文件,内容如下:
{
"useTaobaoRegistry": true
}
本文从源码设计角度看一下背后的实现:
在新版本 Vue CLI 中目录结构变动了,我们找到了如下几个文件:
@vue/cli/lib/util/shouldUseTaobao.js
这个文件的函数只会执行一次
:设置了变量 checked
、result
let checked
let result
在函数内部一上来就会判断
if (checked) return result
第一步:需要在命令行以询问方式:
一般多会采用 inquirer
这个工具包,先加载:
const inquirer = require('inquirer')
然后调用 prompt
方法,注意这里设置了 type confirm
的方式
然后用 chalk
这个工具包来在命令行改变字颜色
const chalk = require('chalk')
最核心的代码片段如下:
定义了 name、type 和 message 字段:
const { useTaobaoRegistry } = await inquirer.prompt([
{
name: 'useTaobaoRegistry',
type: 'confirm',
message: chalk.yellow(
` Your connection to the default npm registry seems to be slow.\n` +
` Use ${chalk.cyan(registries.taobao)} for faster installation?`
)
}
])
第二步:判断 register 的速度
定义一个变量 faster
let faster
这里使用了 Promise.race
函数(返回一个 promise,一旦迭代器中的某个promise 解决或拒绝,返回的 promise就会解决或拒绝。)
try {
faster = await Promise.race([
ping(defaultRegistry),
ping(registries.taobao)
])
} catch (e) {}
这里的变量就是:
const registries = require('./registries')
如上,来自一个同级的 registries.js
文件
const defaultRegistry = registries.npm
registries 在 @vue/cli/lib/util/registries.js
源码内容如下:维护了 3 个映射关系,里面就有官方 registry
和 taobao
的
const registries = {
npm: 'https://registry.npmjs.org',
yarn: 'https://registry.yarnpkg.com',
taobao: 'https://registry.npm.taobao.org'
}
module.exports = registries
我们看一下最核心的 ping
函数:
使用了 @vue/cli-shared-utils
的 request
方法
async function ping (registry) {
await request.get(`${registry}/vue-cli-version-marker/latest`)
return registry
}
去 @vue/cli-shared-utils/lib/request.js
看一下源码:
对外暴露了 get 方法,内部依赖 request-promise-native 工具包(uses native ES6 promises),传入了一个对象:
- method 方法为 'GET'
- resolveWithFullResponse
- json
- uri 请求地址
核心代码如下:
exports.request = {
get (uri) {
// lazy require
const request = require('request-promise-native')
const reqOpts = {
method: 'GET',
resolveWithFullResponse: true,
json: true,
uri
}
return request(reqOpts)
}
}
第三步:写入一个 .vuerc 文件
定义了 save 函数,代码实现如下:
const save = val => {
result = val
saveOptions({ useTaobaoRegistry: val })
return val
}
saveOptions 在 @vue/cli/lib/options.js 中定义:
exports.saveOptions = toSave => {
// 实现在下面
}
在里面定义了一个 defaults 的对象,里面默认设置了 useTaobaoRegistry 为 undefined:
exports.defaults = {
useTaobaoRegistry: undefined
}
核心是采用了 fs.writeFileSync 往指定目录写文件:
注释:关于写入路径可以看一下 rcPath.js 文件提供的 getRcPath
const rcPath = exports.rcPath = getRcPath('.vuerc')
注意:下面的 JSON.stringify 的第三个参数,也是通过 try catch 的方式:
fs.writeFileSync(rcPath, JSON.stringify(options, null, 2))
那如果用户本地已经设置了呢,先获取本地的设置:
核心是使用了 execa 这个工具包:
const execa = require('execa')
定义了一个参数 userCurrent ,传入了命令和参数:
(await execa(`npm`, ['config', 'get', 'registry'])).stdout
比较两个路径:
if (removeSlash(userCurrent) !== removeSlash(defaultRegistry)) {
// user has configured custom regsitry, respect that
return save(false)
}
removeSlash 的实现如下:
function removeSlash (url) {
return url.replace(/\/$/, '')
}
第三个问题:用户第一次设置之后,后面的创建项目操作是如何处理的呢?
在 @vue/cli/lib/util/shouldUseTaobao.js 内部,会调用 loadOptions 函数(下面会提到)
const saved = loadOptions().useTaobaoRegistry
@vue/cli/lib/options.js
会定义一个变量:
let cachedOptions
对外暴露了 loadOptions 函数:
exports.loadOptions = () => {
}
在 loadOptions 函数内部:
第一步:会先看 cachedOptions 是否有值:
if (cachedOptions) {
return cachedOptions
}
然后会读取配置文件内容:通过 fs.readFileSync 方法,然后用 JSON.parse 转成对象
// 判断配置文件是否存在
if (fs.existsSync(rcPath)) {}
内部使用 try catch
,给 cacheOptions
赋值
JSON.parse(fs.readFileSync(rcPath, 'utf-8'));
所以第二次这里因为 .vuerc
文件已经写入了内容,所以第一步就返回了
本文原创来自微信公众号:前端新视野
扩展链接:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/race