转载自:慕课网课件/ 大前端试听课 /
步骤一 · 前端工程化和进阶调试技巧
前言
什么是脚手架?
脚手架是为了保证各施工过程顺利进行而搭设的工作平台。——百度百科
下面看一段vue-cli的官方解释:
Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,Vue CLI 致力于将 Vue 生态中的
工具基础标准化
。它确保了各种构建工具能够基于智能的默认配置
即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。与此同时,它也为每个工具提供了调整配置的灵活性,无需 eject。
简单来说,脚手架就是「为了减少重复性工作而做的工具」
通常我们使用vue-cli创建项目会如下图所示,用交互的方式,选择用户需要的功能,最后创建一个项目,那么它内部是怎么实现的呢?今天我们这篇文章就简单介绍一下vue-cli工具的核心原理,以及实现一个简单的cli工具。
基本组成
通常来说,cli工具都必须用到下面的一些插件。
- commander
- 作用:解析参数
- inquirer
- 作用:交互式命令作用
- download-git-report
- 作用:在官网上下载模板
- chalk
- 作用:在命令行增加色彩
- metalsmith
- 作用:读取文件,实现模板渲染
consoledate
- 作用:统一的模板引擎(比如:对
ejs
的解析)核心代码的实现
下面,会通过代码的形式,一步一步的来做一个简单的CLI
的项目。
首先,我新建了一个项目,并且进行npm init
的初始化工作。
新建一个bin
的目录,然后在该目录下新建www
的文件。
在package.json
中,把bin
指向bin目录下的www.js
文件。
如下图
bin/www.js 的文件内容
这个时候,执行#! /usr/bin/env node
console.log('111')
npm link
就可以把package.js中配置的bin
命令生效了。试一下:
利用Commander来解析用户的参数
这里需要安装Commander的插件了。
npm i commander<br />如何查看用户传递过来的参数呢?<br />先对
bin/www.js`文件进行如下修改
新建src目录,新建main.js 和 constance.jsrequire('../src/main');
上述代码做了这几件事情:// constance.js
const { version } = require('../package.json');
module.exports = {
version
}
// main.js
const program = require('commander')
console.log(process.argv)
const { version } = require('./constants.js')
program.version(version).parse(process.argv)
- 作用:统一的模板引擎(比如:对
从package里拿到当前版本
- 解析用户传递的参数
当我们运行toimooc --version
的时候,会输出以下内容。
创建一个create的命令
// main.js
const program = require('commander')
const { version } = require('./constants.js')
program
.command('create')
.alias('c')
.description(' create a project')
.action(() => {
console.log('done')
})
// 解析用户传递的参数
program.version(version).parse(process.argv)
上述命令通过command创建了一个create
的命令,别名是c
. 操作的内容是打印done
。
当我们运行toimooc create
的时候,会打印如下内容:
通常情况下,需要的命令一定不止create
一个,这样的话,就需要对命令进行封装了。
下面对该部分代码进行封装
// main.js
// 定义映射对象
const mapActions = {
create: {
alias: 'c',
description: 'create a project',
examples: [
'toimooc-cli create <project-name>'
]
},
config: {
alias: 'c',
description: 'config project variable',
examples: [
'toimooc-cli config set <k> <v>',
'toimooc-cli config get <k>',
]
},
'*': {
alias: 'c',
description: 'command not found',
examples: []
}
}
// 循环映射对象
Reflect.ownKeys(mapActions).forEach(action => {
program
.command(action)
.alias(mapActions[action].alias)
.description(mapActions[action].description)
.action(() => {
// console.log('done')
if(action === '*') {
console.log(mapActions[action].description)
} else {
console.log(action)
}
})
})
为了代码的层次化结构更加清晰,action里面的所有操作, 需要拆分到独立的js文件中,使每个函数文件独立
完成自己需要完成的事情。
新建src/create.js文件:
module.exports = (projectName) => {
console.log('create', projectName)
}
修改Main.js
Reflect.ownKeys(mapActions).forEach(action => {
program
.command(action)
.alias(mapActions[action].alias)
.description(mapActions[action].description)
.action(() => {
// console.log('done')
if(action === '*') {
console.log(mapActions[action].description)
} else {
require(path.resolve(__dirname, action))(...process.argv.slice(3))
}
})
})
此时执行create projectName
就会打印出我们需要的内容:
监听help事件
通常用户会使用help来查看帮助命令,这个是如何实现的呢?
// 监听用户的help事件
program.on('--help', () => {
Reflect.ownKeys(mapActions).forEach(action => {
mapActions[action].examples.forEach(example => {
console.log(' ' + example)
})
})
})
从GitHub 拉取代码
github官网提供了拉取代码的Api接口
通过这个地址可以获取我在github上面所有的项目https://api.github.com/users/haimingyue/repos](https://api.github.com/users/haimingyue/repos)user/
后面的haimingyue
是我的github
的名称。可以替换成你个人的。
完善create文件代码
create的功能是创建项目
,拉取所有的项目列表,让用户选择安装哪个项目。
const axios = require('axios');
// 获取项目列表
const fetchRepoList = async () => {
const { data } = await axios.get('https://api.github.com/users/haimingyue/repos');
return data;
}
module.exports = async (projectName) => {
// console.log('create', projectName)
let repos = await fetchRepoList();
repos = repos.map((item) => item.name)
console.log(repos)
}
接下来需要用到ora
和Inquirer
插件。
- ora的作用是Loading效果
- Inquirer的作用是和用户进行交互式命令
完成效果如下:// 封装loading效果
const waitFnloading = (fn, message) => async(...args) => {
const spinner = ora(message)
spinner.start()
const result = await fn(...args)
spinner.succeed()
return result;
}
//根据github接口,获取项目的tag信息
const fetchTagList = async () => {
const { data } = await axios.get('https://api.github.com/repos/haimingyue/mmall-fe/tags');
return data;
}
// 使用Inquirer实现交互式命令
module.exports = async (projectName) => {
let repos = await waitFnloading(fetchRepoList, 'fetching template')()
repos = repos.map((item) => item.name)
const { repo } = await Inquirer.prompt({
name: 'repo',
type: 'list',
message: 'please choise a template to create project',
choices: repos
})
let tags = await waitFnloading(fetchTagList, 'fetching tags')(repo)
const { tag } = await Inquirer.prompt({
name: 'tag',
type: 'list',
message: 'please choise a tag',
choices: tags
})
tags = tags.map(item => item.name)
console.log('tags', tags)
}
使用download-git-repo下载
这一步的主要目的是使用download-git-repo
插件下载仓库的文件,并保存到本地的临时文件夹内。// constants.js
// 选择下载模板的目录
const downloadDirectory = `${process.env[process.platform === 'darwin' ? 'HOME' : 'USERPROFILE']}/.template`
module.exports = {
downloadDirectory
}
// create.js
const { promisify } = require('util');
let downloadGitRepo = require('download-git-repo');
downloadGitRepo = promisify(downloadGitRepo);
const downloadDirectory = require('./constants');
// 封装拉取项目的代码
const download = async (repo, tag) => {
let api = `haimingyue/${repo}`
if(tag) {
api+=`#${tag}`
}
const dest = `${downloadDirectory}/${repo}`;
await downloadGitRepo(api, dest)
return dest;
}
module.exports = async (projectName) => {
let repos = await waitFnloading(fetchRepoList, 'fetching template')()
repos = repos.map((item) => item.name)
const { repo } = await Inquirer.prompt({
name: 'repo',
type: 'list',
message: 'please choise a template to create project',
choices: repos
})
let tags = await waitFnloading(fetchTagList, 'fetching tags')(repo)
const { tag } = await Inquirer.prompt({
name: 'tag',
type: 'list',
message: 'please choise a tag',
choices: tags
})
tags = tags.map(item => item.name)
const result = await download(repo, tag)
console.log('tags', tags)
}
把下载好的文件赋值到当前文件夹
ncp Asynchronous recursive file & directory copying
下载复制文件夹是需要使用插件ncp的。npm install ncp
const path = require('path');
let ncp = require('ncp');
ncp = promisify(ncp);
...
module.exports = async (projectName) => {
... // 其他代码
const result = await waitFnloading(download, 'do') (repo, tag)
ncp(result, path.resolve(projectName))
}
这里把临时文件复制到当前的文件夹。
到这里,CLI工作原理的介绍就基本完成了。
这里只是简单的实现了模板的下载过程,复杂的cli工具还需要处理文件是否存在,ejs等模板引擎的解析等等。完成CLI的代码之后,发布到npm上面,就可以被用户使用了。
检测到您还没有关注慕课网服务号,无法接收课程更新通知。请扫描二维码即可绑定
+ 10 MP看完一节视频
步骤一 大前端试听课
· 走进大前端世界,拒绝迷茫
第1章 场景二:前端工程化如何入门到进阶?
- 1-1 前端工程化工具介绍
- 1-2 webpack五大核心概念
- 1-3 脚手架及CLI工具正在学习
-
第2章 场景三:开发如剑,测试如具,调试如诊
- 2-2 如何调试Webpack?如何配置VSCode调试?
- 2-3 接口测试必会的Mock接口&平台介绍
- 2-4 项目作业
· 全栈开发通用Web后台框架
· 自动化&缺陷控制+多端展望
· 大前端