转载自:慕课网课件/ 大前端试听课 /
步骤一 · 前端工程化和进阶调试技巧
前言
什么是脚手架?
脚手架是为了保证各施工过程顺利进行而搭设的工作平台。——百度百科
下面看一段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 nodeconsole.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.jsconst { version } = require('../package.json');module.exports = {version}// main.jsconst program = require('commander')console.log(process.argv)const { version } = require('./constants.js')program.version(version).parse(process.argv)
 
- 作用:统一的模板引擎(比如:对
 从package里拿到当前版本
- 解析用户传递的参数
 
当我们运行toimooc --version的时候,会输出以下内容。
创建一个create的命令
// main.jsconst 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.jsconst { 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后台框架
· 自动化&缺陷控制+多端展望
· 大前端
