出發點並非小程序自己,是想要作一個腳手架(command-line interface),看過 VUE / REACT 腳手架,以爲很厲害,可是並不太知道里面是怎麼作成的,因此最近研究了研究,看能不能本身作成一個 簡單的腳手架。javascript
之因此以小程序未入點來開發一個 cli, 是由於最近在作小程序的時候有點不方便, 好比每次新建一個 page module, 都要 mkdir x, touch x.json, x.wxml, x.js, x.wxss
, 很麻煩。好比,我查找 bug 的時候習慣本身新建一個最小重複 bug 的新項目。若是每次執行一個命令行就能生成對應的項目骨架就行了。小程序自己有很好的開發工具, 好比選中 自動壓縮等,開發工具會本身幫你壓縮。因此這些其實不須要額外的工做量。html
考慮到:若是可以生成一個 腳手架, 每次都只用執行好比下面的步驟:前端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 首次安裝此 package
npm install mina-cli --save-dev
// 首次 init, 自動生成 項目名稱, package.json, gitignore 等
mina-cli init
// 首次 你須要的modules
mina-cli create user, logger
// 下次若是還須要新建一個module,直接
mina-cli create pageModule
// 刪除某一個 page module
mina-cli delete pageModule
|
這只是一個簡單的想象,具體的實現過程,我總結了以下。項目地址在這裏:mina cli。java
其中 fs-extra 是用來簡化 對文件操做的 npm 包。 inquirer 能夠用來詢問用戶的需求,而且獲得用戶的答覆。chalk 美化你的 terminal。最後的效果是:node
1.建立程序包清單,也就是 package.json ,說明全部的項目依賴。github
1
|
npm init
|
按照 terminal 的提示一步一步來便可。npm
2.建立一個入口文件, 好比咱們在 bin 目錄下,新建一個 script.js 文件, 用於最開始的入口。json
1
2
|
console.log('this is an entry');
|
此時執行 bin/script.js
,能夠看到相應的 log 輸出。固然這不是咱們最後須要的效果。由於咱們最好能以一個命令行 代替這個命令,讓用戶對咱們的項目目錄透明。小程序
3.添加 bin 字段,package.json 中有一個 bin 字段,能夠指定相應的命令。
1
2
3
|
"bin": {
"minaapp": "./bin/script.js"
}
|
4.關聯 npm 包。此時,你執行 minaapp 仍是沒有效果的,咱們須要關聯一下:
1
|
npm link
|
npm link 的做用是本地安裝這個 你本身開發的包。創建關聯,簡化你的工做流程。
5.輸入 minaapp , 就能夠運行了。
上面有些地方,好比 #! /user/bin/env node
和 npm link
可能不太好理解,這些我在後面有詳細的解釋。
當咱們把入口編寫好了之後,流程清楚了,就能夠編寫具體的內容實現。每個目錄結構,或者說內容都要有它存在的理由和方式。我設置的目錄結構以下:
script.js 是咱們的入口文件,做用固然是接受用戶的輸入命令,好比上面咱們 提到的 init 和 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
|
const program = require('commander');
// 定義當前版本
program
.version(
require('../package').version);
// 初始化項目
program
.command(
'init')
.description(
'Generate a new project')
.alias(
'i')
.action(
() => {
require('../command/init')()
});
// 新建modules
program
.command(
'create')
.description(
'Generate new modules')
.alias(
'c')
.action(
() => {
require('../command/create')()
});
program.parse(process.argv);
if(!program.args.length){
program.help()
}
|
這裏我使用了 tj 的 commander.js 來接受用戶的輸入。 好比 init 和 create 的時候,去到相應 command 目錄下的不一樣入口, 執行不一樣的內容。注意這裏必定要執行一下 parse, 而且最好當用戶沒有輸入正確命令時,給用戶一個 提示信息。這裏用下 program.help()
。
入口文件其中一個做用就是內容分發。好比 init 命令,到 command/init 下面去執行, create 命令 到 command/create 中執行。command 目錄就是具體的命令目錄。 若是之後擴展是否是也很方便, 好比增長一個 del 模塊。先看下 init 和 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
|
var path = require('path');
var inquirer = require('inquirer');
var fs = require('../lib/file');
var questions = require('../lib/questions');
var config = require('../lib/config');
module.exports = function () {
// 清空屏幕並顯示 npm 信息
fs.showNpmInfo();
// 等待用戶輸入項目信息
inquirer
.prompt(questions.question1)
.then(
(args) => {
let dir = path.resolve(process.cwd(), './' + args.appName);
// 將 appName 寫入配置文件
fs.outPutFile(
'./lib/config.json', JSON.stringify({
appPath: dir
})).then(
flag => {
if(flag) fs.showCreateDirInfo('begin');
});
// 保證有用戶輸入的目錄,並開始複製模板進入項目
fs.ensureDir(dir)
.then(
(flag) => {
return flag == true
? fs.copy(
'./template', dir)
:
Promise.reject(flag);
}).then(
(flag) => {
//...
})
.catch(
err => {
console.log('err', err);
});
});
};
|
在執行 minaapp init 的時候,首先接受一個用戶的參數,這裏用到 inquirer, inquirer 很方便的獲得用戶的輸入結果。question 是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
const question1 = [
{
name: 'appName',
type: 'input',
message: 'Enter your App\'s name: ',
validate( value ) {
if (value.length) {
return true;
}
else {
return 'Please Enter your App\'s name';
}
}
}
];
|
用戶輸入項目名稱,而後我利用 args 接受到對應的參數。獲得後,再建立項目目錄。建立目錄的過程就是建立項目結構的時候,這個時候,由於模板內容比較多,我就直接放在了npm 包裏,而後直接把目錄拷貝過來就能夠了。 因此若是有的項目團隊有本身的項目結構,能夠直接修改 template 裏面的目錄,執行目錄能夠直接修改。每次建立出來的目錄和 template 中的目錄相同。
執行 create 目錄固然就是 建立 pages 中的內容,這裏確定就比較容易,用下咱們本身封裝的 fs 文件,而後建立對應的 json,wxml,wxss,js 文件便可。
lib 目錄中能夠定義你本身的庫。好比我定義的 file.js 封裝了一層 promise。
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
|
module.exports = {
ensureDir(dir) {
return new Promise ((resolve, reject) => {
fs.ensureDir(dir, err => {
if(!err) resolve(true);
reject(err);
})
})
},
copy(
from ,to) {
return new Promise((resolve, reject) => {
fs.copy(
from ,to, err => {
if(!err) resolve(true);
reject(err);
})
})
},
//.....
}
|
大概的過程就是上面這樣,這裏只是一個簡單的流程說明。記錄下開發的過程。具體的代碼地址能夠在sevencai’s mina cli中看到。
上面建立項目的過程當中是一個簡單的過程, 中間有幾點想解釋下:
咱們剛剛在代碼的開頭第一行,寫了 #! /user/bin/env node
,這是爲了指定咱們的腳本執行所須要的解釋程序。咱們這裏是利用的 nodejs, 因此使用 node 來做爲腳本的解釋程序,而#! /usr/bin/env node這樣寫的目的是使用env來找到node,並使用node來做爲程序的解釋程序。
那這裏的 env 是啥 ? env 中規定了 不少系統的環境變量, 包括安裝的一些環境路徑等。不一樣的操做系統, node 的安裝路徑不必定相同, 可是環境變量都會存在 env 裏, 因此咱們這裏能夠用 env 來找到 node, 從而用 node 做爲解釋程序。 總結下,這裏的 env 就是爲了讓咱們的腳本在不一樣的操做系統可以上均可以被正常的解釋和執行。
npm link
也是我此次新學到的東西。我看到了這樣一篇文章:npm命令解析,以爲講的很清晰明瞭。下面我把這段文字摘錄下來。感謝。
開發Npm模塊的時候,有時咱們會但願,邊開發邊試用。可是,常規狀況下,使用一個模塊,須要將其安裝到node_modules目錄之中,這對於開發中的模塊,顯然很是不方便。npm link就能起到這個做用,創建一個符號連接,在全局的node_modules目錄之中,生成一個符號連接,指向模塊的本地目錄。
爲了理解npm link,請設想這樣一個場景。你開發了一個模塊myModule,目錄爲src/myModule,你本身的項目myProject要用到這個模塊,項目目錄爲src/myProject。每一次,你更新myModule,就要用npm publish命令發佈,而後切換到項目目錄,使用npm update更新模塊。這樣顯然很不方便,若是咱們能夠從項目目錄創建一個符號連接,直接連到模塊目錄,就省去了中間步驟,項目能夠直接使用最新版的模塊。
具體的使用方法是:
1.首先,在模塊目錄(src/myModule)下運行npm link命令。
2.src/myModule$ npm link 上面的命令會在Npm的全局模塊目錄內,生成一個符號連接文件,該文件的名字就是package.json文件中指定的文件名。
3./path/to/global/node_modules/myModule -> src/myModule 這個時候,已經能夠全局調用myModule模塊了。可是,若是咱們要讓這個模塊安裝在項目內,還要進行下面的步驟。
4.切換到項目目錄,再次運行npm link命令,並指定模塊名。
5.src/myProject$ npm link myModule 上面命令等同於生成了本地模塊的符號連接。
6.src/myProject/node_modules/myModule -> /path/to/global/node_modules/myModule 而後,就能夠在你的項目中,加載該模塊了。
7.var myModule = require(‘myModule’); 這樣一來,myModule的任何變化,均可以直接反映在myProject項目之中。可是,這樣也出現了風險,任何在myProject目錄中對myModule的修改,都會反映到模塊的源碼中。
8.若是你的項目再也不須要該模塊,能夠在項目目錄內使用npm unlink命令,刪除符號連接。
9.src/myProject$ npm unlink myModule
瞭解了建立一個 cli 的簡單流程,瞭解了一些經常使用的 npm 庫。知道了 npm link 是幹什麼用的, 之後建立小程序也更簡單方便了。項目總體的功能很簡單,我也不許備再多作別的命令解析,由於學習目的已經達到了。下面推薦一些閱讀的文章, 感謝:
1.command-line-with-node
2.javascript-command-line-interface-cli-node-js
3.npm命令解析
4.nodejs 中相對路徑和絕對路徑
5.msh-using-npm-manage-node.js-dependence
6.前端掃盲-之打造一個Node命令行工具