出发点并非小程序自己,是想要作一个脚手架(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命令行工具