脚手架
一、要实现什么
希望像@vue/cli一样的方式
1.安装我的脚手架包,基于@vue/cli
2.能全局使用我的命令来创建项目,能提示并根据用户输入的参数来创建
3.创建的项目是写好的脚手架模板,集成了所需的组件和工具和配置
4.cd进新创建的项目
5.执行npm install,执行完npm install后自动执行一个脚本,来安装本地组件库,减少了手动操作,像patch-package
6.执行npm run dev,自动启动项目,并自动打开浏览器
二、需要怎么做
1.要是一个npm包的形式
2.安装后要能全局使用命令,来下载模板项目
3.开发模板项目,上传到git
1.新建文件夹
新建目录,cd进mycli目录,开发mycli的npm包
2.执行npm init
生成package.json
3.打开package.json
以下为常用字段说明:
3.1 name: 包名称
3.2 version: 版本号
3.3 description: 描述
3.4 main: 入口文件
3.5 scripts: 脚本
3.6 author: 作者
3.7 license: 许可证
3.8 keywords: 关键字
3.9 dependencies: 依赖
3.10 bin: 命令
4.main字段说明
这里在根目录下新建入口文件index.js。如果想以esmodule的方式导入导出模块,需要文件后缀为.mjs,并且设置type: “module”
5.scripts字段说明
它的每一个属性都对应一个脚本。我们在项目当中运行npm run xxx的时候,主要分为以下几步:
1、从package.json当中读取scrips选项。
2、以传给npm run命令的第一个参数作为键,在scripts中找到要执行的命令,没有找到会报错。
3、找到命令后,自动创建一个shell,其中只要是shell可以运行的命令,就可以写在npm script当中。
4、将当前目录下的node_modules/.bin这个子目录加入PATH变量(这就意味着,当前目录的node_modules/.bin子目录里的所有脚本,都可以直接用脚本名调用,而不需要加路径)
5、在这个shell上执行上述命令
6.bin字段说明
1、首先我们要清楚,能在命令行中识别的命令,一定是在环境变量中能找到的,所以当我们安装nodejs后,会在电脑环境变量中增加nodejs命令目录,然后就能全局使用node和node全局目录下的所有命令
2、为什么全局安装 @vue/cli 后添加的命令为 vue。
包安装时根据package.json中bin对象,key作为环境变量中可执行命令,value指向实际运行的js文件
在根目录下新建bin目录(bin 目录用来存放可执行命令的文件夹),在bin目录下新建mycli.js(可以不带后缀名),内容如下:
| 12
 
 | #!/usr/bin/env node // 告诉系统此脚本用node执行require('./index.js')
 
 | 
在package.json中添加bin字段,内容如下:
| 12
 3
 
 | "bin": {"mycli": "bin/mycli.js"
 }
 
 | 
这样,在包全局安装的时候,下载包到全局 node_modules 中
js读取package.json的bin字段创建可执行文件放在全局node_modules中(能在环境变量中找到),指向bin/mycli.js文件
然后就能在全局使用mycli命令了
执行mycli命令,会在环境变量中查找,然后使用node环境执行bin/mycli.js文件
7.准备项目模板
以上步骤准备好了npm包,接下来就准备好项目模板,上传至git仓库,在脚本中使用
三、实战代码
1.包结构
| 12
 3
 4
 5
 6
 7
 
 | F:\PRIVATEREPO\MYCLI│  index.js
 │  package.json
 │
 └─bin
 mycli.js
 
 
 | 
2.mycli.js
| 12
 
 | #!/usr/bin/env node // 告诉系统此脚本用node执行require('./index.js')
 
 | 
3.package.json
| 12
 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
 
 | {"name": "mycli",
 "version": "1.0.0",
 "description": "我的脚手架",
 "main": "index.js",
 "scripts": {
 "dev": "node bin/mycli",
 "test": "echo \"Error: no test specified\" && exit 1"
 },
 "bin": {
 "mycli": "bin/mycli"
 },
 "keywords": [
 "cli",
 "typescript",
 "node"
 ],
 "author": "me",
 "license": "ISC",
 "dependencies": {
 "co": "^4.6.0",
 "co-prompt": "^1.0.0",
 "commander": "^2.15.1",
 "ora": "^5.4.1"
 }
 }
 
 
 | 
4.index.js
| 12
 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
 
 | 'use strict'
 const exec = require('child_process').exec;
 const co = require('co');
 const prompt = require('co-prompt');
 const ora = require('ora');
 const program = require('commander');
 const packageInfo = require('./package.json');
 const spinner = ora('正在生成...');
 
 
 program.version(packageInfo.version)
 
 program
 .command('init')
 .description('生成一个项目')
 .alias('i')
 .action(() => {
 
 
 
 const resolve = (result) => {
 const { projectName } = result;
 
 const url = 'https://github.com/***/***.git'
 const cmdStr = `git clone ${url} ${projectName}`;
 
 spinner.start();
 
 exec(cmdStr, (err) => {
 execRm(err, projectName);
 });
 };
 
 
 const execRm = (err, projectName) => {
 if (err) {
 console.log(JSON.stringify(err));
 console.log('fail', '请重新运行!');
 process.exit();
 }
 
 exec('cd ' + projectName + ' && rd /s /q ".git"', (err, out) => {
 execFinish(err, projectName);
 });
 }
 
 
 const execFinish = (err, projectName) => {
 spinner.stop();
 
 if (err) {
 console.log(JSON.stringify(err));
 console.log('err', '请手动删除或重新初始化模板项目的.git文件夹!');
 process.exit();
 }
 
 console.log('suc', '初始化完成!');
 console.log(`cd ${projectName} && npm install`);
 process.exit();
 };
 
 
 co(function *() {
 const projectName = yield prompt('项目名字: ');
 return new Promise((resolve, reject) => {
 resolve({
 projectName,
 });
 });
 }).then(resolve);
 });
 
 program.parse(process.argv);
 if(!program.args.length){
 program.help()
 }
 
 | 
5.准备项目模板
1.模板中的package.json中的dependencies要设置好,确保安装完即可正常运行项目,开发时注意最好局部安装而非全局安装
2.package.json中的scripts字段,有个postinstall脚本,这个脚本会在所有包安装完成后执行,在下面这个例子中,some-script.js 会在所有依赖安装完成后运行。所以可以利用他,实现像patch-package的功能,比如安装完依赖后自动npm link 本地组件库,就不用每次都手动npm link了。
| 12
 3
 4
 5
 
 | {"scripts": {
 "postinstall": "node some-script.js"
 }
 }
 
 | 
四、延伸
child_process模块
通过以下方法,你可以在Node.js应用程序中调用npm install命令,安装所需的依赖包,也可以执行其他命令。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | const { exec } = require('child_process');
 exec('npm install <package-name>', (error, stdout, stderr) => {
 if (error) {
 console.error(`exec error: ${error}`);
 return;
 }
 console.log(`stdout: ${stdout}`);
 console.error(`stderr: ${stderr}`);
 });
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | const { spawn } = require('child_process');
 const npm = spawn('npm', ['install', '<package-name>']);
 
 npm.stdout.on('data', (data) => {
 console.log(`stdout: ${data}`);
 });
 
 npm.stderr.on('data', (data) => {
 console.error(`stderr: ${data}`);
 });
 
 |