那些疯狂到认为自己能够改变世界的人,才能真正改变世界。——<史蒂夫·乔布斯>
介绍
脚手架的作用就是创建项目的基本结构并提供项目规范和约定 。
目前工作中常用的脚手架有 vue-cli、create-react-app、angular-cli、yoeman,express-generator
等,都是通过简单的初始化命令,快速搭建一个完整的项目的结构。
脚手架是我们经常使用的工具,也是团队提效的重要手段。
脚手架就是在启动的时候询问一些简单的问题,并且通过用户回答的结果去渲染对应的模板文件 ,基本工作流程如下:
通过命令行交互询问用户
根据用户回答的结果生成文件
例如 在使用 vue-cli
创建 Vue 项目时。
【vue-cli 文档】- https://cli.vuejs.org/zh/guide/creating-a-project.html
脚手架工具库
搭建一个脚手架或者阅读其他脚手架源码的时候需要了解下面这些工具库 👇
详细可以查看具体说明文档
脚手架 v1 (简单版本)
1. 创建项目
先创建一个简单的 Node-Cli 结构
1 2 3 4 5 6 7 8 9 10 11 12 cl9000-cli ├─ bin │ └─ cli.js # 启动文件 ├─ lib │ └─ constants.js │ └─ create.js │ └─ generator.js │ └─ http.js ├─ .npmrc ├─ LICENSE ├─ package.json └─ README.md
配置文件 package.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 { "name" : "cl9000-cli" , "version" : "1.0.0" , "description" : "simple vue cli" , "main" : "index.js" , "bin" : { "cl" : "./bin/cli.js" }, "scripts" : { "test" : "echo \"Error: no test specified\" && exit 1" }, "files" : [ "bin" , "lib" ], "author" : { "name" : "cl9000" , "email" : "cl9000@126.com" }, "keywords" : [ "cl9000-cli" , "cl" , "脚手架" ], "license" : "MIT" , "dependencies" : { "axios" : "^0.21.1" , "chalk" : "^4.1.2" , "commander" : "^8.1.0" , "download-git-repo" : "^3.0.2" , "figlet" : "^1.5.2" , "fs-extra" : "^10.0.0" , "inquirer" : "^8.1.2" , "ora" : "^5.4.0" } }
简单编辑一下 cli.js
1 2 3 #! /usr/bin/env node console .log('cl9000-cli doing' )
为了方便开发调试,使用 npm link
命令链接到全局
1 2 3 4 5 6 7 8 ~/Desktop/ cli/cl9000-cli ->npm link npm WARN cl9000-cli@1.0 .0 No repository field. up to date in 1.327 s found 0 vulnerabilities /usr/local/bin/cl -> /usr/ local/lib/node_modules/cl9000-cli/bin/cli.js /usr/local/lib/node_modules/cl9000-cli -> /Users/ Desktop/cli/cl9000-cli
测试一下
1 2 ~/Desktop/ cli/cl9000-cli ->cl cl9000-cli doing # 打印内容
我们得到了想要的打印内容。继续下一步。
2. 创建脚手架启动命令
安装依赖
1 2 3 4 5 6 7 8 $ npm install commander --save $ npm install chalk --save $ npm install figlet --save $ npm install path --save $ npm install fs-extra --save $ npm install inquirer --save $ npm install ora --save $ npm install download-git-repo --save
创建命令,询问、帮助信息、打印Logo
编辑 cli.js
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #! /usr/bin/env node console .log('cl9000-cli doing' )const program = require ('commander' )const chalk = require ('chalk' )const figlet = require ('figlet' )program .version(`v${require ('../package.json' ).version} ` ) .usage('<command> [option]' ) program .command('create <app-name>' ) .description('create a new project' ) .option('-f, --force' , 'overwrite target directory if it exist' ) .action((name, options ) => { console .log('name:' , name, 'options:' , options) require ('../lib/create.js' )(name, options) }) program .command('config [value]' ) .description('inspect and modify the config' ) .option('-g, --get <path>' , 'get value from option' ) .option('-s, --set <path> <value>' ) .option('-d, --delete <path>' , 'delete option from config' ) .action((value, options ) => { console .log(value, options) }) program .command('ui' ) .description('start add open cl9000-cli ui' ) .option('-p, --port <port>' , 'Port used for the UI Server' ) .action((option ) => { console .log(option) }) program .on('--help' , () => { console .log('\r\n' + figlet.textSync('cl9000' , { font: 'Ghost' , horizontalLayout: 'default' , verticalLayout: 'default' , width: 80 , whitespaceBreak: true })); console .log(`\r\nRun ${chalk.cyan(`cl <command> --help` )} for detailed usage of given command\r\n` ) }) program.parse(process.argv);
3. 询问用户问题获取创建所需信息
询问创建并进行覆盖
编辑 create.js
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 const path = require ('path' )const fs = require ('fs-extra' )const inquirer = require ('inquirer' )const Generator = require ('./generator' )module .exports = async function (name, options ) { console .log('>>> create.js' , name, options) const cwd = process.cwd(); const targetAir = path.join(cwd, name) if (fs.existsSync(targetAir)) { if (options.force) { await fs.remove(targetAir) } else { let { action } = await inquirer.prompt([ { name: 'action' , type: 'list' , message: 'Target directory already exists Pick an action:' , choices: [ { name: 'Overwrite' , value: 'overwrite' }, { name: 'Cancel' , value: false } ] } ]) if (!action) { return ; } else if (action === 'overwrite' ) { console .log(`\r\nRemoving...` ) await fs.remove(targetAir) } } } const generator = new Generator(name, targetAir); generator.create() }
如何获取模版信息
模板上传到远程仓库
在 lib
目录下创建 constants.js
静态常量文件
1 2 3 exports.REPO_ORGS = 'https://api.github.com/orgs/cl9000-org/' ; exports.REPOS_ORG = 'https://api.github.com/repos/cl9000-org/' ;
在 lib
目录下创建一个 http.js
专门处理模板和版本信息的获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 const axios = require ('axios' )const { REPO_ORGS, REPOS_ORG } = require ('./constants' );axios.interceptors.response.use(res => { return res.data; }) async function getRepoList ( ) { return axios.get(REPO_ORGS + 'repos' ) } async function getTagList (repo ) { return axios.get(`${REPOS_ORG} ${repo} /tags` ) } module .exports = { getRepoList, getTagList }
新建一个 Generator.js 来处理项目创建逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 const { getRepoList, getTagList } = require ('./http' )const ora = require ('ora' )const chalk = require ('chalk' )const inquirer = require ('inquirer' )const util = require ('util' )const path = require ('path' )const downloadGitRepo = require ('download-git-repo' ) async function wrapLoading (fn, message, ...args ) { const spinner = ora(message); spinner.start(); try { const result = await fn(...args); spinner.succeed(); return result; } catch (error) { spinner.fail('Request failed, refetch ...' ) } } class Generator { constructor (name, targetDir) { this .name = name; this .targetDir = targetDir; this .downloadGitRepo = util.promisify(downloadGitRepo); } async getRepo() { const repoList = await wrapLoading(getRepoList, 'waiting fetch template' ); if (!repoList) return ; const repos = repoList.map(item => item.name); const { repo } = await inquirer.prompt({ name: 'repo' , type: 'list' , choices: repos, message: 'Please choose a template to create project' }) return repo; } async getTag(repo) { const tags = await wrapLoading(getTagList, 'waiting fetch tag' , repo); if (!tags) return ; const tagsList = tags.map(item => item.name); const { tag } = await inquirer.prompt({ name: 'tag' , type: 'list' , choices: tagsList, message: 'Place choose a tag to create project' }) return tag } async download(repo, tag) { const requestUrl = `cl9000-org/${repo} ${tag ? '#' + tag : '' } ` ; await wrapLoading( this .downloadGitRepo, 'waiting download template' , requestUrl, path.resolve(process.cwd(), this .targetDir)) } async create() { const repo = await this .getRepo() const tag = await this .getTag(repo) console .log('用户选择了,repo=' + repo + ',tag=' + tag) await this .download(repo, tag) console .log(`\r\nSuccessfully created project ${chalk.cyan(this .name)} ` ) console .log(`\r\n cd ${chalk.cyan(this .name)} ` ) console .log(' npm run dev\r\n' ) } } module .exports = Generator;
测试一下,终端执行命令 cl create my-project
…
脚手架 v2 (扩展版本)
参考
赞赏一下 坚持原创技术分享,您的支持将鼓励我继续创作!