大家都会用到@vue/cli或者create-react-app这种脚手架,但是每个公司都或多或少有一些自己的基础框架。每次都从头搭建一个项目比较麻烦,所以准备搭建一个简单的脚手架方便生成项目代码。
前期搭建准备 脚手架用到了以下几个库:
chalr: 用于命令行绘制彩色文字
commander: 用于提供指令,例如vue/cli的vue create appname就是一个指令
download: 下载
fs-extra: 好用的文件处理
ora: 用于加载动画
inquirer: 用于命令行交互,例如vue/cli在命令行让选择vue版本,css处理器等交互功能。
项目的目录结构如下:
cli
|____ bin # bin目录
| |____ cli # 入口文件,可以没有后缀名
|____ lib # lib目录
| | ____ create.js # 创建项目过程中的主要交互逻辑
| | ____ Creator.js # Creator类
| | ____ Downloader.js # Downloader类
| ____ package.json
目前的脚手架功能其实没必要这么复杂,但是后续还要有新功能加入,所以暂时搞成这个样子了。
在package.json中配置bin属性。
1 2 3 4 5 { "bin" : { "name" : "./bin/cli" } }
这么配置之后就是调用name指令会执行bin/cli这个文件。比如你要用vue create appname来创建项目就配置name为vue这样就可以。
脚手架文件 脚手架是以nodejs运行的,在代码的头部加上\#! /usr/bin/env node标明是以nodejs运行的。
bin/cli
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 #! /usr/bin/env node const program = require ("commander" );const chalk = require ("chalk" )program .command(`create <app-name>` ) .description(`create a new project` ) .option('-f, --force' , 'overwrite target directory if it exists' ) .action((name, cmd ) => { require ('../lib/create' )(name, cmd) }) program.on('--help' , function ( ) { console .log() console .log(`Run ${chalk.cyan('脚手架name <command> --help' )} for details` ) console .log() }) program .version(`脚手架名字 ${require ("../package.json" ).version} ` ) .usage(`<command> [option]` ) program.parse(process.argv);
这里只是搭建了基础功能,还有更多功能可以自行扩展
command 指令
description 描述
option 额外选项,比如这里的-f就是强制替换同名目录
拉取git仓库创建项目 我们在调用create 指令之后调用了lib/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 const path = require ("path" )const fse = require ("fs-extra" )const Inquirer = require ("inquirer" )const Creator = require ("./Creator" )const ora = require ("ora" )module .exports = async function create (projectName, options ) { const cwd = process.cwd() const targetDir = path.join(cwd, projectName) if (fse.existsSync(targetDir)){ if (options.force){ await fse.remove(targetDir) }else { let {action} = await Inquirer.prompt([ { name : "action" , type : "list" , message : "Target directory already exists, Please pick an action" , choices : [ {name : "Overwrite" , value : "overwrite" }, {name : "Cancel" , value : false } ] } ]) if (!action){ return ; }else if (action === "overwrite" ){ let spinner = ora("wating for remove..." ) spinner.start() await fse.remove(targetDir) spinner.succeed("remove success" ) } } } const creator = new Creator(projectName, targetDir) creator.create() }
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 const Inquirer = require ("inquirer" )const path = require ("path" )const Downloader = require ("./Downloader" )const fse = require ("fs-extra" )const fs = require ("fs" )const chalk = require ("chalk" )class Creator { constructor (projectName, targetDir ) { this .name = projectName this .target = targetDir } async create ( ) { let repo = await this .fetchRepo() await this .download(repo) } async fetchRepo ( ) { let {repo} = await Inquirer.prompt({ name : "repo" , type : "list" , choices : ["vue2" , "vue3" ], message : "Please choice a template to create project" }) return repo } async download (repo ) { let requestUrl = `仓库地址` let downloader = new Downloader(requestUrl, path.resolve(process.cwd(), this .name), this .name) downloader.download(this .downloadResHandler, this ) } async downloadResHandler (err ) { if (err){ return ; } let dir = path.resolve(process.cwd(), this .name) await this .editName(dir, this .name) } async editName (dir, name ) { try { const packageObj = await fse.readJson(path.resolve(dir, "package.json" )) packageObj.name = name await fse.outputFile(path.resolve(dir, "package.json" ), JSON .stringify(packageObj, "" , "\t" )) this .drawMessage(name) } catch (err) { console .error(err) } } drawMessage (name ) { let logo = fs.readFileSync(path.resolve(__dirname, "logo.txt" ), "utf-8" ) console .log(logo) console .log(chalk.hex("#ccc" ).bold("🎉 Successfully created project." )) console .log(chalk.hex("#ccc" ).bold("👉 Get started with the following commands:" )) console .log() console .log(' ' + chalk.hex("#666" ).bold("$" ) + " " + chalk.hex("#60d1dd" ).bold('cd ' +name)) console .log(' ' + chalk.hex("#666" ).bold("$" ) + " " + chalk.hex("#60d1dd" ).bold('npm install' )) console .log(' ' + chalk.hex("#666" ).bold("$" ) + " " + chalk.hex("#60d1dd" ).bold('npm run serve' )) console .log() } } module .exports = Creator
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 const downloadUrl = require ('download' )const ora = require ("ora" )class Downloader { constructor (requestUrl, targetDir, name ) { this .requestUrl = requestUrl this .targetDir = targetDir this .name = name } async download (fn,creator ) { let spinner = ora("please wait for a moment, downloading..." ) spinner.start() let downloadOptions = { extract : true , strip : 1 , mode : '666' , headers : { accept : 'application/zip' , } } downloadUrl(this .requestUrl, this .targetDir, downloadOptions) .then(function ( ) { spinner.succeed("download success" ); fn.call(creator) }) .catch(function (err ) { spinner.fail("download failed, please retry..." ) fn.call(creator, err) }) } } module .exports = Downloader
这里的代码也并不复杂,看一下注释应该就没问题,基本流程就是选择要下载的仓库,然后去git拉取仓库下来,然后可以对package.json做一些额外配置,比如改项目名等等。。。
fs-extra可以很方便对处理json
JSON.stringify(xx,xx, “\t”)中传入制表符为了格式化json文件