记一次完整的npm包开发 --- 发布过程

司空叶五
2023-12-01

记一次完整的npm包开发 — 发布过程

前言
本人也是菜鸟一枚,现学现开发的,各个文档也尚未熟练,主要面向百度开发,因此文章中只会列出用到的API,更具体的API说明文档请参考最下边的参考链接部分, 如果有能力, 籍此可以尝试开发 CLI 工具

一、npm包初始化

  1. 创建目录并初始化npm
    mkdir auto_whs && cd auto_whs && npm init

  2. git 创建仓库, 初始化项目, 代码仓库地址写入 package.json 中的 repository 下的 url 字段值

  3. 创建index.js 在开头写入以下代码, 然后就可以在Git命令行用 ./index.js 执行代码了

      #!/usr/bin/env node
      console.log('hello world')
    
  4. 去掉 ./ 路径, 使用 bin 字段下的 auto_whs 执行

    1. 在 package.json 添加代码
      "name": "auto_whs",
      "bin": {
        "auto_whs": "index.js"
      }
    
    1. 执行 npm link 绑定就可以用 auto_whs(package.json中的name) 直接执行了

    npm link 用来在本地项目和本地npm模块之间建立连接,可以在本地进行模块测试

      npm WARN ka@1.0.0 No description
      npm WARN ka@1.0.0 No repository field.
      up to date in 1.026s
      found 0 vulnerabilities
      D:\nodejs\ka -> D:\nodejs\node_modules\ka\index.js // 建立的命令的位置
      D:\nodejs\node_modules\ka -> C:\Users\Hollysys\Desktop\auto_whs // 建立的模块的位置
    
  5. npm unlink 解除 link 绑定

    • 一般来说使用 npm link 后, 在本地就可以测试了, 当将包发布到npm.org后,如果要测试线上下载的包
      就需要进行 npm unlink 进行本地连接解除,
    • where <命令>where yarn, 可以查找 link 后命令的位置(如 where auto_whs), 如果 unlink 失败, 可以进入路径位置,手动删除,并且删除 node_modules 中的包
  6. npm uninstall <package> -g 在npm unlink成功后删除包

二、npm包发布更新

接着就是中间开发功能的过程, 过程后续部分来说, 假设已经开发完成,那么就要发布了

  1. commit 所有代码更改到 git

  2. 注册npm用户 https://www.npmjs.com/

  3. npm login 输入用户名/密码 登录npm

  4. npm publish 发布包,成功后可在npm官网搜索查看

  5. 修改代码后,更新包的版本

    npm采用语义化版本,共三位,以 . 隔开,从左至右依次代表:主版本(major)、次要版本(minor)、补丁版本(patch)。

    1. npm version <patch|minor|major> 会自动修改包的版本加1, 就是将package.json中的version版本号加1, 根据更新内容决定更新的版本号
    2. npm publish
  6. npm unpublish 包名@version 删除指定版本的包

  7. npm unpublish 包名 --force 删除整个包

三、shell.js

  1. 脚本可以通过 child_process 模块新建子进程, 从而执行unix命令
      // child_process 模块是 Node的模块,如 fs,path 一样可以直接require
      // util 模块是Node的模块, 封装了很多通用方法,其中promisify可以将异步方法包装成promise
      const util = require('util')
      const exec = util.promisify(require('child_process').exec);
      (async () => {
        // const res = await exec('这里是要执行的命令')
        const res = await exec('echo hello world')
        console.log(res) // { stdout: 'hello world\r\n', stderr: '' }
      })()
    
  2. shelljs 模块重新包装了child_process, 调用命令更加方便
    1. npm i shelljs -S
    2. 引入并使用
      /**
       * 本地模式, 需要从shell调用方法
      */
      const shell = require('shelljs');
      // 调用shell的exec方法执行命令, 其实调用的child_process.exec
      // 默认调用命令会在命令行中输出命令调用的信息, 通过 silent: true 可以不在命令行中输出命令调用的输出
      let message = shell.exec(
        `git log --no-merges --pretty=%s --author="${username}" --after="${startTime}" --before="${endTime}"`
      , {silent: true}).toString().trim(); // commit说明信息
    
      shell.cd(dir) // 切换目录
      shell.echo('echo 输出内容') // echo输出内容
      shell.chmod(755, <file|dir>) // 修改权限
      shell.cat() // 查看
      shell.cp() // 复制
      shell.mkdir() // 创建目录
      shell.pwd() // 查看当前所在目录
      shell.mv() // 重命名或者移动文件、目录
      shell.rm() // 删除
      /**
       * 全局模式, 可以直接在文件中写命令
      */
      require('shelljs/global')
    

四、inquirer.js

一个用户与命令行交互的工具

  1. 安装并引入
      // npm i inquirer -S
      const inquirer = require('inquirer'); 
    
  2. 提问问题的方法
      /**
       * 方法: inquirer.prompt(questions) -> promise   questions 数组类型
       *        
       * type: 表示提问的类型
       *    -- input 输入框
       *    -- password 密码输入框 密码不显示
       *    -- confirm  询问框 y/n
       *    -- list 选择列表  choices可用
       *    -- rawlist 带编号的选择列表  choices可用
       *    -- expand 带缩写选择列表  choices可用
       *    -- checkbox 多选  choices可用
       *    -- editor
       *  message   问题的描述
       *  name      存储当前问题回答的变量,即字段名
       *  validate  对用户输入的值或者做的选择做校验
       *  default   问题的默认结果值 
       *  choices   列表选项,在某些type下可用,并且包含一个分隔符(separator)
       *  filter    对用户的回答进行过滤处理,返回处理后的值
       *  transformer 对用户回答的显示效果进行处理(如:修改回答的字体或背景颜色),但不会影响最终的答案的内容
       *  when      根据前面问题的回答,判断当前问题是否需要被回答
       *  pageSize  修改某些type类型下的渲染行数
       *  prefix    修改message默认前缀
       *  suffix    修改message默认后缀。
      */
      
      // 说明: 字符串后跟 .red/green 等, 是用了 colors 插件 对命令行输出文字改变颜色
      const initQuestions = [{
          type: 'input',
          message: 'whs用户名:'.green,
          name: 'whsUser',
          validate: (value) => {
            if (!value) {
              const {
                whsUser = ''
              } = readConfig();
              if (whsUser) return true
              return 'whs用户名不能为空'.red
            }
            return true
          }
        },
        {
          type: 'password',
          message: 'whs密码:'.green,
          name: 'whsPwd',
          default: '',
          validate: (value) => {
            if (value) {
              return true;
            } else {
              return '密码不能为空'.red
            }
          }
        },
        {
          type: 'confirm',
          message: '是否保存信息?'.magenta,
          name: 'isSaveConfig',
          default: true
        },
        {
          type: 'list',
          message: '请选择一种水果:',
          name: 'fruit',
          choices: [
              "Apple",
              "Pear",
              "Banana"
          ],
          filter: function (val) { // 使用filter将回答变为小写
              return val.toLowerCase();
          }
        },
        {
          type: 'rawlist',
          message: '请选择一种水果:',
          name: 'fruit',
          choices: [
            "Apple",
            "Pear",
            "Banana"
          ]
        },
        {
          type: "expand",
          message: "请选择一种水果:",
          name: "fruit",
          choices: [
              {
                  key: "a",
                  name: "Apple",
                  value: "apple"
              },
              {
                  key: "O",
                  name: "Orange",
                  value: "orange"
              },
              {
                  key: "p",
                  name: "Pear",
                  value: "pear"
              }
          ]
        },
        {
          type: "checkbox",
          message: "选择颜色:",
          name: "color",
          choices: [
              {
                  name: "red"
              },
              new inquirer.Separator(), // 添加分隔符
              {
                  name: "blur",
                  checked: true // 默认选中
              },
              {
                  name: "green"
              },
              new inquirer.Separator("--- 分隔符 ---"), // 自定义分隔符
              {
                  name: "yellow"
              }
          ]
        },
        {
          type: "editor",
          message: "请输入备注:",
          name: "editor"
        }
      ]
      const answers = await inquirer.prompt(initQuestions) // await  需要在async关键字下使用,此处未做
      console.log(answers) // 交互完毕得到的结果,可以用来后续操作
    

五、commander.js

完整的Node.js命令行(参数)解决方案

  1. 安装并引入

      // npm install commander
      // 声明 program 变量, 为简化使用,Commander 提供了一个全局对象
      const { program } = require('commander');
    
  2. 简单使用

  • 定义版本号

      program.version('0.0.1') // version 定义版本
    
  • 选项 program.option()

    • .option() 定义选项,同时可以附加选项简介
    • 每个选项可以定义一个段选项(-后面跟单个字符)和一个长选项(–后面接一个或多个单词), 使用逗号、空格、或者 | 分隔
      // <dir> 表示 -a或者 --auto 后边的参数传值必填
      // [dir] 表示 -e或者 --exec 后边的参数传值可选
      // 通过program.parse(arguments)方法处理参数,没有被使用的选项会存放在program.args数组中
      // .option(arg1, arg2, [arg3]) arg1 定义选项、arg2定义说明, arg3定义默认值, 
      program
          .option('-a, --auto', 'auto job_booking')
          .option('-p, --project-path <dir>', 'project fulPath') // 项目目录路径
          .option('-e, --exec [dir]', 'config and exec')
          .parse(process.argv) 
    
      const program_opts = program.opts() // 获取命令行参数选项
      const program_args = program.args // 获取命令行参数
      console.log(program_opts) // 如 auto_whs -a   结果: { auto: true, projectPath: undefined, exec: undefined }
      console.log(program_args) // 如 auto_whs config -a  可以解析到参数结果: ['config']
    
  • 通过.command()或.addCommand()可以配置命令,有两种实现方式:为命令绑定处理函数,或者将命令单独写成一个可执行文件

  • .command()的第一个参数可以配置命令名称及参数,参数支持必选(尖括号表示)、可选(方括号表示)及变长参数(点号表示,如果使用,只能是最后一个参数)。

      // 通过绑定处理函数实现命令(这里的指令描述为放在`.command`中)
      // 返回新生成的命令(即该子命令)以供继续配置
      program
        .command('clone <source> [destination]')
        .description('clone a repository into a newly created directory')
        .action((source, destination) => {
          console.log('clone command called');
        });
    
      // 通过独立的的可执行文件实现命令 (注意这里指令描述是作为`.command`的第二个参数)
      // 返回最顶层的命令以供继续添加子命令
      program
        .command('start <service>', 'start named service')
        .command('stop [service]', 'stop named service, or all if no name supplied');
    
      // 分别装配命令
      // 返回最顶层的命令以供继续添加子命令
      program
        .addCommand(build.makeBuildCommand());  
    
  • command 自定义命令关联外部可执行文件

  1. program.command(‘add’, ’ This is a add command’) 形如这样定义命令,没有action回调函数,有第二个参数的情况,当执行auto_whs add命令的时候会从入口文件所在的同级目录查找可执行文件,查找的可执行文件名为program-command形式,以 - 连接,后边是定义的命令, 前边是入口文件名,若入口文件名有后缀名,会被去掉, 比如 入口文件为index.js 则会查找 index-add 文件(有无后缀名均可)
  2. program.command(‘add’) 形如这样的,没有action 也没有第二个参数,则什么都不执行, 不会报错
  3. program.comman(‘add’).action(() => {}) 有action 会执行 action回调, 另外需要注意的是,这样定义命令需要放在最后,否则action的参数并非是当前执行的命令,可能会达不到你预期想要的执行操作
  4. program.comman(‘add’, ‘这是第二个参数对命令的说明’).action(() => {}) 有第二个参数也有action, 会执行action, 不会查找外部可执行文件

六、colors.js

一个用于 node.js 终端 console.log 的颜色库,可以给命令行中输出不同颜色的文字

  // npm install colors
  var colors = require('colors');
  console.log('hello'.green); // outputs green text
  console.log('i like cake and pies'.underline.red) // outputs red underlined text
  console.log('inverse the color'.inverse); // inverses the color
  console.log('OMG Rainbows!'.rainbow); // rainbow
  console.log('Run the trap'.trap); // Drops the bass
  console.log(colors.green('这是绿色'))

colors 支持的样式

colors 不仅仅支持输出的文字颜色,还支持输出的背景颜色、字体样式等

  • 支持的字体颜色
    • black
    • red
    • green
    • yellow
    • blue
    • magenta 洋红色
    • cyan 青色
    • white
    • gray
    • grey 灰色

七、puppeteer

Puppeteer 是一个 Node 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。Puppeteer 默认以 headless 模式运行,但是可以通过修改配置文件运行“有头”模式。 由头模式 就是 { headless:false } 会启动chromium打开浏览器,而默认是不会打开的.

  1. 安装
      // 安装的过程会自动下载最新版的Chromium以保证API可以使用
      // Chrominum 是从国外下载的, 所以一般会失败报错
      // 可以通过环境变量类配置不下载chrominum,具体请百度或查看文档PUPPETEER_SKIP_CHROMIUM_DOWNLOAD
      npm install puppeteer -S
    
      // 另一种方式是 替换puppeteer插件,使用 puppeteer-chromium-resolver
      // 这个插件会在下载的时候 自动切换下载源,比如 用cnpm下载过程中,走到下载chrominum时会自动切换到npm源
      // 同样的,引入方式也会发生改变,具体请看下文
      npm install puppeteer-chromium-resolver -S
    
  2. 引入使用
    //  puppeteer 插件时候引用方式
    (async () => {
      const browser = await puppeteer.launch(); // 创建浏览器实例,会唤起chrominum 浏览器窗口
      const page = await browser.newPage(); // 创建一个新tab页
      await page.goto('https://www.baidu.com'); // 打开百度首页
      await page.screenshot({path: 'example.png'}); // 截屏
    
      await browser.close(); // 关闭浏览器实例
    })();
    
    // puppeteer-chrominum-resolver 插件时候引用方式
    const PCR = require("puppeteer-chromium-resolver");
    const stats = await PCR();
    const browser = await stats.puppeteer.launch({headless: false,
      args: ["--no-sandbox"],
      executablePath: stats.executablePath,
      defaultViewport: {
        width: 1550, // 设置浏览器窗口大小
        height: 722,
        slowMo: 20 // 设置peppeteer操作的运行速度, 可以更清晰的看清运行过程
      }
    })
    const page = await browser.newPage();
    await page.goto('https://www.baidu.com'); // 打开网页
    
  3. 设置窗口大小
      Page.setViewport()
    
  4. 生成PDF
      await page.pdf({path: 'hn.pdf', format: 'A4'});
    
  5. 上传
    const el = await page.$(selector)
    await el.uploadFile(...filePaths) // 设置输入这些路径的文件的值。如果某些 filePaths 是相对路径,那么它们将被解析为相对于 当前工作目录
    
  6. 其他的一些使用到的API
      page.on('console', msg => console.log('PAGE LOG:', msg.text())); // 捕获console输出
      const el = await page.$(selector) // 获取一个元素
      el.click() // 点击元素
      page.$$(selector) // 获取一类元素
      page.$eval(selector, pageFunction[...args]) // 获取元素后执行pageFunction,
      page.waitFor(string|number|function>) // 选择器 方法 或超时时间, 就是 等待的意思,或者等待元素出现
    
  7. sleep函数
      // 自己写了个简单的 sleep() 等待函数
        function sleep (timeStamp) {
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve()
            }, timeStamp)
          })
        }
        sleep(3000) // 利用js的单线程, 等待3秒以阻塞代码执行
    

八、nexe

nexe 可以将node应用打包成 exe 可执行程序

九、收藏夹吃灰记语

虽然只是一个简单的可以与命令行交互的npm包的开发,但是用到的插件也是比较多,重要的不是功能如何, 而且了解一个 CLI 的开发流程,npm包的开发、发布、更新流程,涉及的文档也有一定的量,还需要学习才能熟悉啊。

十、相关参考链接

  1. https://www.w3cschool.cn/jhnpsm/mxfyoozt.html Node.js命令行程序开发教程
  2. https://segmentfault.com/a/1190000017461666 npm包发布教程系列
  3. http://documentup.com/shelljs/shelljs shelljs英文文档
  4. https://www.npmjs.com/package/inquirer inquirer.js英文文档
  5. https://github.com/tj/commander.js/blob/master/Readme_zh-CN.md Commander.js中文文档
  6. http://bullyork.github.io/2017/09/27/yargs/ yargs中文教程类似于Commander.js
  7. https://www.npmjs.com/package/colors colors.js文档
  8. https://github.com/nexe/nexe nexe文档
  9. https://zhaoqize.github.io/puppeteer-api-zh_CN/#/ puppeteer中文API文档
  10. https://blog.csdn.net/qupan1993/article/details/85371556 网上一篇 puppeteer 学习笔记

十一、附一部分 npm 相关命令

  1. npm info 包名 // 查看npm远程仓库当前包的最新信息,版本等
  2. npm root [-g] // 在标准输出上将有效的 node_modules 文件夹打印出来。
  3. npm ls 包名 // 查看当前有效的指定包的版本
  4. npm ls -g 包名
  5. npm list [-g] // 查看打当前安装的包
  6. npm update 包名 // 更新升级指定包
  7. npm uninstall 包名 // 从当前node_modules 删除包
  8. npm uninstall 包名 -S // 从package.json的 dependencies 删除
  9. npm uninstall 包名 -D // 从package.json的 devDependencies 删除
  10. npm config set registry http://registry.npmjs.org 临时设置淘宝镜像
  11. npm install -g cnpm --registry=https://registry.npm.taobao.org
  12. npm -v 查看npm版本
  13. npm login 登录npm
  14. npm publish 发布(将包发布到npm), package.json中的仓库地址一定要存在
  15. npm version patch 修改包的版本,在原来版本上加1
  16. npm unpublish 包名@version 删除指定版本的包
  17. npm unpublish 包名 --force 删除整个包

十二、附 nrm 相关命令

nrm(npm registry manager) 是npm的镜像源管理工具, 可以迅速的在下载源地址之间切换

  1. nrm ls 查看当前可选的源地 (带*的是当前使用的源)
    npm -------- https://registry.npmjs.org/
    yarn ------- https://registry.yarnpkg.com/
    cnpm ------- http://r.cnpmjs.org/
    taobao ----- https://registry.npm.taobao.org/
    nj --------- https://registry.nodejitsu.com/
    npmMirror -- https://skimdb.npmjs.com/registry/
    edunpm ----- http://registry.enpmjs.org/
  * hollysys --- http://192.168.88.50:8081/repository/group-npm/

  1. nrm test [镜像源] 测试相应源的速度,可测所有的速度
  2. nrm use [镜像源] 切换镜像源名,如hollysys
  3. nrm add registry 添加镜像源, registry 镜像源名 url 镜像源地址
  4. nrm delete 删除对应的源
 类似资料: