简单来说就是在命令行可以使用nodejs来执行的应用,例如:vue-cli、creat-react-app、webpack-cli等;在前端开发过程中我们会用到很多的工具,这些工具在安装过后可以直接使用命令行执行;注意在全局安装和在项目安装不同。

  1. // 全局安装,直接执行命令
  2. > npm install webpack webpack-cli -g
  3. > webpack
  4. // 项目安装,需要借助npx执行
  5. > npm install webpack webpack-cli --save-dev
  6. > npx webpack

1、启动过程:

命令行执行命令 => 根据package.json中bin查询入口 => 执行入口js文件cli.js

2、执行过程:

命令行执行js文件功能启动=> 命令行询问用户问题 => 结合问题答案+模板等文件 => 生成结构文件

1、入口文件路径 ,首先在package.json 中添加bin字段

  1. {
  2. "name": "ncl",
  3. "main": "index.js",
  4. "bin": {
  5. "ncl": "./cli.js" //入口文件,ncl和name保持一致
  6. },
  7. ...
  8. }

2、入口文件特定的文件头 ,在cli.js顶部输入

  1. #!/usr/bin/env node

3、入口文件权限
// 如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
// 具体就是通过 chmod 755 cli.js 实现修改

4、简单测试模块
npm link 可以将模块链接到全局,也可以链接到使用该模块的项目node_modules中;这样在开发模块的过程中,不用发布到npm也可以使用模块进行测试。

  1. > npm link // 在自定义模块项目的目录执行,将模块连接到全局
  2. > ncl // 直接执行模块,使用模块名

注意:在 Window PowerShell 脚本中直接执行模块名不会成功,而在 Windows命令脚本(cmd)中直接执行模块名就可以成功,具体原因尚不清楚。

该示例的功能实现一个自定义的脚手架:在命令行询问用户一些简单的问题作为参数,然后自动生成一些项目文件。其中的文件可以通过模板生成,也可以传递数据到模板。
image.png

1、安装一些依赖模块

  1. > npm install inquirer --save //nodejs环境下,实现命令行的用户交互插件
  2. > npm install ejs --save //模板引擎

2、cli.js中定义命令行询问用户问题

  • inquire.prompt进行命令行的用户询问操作
  • inquirer.prompt返回值为一个promise对象
  • inquirer.prompt的参数为一个数组
  1. const inquirer = require('inquirer')
  2. inquirer.prompt([
  3. {
  4. type: 'input',
  5. name: 'name',
  6. message: '请输入项目名称?'
  7. }
  8. ])
  9. .then(anwsers => {
  10. // anwsers: { name: "xxx" } //anwsers返回一个结果对象
  11. })

3、获取模板目录和目标生成目录

  1. const path = require('path')
  2. // 模板目录
  3. // __dirname 获取当前执行代码文件的绝对路径
  4. // tmplDir 为templates的绝对路径
  5. const tmplDir = path.join(__dirname, 'templates')
  6. // 目标目录
  7. // process.cwd()返回 Node.js 进程的当前工作目录。
  8. // process参考api文档:http://nodejs.cn/api/process.html
  9. const destDir = process.cwd()

4、模板引擎渲染模板

  1. const ejs = require('ejs')
  2. const path = require('path')
  3. // 通过模板引擎渲染文件
  4. // 参数1:fileDir为文件的绝对路径
  5. // 参数2:渲染模板所需变量,存在anwsers对象里面
  6. // 参数3:回调函数,result为新文件
  7. ejs.renderFile(fileDir, anwsers, (err, result) => {
  8. if (err) throw err
  9. // 将结果写入目标文件路径
  10. fs.writeFileSync(fileDestDir, result)
  11. })
  12. // 一个package.json作为模板的示例:
  13. {
  14. "name": "<%= name %>",
  15. "version": "<%= version %>",
  16. "description": "<%= description %>",
  17. "author": "<%= author %>",
  18. "bin": "cli.js",
  19. "scripts": {
  20. "test": "echo \"Error: no test specified\" && exit 1"
  21. },
  22. "license": "ISC"
  23. }

5、读取目录下的文件

  1. // 将模板下的文件全部转换到目标目录
  2. // 参数1:path
  3. // 参数2:回调函数,参数files为文件相对路径组成的数组
  4. fs.readdir(tmplDir, (err, files) => {
  5. if (err) throw err
  6. files.forEach(file => {
  7. // 通过模板引擎渲染文件
  8. // 处理 file
  9. })
  10. })
  11. })

6、将文件写入路径

  1. // 将结果写入目标文件路径
  2. // 参数1:文件的绝对路径
  3. // 参数2:文件内容
  4. fs.writeFileSync(fileDestDir, result)

这里是根据自己的需求将需要自动生成的文件放到模板目录下,没有变化的或者统一的文件就不需要使用模板引擎。
1、模板路径:templates;

2、通常将整理好的项目结构整体拷贝到templates下,例如:vue的示例源文件、lint文件、package.json等等;
image.png

本地开发可以使用npm link关联模块目录和依赖此模块的项目node_modules目录;也可以发布到npm源上后直接安装使用模块。

1、关联模块:

  1. > cd nodejs-cli-sample //Nodejs Cli应用的目录
  2. > npm link // 将模块连接到全局

2、执行模块:

  1. > cd nodejs-cli-demo // 在项目目录执行模块
  2. > ncl // 直接执行模块,使用模块名(ncl 是项目nodejs-cli-sample的名称)

1、可以直接使用npm publish发布到源上

  1. > npm publish --registry=https://registry.xxxx

2、要考虑到npm源是否有写权限,可以发布到自己公司的npm源上或者yarn源上

  1. // 淘宝镜像源是只读的,publish不上去
  2. // 发布到yarn的镜像源之后,使用淘宝镜像源时可以手动同步加快模块下载速度
  3. yarn publish --registry https://registry.yarnpkg.com/

1、NodeJs Cli应用cli.js 入口文件

  1. #!/usr/bin/env node
  2. // Node CLI 应用入口文件必须要有这样的文件头
  3. // 如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
  4. // 具体就是通过 chmod 755 cli.js 实现修改
  5. const fs = require('fs') // 文件读写
  6. const path = require('path') // 路径获取
  7. const inquirer = require('inquirer') //命令行用户交互
  8. const ejs = require('ejs') // 模板引擎
  9. // 脚手架的工作过程:启动 => 命令行询问用户问题 => 结合问题答案+模板 => 生成结构文件
  10. inquirer.prompt([
  11. {
  12. type: 'input',
  13. name: 'name',
  14. message: '请输入项目名称(\'\')'
  15. },
  16. {
  17. type: 'input',
  18. name: 'version',
  19. message: '请输入项目版本号(1.0.0)'
  20. },
  21. {
  22. type: 'input',
  23. name: 'description',
  24. message: '请输入项目备注(\'\')'
  25. },
  26. {
  27. type: 'input',
  28. name: 'author',
  29. message: '请输入作者名称(\'\')'
  30. }
  31. ])
  32. .then(anwsers => {
  33. // anwsers: { name: "xxx" } //anwsers返回一个结果对象
  34. // 模板目录绝对路径
  35. const tmplDir = path.join(__dirname, 'templates')
  36. // 目标目录
  37. const destDir = process.cwd()
  38. // 读取目录下所有文件
  39. let readFiles = (dir) => {
  40. return new Promise((resolve, reject)=>{
  41. // 参数1:目录路径
  42. // 参数2:回调函数(错误对象,files为文件相对路径组成的数组)
  43. fs.readdir(dir, (err, files) => {
  44. if (err) reject(err)
  45. resolve(files)
  46. })
  47. })
  48. }
  49. // 处理模板文件
  50. let ejsRender = (file) => {
  51. return new Promise((resolve, reject)=>{
  52. // 模板文件绝对路径
  53. let dir = path.join(tmplDir, file)
  54. // 参数1:文件路径
  55. // 参数2:数据对象
  56. // 参数3:回调函数(错误对象,渲染后的新文件)
  57. ejs.renderFile(dir, anwsers, (err, result) => {
  58. if (err) reject(err)
  59. resolve(result)
  60. })
  61. })
  62. }
  63. // 1、先读取目录下所有文件
  64. // 2、使用ejs渲染所有模板
  65. // 3、再将新文件写到目标路径
  66. readFiles(tmplDir).then((files)=>{
  67. files.forEach(file => {
  68. ejsRender(file).then((result)=>{
  69. // 目标文件绝对路径,file其实是文件相对路径
  70. let fileDestDir = path.join(destDir, file)
  71. // 将结果写入目标文件路径
  72. // 参数1:文件绝对路径
  73. // 参数2:渲染后新文件
  74. fs.writeFileSync(fileDestDir, result)
  75. },throwError)
  76. })
  77. },throwError)
  78. })
  79. /**
  80. * 错误处理函数
  81. * @param {*错误对象} error
  82. */
  83. function throwError(error){
  84. throw error
  85. }

2、package.json 示例模板文件,使用的ejs模板引擎

  1. {
  2. "name": "<%= name %>",
  3. "version": "<%= version %>",
  4. "description": "<%= description %>",
  5. "author": "<%= author %>",
  6. "bin": "cli.js",
  7. "scripts": {
  8. "test": "echo \"Error: no test specified\" && exit 1"
  9. },
  10. "license": "ISC"
  11. }

转载自:https://segmentfault.com/a/1190000040246466

版权声明:本文为zengzeng91原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/zeng91/p/16292987.html