前面的話
以前使用vue-cli
,vue init webpack project-name
一行命令就可以初始化一個我們自己的項目,小柒覺得好神奇,之後研究啦一下vue-cli
,其實就是一個高級版的克隆。然後就決定自己實現一套屬於自己的腳手架(xq-cli
),目前xq-cli
腳手架已經實現了有一段時間了,決定拿出來分享。
xq-cli腳手架功能
創建項目
- 詢問用戶:詢問用戶新建項目的基本信息
- 下載模板:根據用戶的選擇下載對應的模板
- 生成配置文件:根據用戶的選擇生成對應的配置文件
- 生成新項目:在目錄中生成新項目
初始化項目
項目創建好後,我們要對它進行初始化操作:
- 自動創建一個GitHub倉庫,將我們的項目推送至倉庫
- 安裝依賴
啓動項目
- 本地啓動瀏覽
- 支持熱更新
打包項目
- 打包我們的項目
前置知識
在開始實現時,我們必須要先了解一些工具:
commander:
-
作用:命令行工具,用來編寫指令和處理命令行的。
-
用法:
const program = require('commander') program .command('init') .alias('i') .description('初始化') .option('-x, --xxx', 'xxx') // 有參數時使用 .action( () => { // 回調函數 } }) program.parse(process.argv)
vue init
就是這麼來滴。
- commander API 介紹
- command - 自定義執行的命令,後面跟上自己定義的命令名稱
- alias - 定義更短的命令行(配置別名)(懶人必備)
- description - 描述,它會在 --help裏面顯示
- option - 定義參數,接受四個參數
- action - 註冊一個callback函數(執行命令之後,就會執行這個回調)
- usage:用戶使用提示
- parse - 解析命令行,注意這個方法一定要放到最後調用
inquirer
- 作用:這是一個強大的交互式命令行工具,詢問用戶就靠它啦。
- 用法:
想一下,使用onst inquirer = require('inquirer'); inquirer .prompt([ // 一些交互式的問題 ]) .then(answers => { // 回調函數,answers 就是用戶輸入的內容,是個對象 });
vue init webpack project-name
時,是不是都要問你幾個問題,項目名稱、作者、描述等。 - inquirer 功能簡介
- input - 輸入
- validate - 驗證
- list - 列表選項
- comfirm - 提示
- checkbox - 複選框
chalk
- 作用:美化我們滴命令行,一個顏色插件,用來修改命令行輸出樣式,通過顏色區分 info、error 日誌,清晰直觀
- 用法:
const chalk = require('chalk'); console.log(chalk.green('success')); console.log(chalk.red('error'));
ora
-
作用:用於顯示加載效果,轉圈圈滴那種
-
用法:
const ora = require('ora') let loading = ora('downloading template ...') loading.start()
log-symbols
- 作用:控制檯彩色日誌符號,用來顯示√ 或者 × 等圖標。(搭配chalk使用)
- 使用:
import symbol from 'log-symbols';
console.log(symbol.error, chalk.red('請輸入項目名'));
console.log(symbol.success, chalk.green('配置文件更新完成'));
download-git-repo
-
作用:用來下載遠程模板的
-
用法:
import downloadGit from 'download-git-repo'; downloadGit(repository , ProjectName, options ,callback); // repository : 爲遠程倉庫地址 // ProjectName:是存放下載的文件路徑,可以直接是文件名,默認是當前目錄 // options: 一些選項,比如`{clone: boolean}`表示用http download // 還是 git clone 的形式下載 // callback: 回調函數
準備工作
1、先創建一個文件夾,小柒這裏叫xq-cli
。
2、創建.babelrc文件,支持es6語法。
3、在該目錄下執行npm init
,生成package.json文件,然後下載上面的依賴。
"dependencies": {
"babel-cli": "^6.26.0",
"babel-env": "^2.4.1",
"chalk": "^3.0.0",
"commander": "^5.0.0",
"download-git-repo": "^1.1.0",
"inquirer": "^7.1.0",
"ora": "^4.0.3",
"log-symbols": "^3.0.0",
"download-git-repo": "^3.0.2",
}
(有了package.josn文件,這樣我們就可以發佈我們的npm包了)。
4、新建一個bin文件夾,並在bin目錄下新建一個無後綴名的cmd文件:
#!/usr/bin/env node
console.log('hello'); // 測試用
這個文件是我們整個腳手架的入口文件
再去package.json文件中配置bin
參數,專門放置用戶的自定義命令:
使用npm link
命令將xq-cli
與xqc
軟連接至全局,這樣就可以全局使用xq-cli
或者xqc
這個命令來啓動了。
(這裏只是簡單的測試一下)。
實際代碼爲:
#!/usr/bin/env node
require('../dist/main.js'); // 編譯後的main.js文件作爲入口文件
5、修改 package.json 中的 scripts 參數,指定可執行命令,實時編譯腳本,讓 node 能夠識別並執行,開啓watch實時監控。
6、補全目錄結構
入口文件main.js
在整個腳手架的入口文件bin/cmd
中,我們引入了require('../dist/main.js');
這個編譯後的文件,那我們就從src/main.js
文件開始:
這個文件就是使用commander
工具來定義我們的命令,主要代碼:
program
.command(action)
.description(actionMap[action].description)
.alias(actionMap[action].alias)
.action(()=>{
switch (action) {
case 'create':
create(...process.argv.slice(3));
break;
case 'init':
init(program.username, program.token);
break;
case 'dev':
dev(program.port);
break;
case 'build':
build();
break;
default:
break;
}
})
});
// 項目版本
program
.version(require('../package.json').version, '-v --version')
.parse(process.argv);
action就是我們的命令,這裏用四個命令,也就是對應腳手架的4個功能:create、init、dev 、build
它們對應的回調函數分別在create.js
、init.js
、dev.js
、build.js
。下面分別來實現這個幾個功能。
創建項目(xq-cli create)
項目創建思路:
-
項目創建命令必須輸入新建項目名稱(必須做一層判斷,避免創建的項目名稱覆蓋已有項目)
// 文件是否存在 let isExist = async(name) => { return new Promise((resolve) => { if(fs.existsSync(name)) { console.log(symbol.error, chalk.red('文件夾名已被佔用,請更換名字重新創建')) }else{ resolve(); } }); }
-
詢問用戶,引導用戶輸入配置信息
// 詢問用戶 let promptList = [ { type: 'list', name: 'frame', message: 'please choose this project template', choices: ['vue', 'react'] }, { type: 'input', name: 'description', message: 'Please enter the project description:' }, { type: 'input', name: 'author', message: 'Please enter the author name:' } ]; let prompt = ()=>{ return new Promise((resolve)=>{ // inquirer提供prompt函數來實現詢問,其參數爲數組,詢問將按數組的順序來 inquirer.prompt(promptList) .then(answer => { resolve(answer); }) }); }
-
下載模板,下載模板比較耗時,可以通過ora提示用戶 正在下載模板,下載完畢之後給出提示。
// 項目模塊遠程下載 let downLoadTemplate = async (ProjectName ,api) => { return new Promise((resolve, reject) => { downloadGit(api, ProjectName, {clone: true}, (err) => { if(err) { reject(err); }else{ resolve(); } }); }); };
-
項目下載完畢後,更新配置文件
// 更新json配置文件 let updateJsonFile = (fileName, obj) => { return new Promise((resolve) => { if(fs.existsSync(fileName)) { // 讀出模板下的package.json文件 const data = fs.readFileSync(fileName).toString(); // 轉爲json對象 let json = JSON.parse(data); // 將用戶輸入的更新到模板package.json文件 Object.keys(obj).forEach(key => { json[key] = obj[key]; }); // 重寫模板下的package.json文件 fs.writeFileSync(fileName, JSON.stringify(json, null, '\t'), 'utf-8'); resolve(); } }); }
上面的工具方法都在
unit.js
代碼中,具體調用都在create.js
中,可以到create.js
文件中查看源碼。
項目初始化(xq-cli init )
curl -u username:token https://api.github.com/user/repos -d "{\"name\": \"project name \"}"
使用這條命令就可以自動生成倉庫,不用手動再去創建了。這裏的username 就是你Github的用戶名,token在Github上生成:(這裏我的是windows系統,不支持單引號,所以要用轉義符)
然後會有一個一長串數字字符串就是token了。
初始化項目的思路:
- Git初始化
- 創建Github倉庫
- 關聯Github倉庫
- 更新package.json的repository配置
- 提交代碼到GitHub倉庫
- 安裝依賴
let init = async (username, token)=>{
try {
await loadCmd(`git init`,'git初始化');
if(username === '' || token === '') {
console.log(symbol.warning, chalk.yellow('缺少參數無法創建遠程倉庫'));
}else {
const projectName = process.cwd().split('\\').slice(-1)[0];
await loadCmd(`curl -u qjk-xiaoqi:a97fc56cbefe4bdc092490067bb1a9727615a583 https://api.github.com/user/repos -d "{\"name\": \"kkk\"}"`, 'Github倉庫創建');
// curl -u qjk-xiaoqi:a97fc56cbefe4bdc092490067bb1a9727615a583 https://api.github.com/user/repos -d "{\"name\": \"auto\"}"
await loadCmd(`git remote add origin https://github.com/${username}/${projectName}.git`, '關聯遠端倉庫')
let loading = ora();
loading.start(`package.json更新repository:命令執行中...`);
await updateJsonFile('package.json', {
"repository": {
"type": "git",
"url": `https://github.com/${username}/${projectName}.git`
}
}).then(() => {
loading.succeed(`package.json更新repository: 命令執行完成`);
});
await loadCmd(`git add .`,"執行 git add");
await loadCmd(`git commit -a -m "init"`, '執行git commit')
await loadCmd(`git push --set-upstream origin master`, '執行git push')
}
await loadCmd(`npm install`,"安裝依賴");
}catch(err) {
console.log(symbol.error, chalk.red('初始化失敗'));
console.log(symbol.error, chalk.red(err));
process.exit(1);
}
}
let loadCmd = async(cmd, text) =>{
let loading = ora();
loading.start(`${text}: 命令執行中...`);
await child.exec(cmd);
loading.succeed(`${text}: 命令執行完成`)
}
相關代碼在:init.js
文件中。
運行命令:xq-cli init -u xxx -t xxx
就可以了:
項目啓動 (xq-cli dev )
項目啓動就要用我們的Webpack啦,將項目的webpack.config.js
配置好。使用open-browser-webpack-plugin
插件來打開了頁面, 並且支持熱更新。
const config = require('./webpack.config.js');
let dev = (port) => {
// 啓動項目後自動打開瀏覽器
config.plugins.push(new OpenBrowserPlugin({ url: `http://localhost:${port}` }))
// 創建一個小型服務器
new WebpackDevServer(webpack(config), {
contentBase: './public',// 配置http服務器的文件目錄
hot: true, // 開啓模塊熱替換
historyApiFallback: true // 開啓html5 History API網頁
}).listen(port, 'localhost', function (err, result) {
if (err) {
console.log(err);
}
});
}
項目打包(xq-cli build)
這個就比較簡單了,直接只用webpack
let build = () => {
// 打包
webpack(config, function(err, stats) {
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}))
console.log( symbol.success, chalk.green(' 打包完成'));
process.exit(1);
})
}
執行命令:xq-cli build
發佈npm包
最後就可以發佈我們自己的npm包啦,刪掉創建的文件,去官網創建一個賬號,就可以發佈啦。
npm login
登錄,npm publish
發佈,之後就可以去官網上查看了:
(xq-cli已經被使用了,所以該爲xqc)