# npm脚手架
项目开发过程中, 经过版本迭代,项目变得越来越大,结构也越来越复杂。这时候,如果想创建一个新项目,要么是基于既有项目复制,然后删除、修改,要么是直接从头开始创建, 两种方式虽然能达到目的,但是费时费力, vue-cli 脚手架就是一个基于 Vue.js 进行快速开发的完整系统,提供丰富的功能和配置大大降低了创建项目难度。
为此,我们可以通过自定义一个脚手架,实现简单拉取模板的功能,一定程度上 (可以愉快地摸鱼) 降低创建项目工作量。
部分知识同 npm发包,因此本篇不再赘述,以 hishion-cli 为例重点介绍如何开发。
# 起步
项目中用到的一些依赖,这里先简单了解一下。
注意:项目中的依赖需要安装在 dependencies
中,即:
npm install <package>
chalk
chalk (opens new window) :可以美化终端输出。
commander
commander (opens new window) :node.js的命令行,可以解析命令和参数,处理用户输入。
download-git-repo
download-git-repo (opens new window) :下载并提取git仓库。
inquirer
inquirer (opens new window) :常见的交互式命令行用户界面的集合。
log-symbols
log-symbols (opens new window) :根据日志输出不同等级标志。
ora
ora (opens new window) :终端loading效果。
# 开发
文件目录如下:
|-- root
|-- bin
|-- index.js
|-- commands
|-- init.js
|-- lib
|-- message.js
|-- package.json
|-- README.md
|-- templates.json
2
3
4
5
6
7
8
9
10
- package.json
package.json
中的 bin
字段可以映射本地执行文件,在安装时,npm
将把该映射到全局安装的 prefix/bin
中,或者本地安装的 ./node_ modules/.bin
。
可以如下配置:
"bin": {
"hishion-cli": "./bin/index.js"
}
// 如果单一的执行文件,并且是包名
"bin": "./bin/index.js"
2
3
4
5
- bin/index.js
入口文件,处理命令行逻辑,可以在这里处理子命令。
确保 bin
引用的文件以 #!/usr/bin/env node
开头,指定用node执行脚本,配置用于解决不同用户的 node
路径不同问题,让系统动态去查找 node
来执行脚本。如果报错 no such file or directory
,是因为没有把 node
安装路径添加到系统 PATH
中,去配置下环境变量即可。
#!/usr/bin/env node
process.env.NODE_PATH = __dirname + '/../node_modules/'
const program = require('commander')
const pkg = require('../package.json')
program.version(pkg.version).usage('<command> [options]')
program.command('init').description('create a uni-cli project')
.action(() => require('../commands/init'))
program.parse(process.argv)
if (!program.args.length) {
program.help()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这里我们定义了一个 init
的命令,这个命令会执行 commands/init.js
的子命令。
- commands/init.js
子命令 init
入口,分4步简单说明。
- 基础引入
const path = require('path')
const {
prompt
} = require('inquirer')
const program = require('commander')
const message = require('../lib/message')
const download = require('download-git-repo')
const ora = require('ora')
const fs = require('fs')
2
3
4
5
6
7
8
9
- 交互配置
const templates = require('../templates')
const question = [{
type: 'input',
name: 'name',
message: 'project name',
default: 'uni-template',
filter(val) {
return val.trim()
},
validate(val) {
const validate = (val.trim().split(" ")).length === 1;
return validate || 'project name is not allowed to have spaces.';
},
transformer(val) {
return val || 'uni-template';
}
}]
const question2 = [{
type: 'input',
name: 'description',
message: 'project description',
default: 'uni-app project'
},
{
type: 'list',
name: 'template',
message: 'which template?',
choices: ['uni', 'wepy'],
default: 'uni'
}
]
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
- 执行入口
init()
async function init() {
// 项目名称
const {
name: projectName
} = await prompt(question)
const rootName = path.basename(process.cwd()) // 当前目录
const list = fs.readdirSync(process.cwd()) // 当前目录列表
try {
if (list.length) {
// 目录不为空,且存在于projectName同名目录,提示已存在,结束
let isExist = list.some(name => {
const fileName = path.join(process.cwd(), name)
const isDir = fs.statSync(fileName).isDirectory()
return name === projectName && isDir
})
if (isExist) {
message.error(`项目${projectName}已存在`)
process.exit()
}
}
} catch (err) {
message.error(err)
process.exit()
}
create(projectName)
};
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
- 创建下载
async function create(projectName) {
try {
const {
description,
template
} = await prompt(question2)
const gitPlace = templates[template].path
const spinner = ora('Downloading please wait...');
spinner.start();
// 非GitHub, GitLab, Bitbucket的仓库使用clone
download(`${gitPlace}`, `./${projectName}`, {
clone: gitPlace.includes('uni')
}, (err) => {
if (err) {
message.error(err)
process.exit()
}
fs.readFile(`./${projectName}/package.json`, 'utf8', function(err, data) {
if (err) {
spinner.stop();
message.error(err)
return;
}
const packageJson = JSON.parse(data);
packageJson.name = projectName;
packageJson.description = description;
fs.writeFile(`./${projectName}/package.json`, JSON.stringify(packageJson, null, 2), 'utf8', function(
err) {
if (err) {
spinner.stop();
message.error(err)
} else {
spinner.stop();
message.success('project init successfully!')
message.info(
`${`cd ${projectName}`}
${'npm install'}
${`npm run ${gitPlace.includes('wepy') ? 'dev' : 'serve'}`}
`
);
}
});
});
})
} catch (err) {
message.error(err)
process.exit()
}
}
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
- lib/message.js
封装 console.log()
方法,不详细展开。
- templates.json
这里配置需要 clone
的模板地址。
# 调试
在 cli
目录执行 npm link
,本地关联,之后我们可以简单执行命令查看一下输出:
λ hishion-cli
Usage: index <command> [options]
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
init create a uni-cli project
help [command] display help for command
2
3
4
5
6
7
8
9
10
可以看到命令 commands
中除了默认命令外,多了一个 init
的命令。实际中,我们要求的完整命令是
λ hishion-cli init
# 发布
调试确认无误之后,可以发布。
npm publish
← npm发包 字体文件转base64 →