脚手架
一、要实现什么
希望像@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(可以不带后缀名),内容如下:
1 2
| #!/usr/bin/env node // 告诉系统此脚本用node执行 require('./index.js')
|
在package.json中添加bin字段,内容如下:
1 2 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.包结构
1 2 3 4 5 6 7
| F:\PRIVATEREPO\MYCLI │ index.js │ package.json │ └─bin mycli.js
|
2.mycli.js
1 2
| #!/usr/bin/env node // 告诉系统此脚本用node执行 require('./index.js')
|
3.package.json
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
| { "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
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 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了。
1 2 3 4 5
| { "scripts": { "postinstall": "node some-script.js" } }
|
四、延伸
child_process模块
通过以下方法,你可以在Node.js应用程序中调用npm install命令,安装所需的依赖包,也可以执行其他命令。
1 2 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}`); });
|
1 2 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}`); });
|