【玩转前端】一步步教你如何做一个简单的前端node工具

765 阅读5分钟

背景

这段时间因项目需求,需要写一个node的工具,用于扫描一些结构文件,并输出扫描的结果。 本质上其实是一个node工具,类似于vue cli这样的node工具。

这里,我们不弄这么麻烦,我们做一个简单的从远端git库中下载文件,并记录下载的文件数量即可。

本文所涉及的代码,均已记录在 示例仓库-node-tool中,如有需要可自取!

任务拆解

node工具需要做以下事情:

  • 命令行交互,用于接收以下信息
    • 项目名称,可以输入中文
    • git库地址,可能需要校验下地址是不是绝对地址
    • 用户名和密码,密码输入时不可见
  • 下载远端git库中的文件,并存储至本地
  • 【进阶】发布包

开始实现

项目初始化

在gitee.com中创建一个仓库,例如: node-tool

image.png 创建好仓库后,将代码clone下来。

进入到项目目录下,然后,在项目录下打开命令行,并输入: yarn initnpm init

根据提示,填写相应的信息,例如:

James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ yarn init
yarn init v1.17.3
question name (node-tool):
question version (1.0.0):
question description: A simple front-end node tool, which supports global installation and use, and supports command-line interaction
question entry point (index.js):
question repository url (git@gitee.com:hellojameszhang/node-tool.git): https://gitee.com/hellojameszhang/node-tool.git
question author (jameszhang <james.zxj@foxmail.com>):
question license (MIT):
question private:
success Saved package.json
Done in 111.13s.

添加脚本和命令行工具入口

  "bin": {
      "scan": "./bin/index.js"
  },
  "type": "commonjs",
  "scripts": {
    "start": "node ./bin/index.js"
  },
  "engines": {
    "node": ">=12.16"
  }

关于package.json,大家可以参考npm package.json,官方文档是正解!

这里简单介绍下用到的几个属性:

  • scripts, 是一个字典,它包含有package生命周期的不同时间运行的脚本命令。键是生命周期事件,值是要在该点运行的命令。例如:
    • prepublish,在打包和发布包之前运行,也可以在本地运行
    • prestart,在npm start命令运行执行前运行,也可以在本地运行
    • 关于scripts相关的更多细节,可以参阅:npm package.json scripts
  • engines, 指定包需要在哪个node版本中运行,如果不指定,默认所有node版本中均可运行。
  • type: commonjs, 不特别指定的话,默认此项值为: commonjs, 也可指定值为:module, 即支持ESM规范(import / export)
  • bin, 许多软件包都有一个或多个可执行文件,在全局安装后,会安装到 PATH 中包提供的可执行文件,安装后时,npm 会将该文件符号链接到
    • 本地安装: ./node_modules/.bin/
    • 全局安装:prefix/bin,与npm全局命令的路径相同。

通过以上配置后,我们在项目目录下添加新的文件目录和文件:

#!/usr/bin/env node

console.log('>>> hello node tool')

image.png

内容很简单,添加一个打印信息,以表明功能可正常运行!其中 ./bin/index.js文件的第一行: #!/usr/bin/env node 表明,需要指定使用node来执行此文件:

  • windows环境中,会从环境变量中查找node安装路径下的bin目录查找node来执行脚本。
  • linux环境下,会查找用户的/usr/bin下的env环境变量中查找node来执行脚本。

若不添加此行代码,在全局安装后,可能会报如下报错:

$ node-tool
C:\Users\James\AppData\Roaming\npm/node_modules/node-tool/bin/index.js: line 1: syntax error near unexpected token `'>>> hello node tool''
C:\Users\James\AppData\Roaming\npm/node_modules/node-tool/bin/index.js: line 1: `console.log('>>> hello node tool')'

本地安装&验证

在当前项目目录下(与package.json同级)执行以下命令,将通过本地目录,全局安装node-tool包 npm install -g .

安装好后,我们在命令行执行一下,试试看有没有效果:

James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ node-tool
>>> hello node tool

可以看到,我们的命令生效了,说明可以通过全局安装包执行命令了。

命令行交互

完成上面的步骤,基本上我们一个简单的框架算是搭建完成了,接下来我们丰富下工具包,添加命令行交互相关的功能。 接下来,我们将会使用到以下2个包:

  • Inquirer, 交互式命令行提示工具
  • commander, 一个轻巧的nodejs模块,提供了用户命令行输入和参数解析强大功能,有提供中文文档 commander-中文文档

关于这2个包的详细使用方法,可自行查阅文档,这里我们将只会演示使用到的场景,就不“搬砖”了。

首先,我们先安装这2个包:

yarn add inquirer@^8.0.0 commander

注意: inquirer在9.x版本后,原生支持ESM语法,若仍想使用commonjs语法,需安装8.x版本.详细可参考官方文档。

image.png

添加交互

接下来,我们将实现一个简单的交互功能,其主要提示用户输入以下信息,且每一项都必须填写:

  • 应用名称
  • 仓库地址
  • 用户名
  • 密码,不可明文展示

添加如下代码:

#!/usr/bin/env node
const inquire = require('inquirer')

async function main(){
  const answers = await inquire.prompt([
    {
      type: 'input',
      name: 'application',
      message: 'please input your application name: ',
      validate: (str) => required(str, 'please input your application name')
    },
    {
      type: 'input',
      name: 'url',
      message: 'please input your git repo url: ',
      validate: (str) => required(str, 'please input your git repo url')
    },
    {
      type: 'input',
      name: 'username',
      message: "please input gitee.com's username: ",
      validate: (str) => required(str, "please input gitee.com's username")
    },{
      type: 'password',
      name: 'password',
      message: "please input gitee.com's password: ",
      validate: (str) => required(str, "please input gitee.com's password")
    }
  ])
  console.log(answers)
}
main()

function required(str, message){
  if(str) return true
  return message
}

试着运行一下吧,正常应该可以得到类似如下的信息:

James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ yarn start
yarn run v1.17.3
$ node ./bin/index.js
? please input your application name:  node-tool
? please input your git repo url:  https://gitee.com/hellojameszhang/node-tool.git
? please input gitee.com's username:  username
? please input gitee.com's password:  [hidden]
{
  application: 'node-tool',
  url: 'https://gitee.com/hellojameszhang/node-tool.git',
  username: 'username',
  password: 'password'
}
Done in 27.73s.

至此,基本的交互功能,我们基本实现了。

优化命令行

接下来,我们优化下我们的命令,即:把commander用起来。通过它,我们将实现以下命令:

  • scan help
  • scan --version
  • scan version
  • scan repo

最终效果可能如下:

James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ scan --help
Usage: index <command> [options]

Options:
  -V, --version   output the version number
  -h, --help      display help for command

Commands:
  version
  repo            扫描单个仓库文件
  help [command]  display help for command

James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ scan version
node-tool 1.0.0

James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ scan repo --help
Usage: index repo [options]

扫描单个仓库文件

Options:
  -h, --help  display help for command

James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$

要想实现上述功能,我们就需要使用到commander. 基于我们前面实现的交互功能,再新增如下代码(完整的代码,可通过node-tool仓库查看下载):

const {program} = require('commander')

program
  .version(`node-tool ${require('../package').version}`)
  .usage('<command> [options]')
  .command('version')
  .action(() => {
    console.log(`node-tool ${require('../package').version}`)
  })

program
  .command('repo')
  .description('扫描单个仓库文件')
  .action(async (str, options) => {
    console.log('==>', str, options)
    await main()
  })
  
// other code ...

program.parse()

到此,基本功能已实现,这里我们稍做些解释

  • prgram.command, 创建子命令
  • prgram.description, 子命令的描述,在帮助信息中可以看到
  • program.action, 子命令对应的动作
  • program.parse, 解析命令

有关commander更详细的介绍,可以参阅:commander-中文文档

下载远端git库中的文件

基于前面我们已实现的功能,接下来,开始做我们真正需要做的事情:下载远端git库中的文件。

要实现这个功能,我们安装一个npm包,再稍微包装下即可完成功能: yarn add download-git-repo

download-git-repo 是一个用来从代码仓库中下载代码用的包

安装好包之后,我们在根目录下仓库一个utils目录,用于存储工具函数,并在此目录下添加index.js文件,其内容如下:

const download = require('download-git-repo')


function downloadRepo(url, distDir, options = {clone: true}){
  return new Promise((resolve, reject) => {
    download(url, distDir, options, function(err){
      if(err){
        console.error(`download file from ${url} failed!`)
        reject(err)
      } else {
        resolve(distDir)
      }
    })
  })
}

/**
 * @description: 是否为绝对地址
 * @param {String} url 
 * @returns {Boolean}
 */
 function isAbsoluteUrl(url) {
  // const reg = new RegExp("^(https):\/\/[\\w\\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?")
  const reg = new RegExp("^(https):\/\/[\\w-_]+(\\.)+[\\w\\-_]+")
  return reg.test(url)
}

function getRepoUrlByAuth(username, password, url){
  if(isAbsoluteUrl(url)) {
    const prefix = 'https://'
    const repoUrl = url.substring(prefix.length)
    let downloadUrl = `direct:${prefix}${encodeURIComponent(username)}:${encodeURIComponent(password)}@${repoUrl}`
    if(repoUrl.indexOf('#') <= -1){
      downloadUrl += '#master'
    }
    return [true, downloadUrl]
  }
  return [false, '请输入绝对地址,如:https://gitee.com/demoRepo/']
}
module.exports = {
  downloadRepo,
  getRepoUrlByAuth,
  isAbsoluteUrl
}

接下来,修改下 /bin/index.js文件,接收命令行传入的地址,或提示用户输入仓库地址,修改部分的代码为:

async function main(url){
  const answers = await inquire.prompt(PROMPT)
  const rawUrl = url ? url : answers.url
  const [flag, downloadUrl] = getRepoUrlByAuth(answers.username, answers.password, rawUrl)
  if(!flag){
    const message = downloadUrl
    console.error(message)
  } else {
    const storageDir = './repos/' + answers.application
    await downloadRepo(downloadUrl, storageDir)
    console.log('download repo success!')
  }
}

完整的代码修改部分,可以参考如下变更集: gitee.com/hellojamesz…

接下来,我们全局安装下,简单验证下功能。

James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ npm install -g .
James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ scan repo https://gitee.com/hellojameszhang/node-tool.git
? please input your application name:  node-tool
? please input gitee.com's username:  hellojames
? please input gitee.com's password:  [hidden]
download repo success!

到此,工具包的基本功能已实现!

进阶:发布包

发包前,我们需要在npmjs中注册一个帐号,注册过程这里就不细述了。 发布包是比较简单的,我们尝试下。 基本分为2个步骤:

  • npm login, 登录
  • npm publish, 发布包

登录npm

输入命令npm login,然后按提示输入相应信息即可:

James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ npm login
npm notice Log in on https://registry.npmjs.org/
Username: jameszhang
Password:
Email: (this IS public) james.zxj@foxmail.com
npm notice Please check your email for a one-time password (OTP)
Enter one-time password: 82719745
Logged in as jameszhang on https://registry.npmjs.org/.

发布包

正常输入命令npm publish即可:

James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ npm publish
npm notice 
npm notice 📦  node-tool@1.0.0
npm notice === Tarball Contents ===
npm notice 92B   .gitee/ISSUE_TEMPLATE.zh-CN.md
npm notice 190B  .gitee/PULL_REQUEST_TEMPLATE.zh-CN.md
npm notice 924B  README.en.md
npm notice 1.0kB README.md
npm notice 2.1kB bin/index.js
npm notice 692B  package.json
npm notice 1.3kB utils/index.js
npm notice === Tarball Details ===
npm notice name:          node-tool
npm notice version:       1.0.0
npm notice filename:      node-tool-1.0.0.tgz
npm notice package size:  2.9 kB
npm notice unpacked size: 6.3 kB
npm notice shasum:        6e6669f2c38fae550b22b69b1036c6727334369b
npm notice integrity:     sha512-gyK5eQe9mR0R+[...]yuCTN/f0d1Xeg==
npm notice total files:   7
npm notice
npm notice Publishing to https://registry.npmjs.org/
npm ERR! code E403
npm ERR! 403 403 Forbidden - PUT https://registry.npmjs.org/node-tool - Package name too similar to existing package nodetool; try renaming your package to '@jameszhang/node-tool' and publishing with 'npm publish --access=public' instead
npm ERR! 403 In most cases, you or one of your dependencies are requesting
npm ERR! 403 a package version that is forbidden by your security policy, or
npm ERR! 403 on a server you do not have access to.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\James\AppData\Local\npm-cache\_logs\2022-07-26T14_21_57_550Z-debug-0.log

可以发现,已经存在有一个名为node-tool的包名了,我们需要更换一个包名,这里我们修改为了james-node-tool,然后,我们再尝试发包一次:

James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ npm publish
npm notice 
npm notice 📦  james-node-tool@1.0.0
npm notice === Tarball Contents ===
npm notice 92B   .gitee/ISSUE_TEMPLATE.zh-CN.md
npm notice 190B  .gitee/PULL_REQUEST_TEMPLATE.zh-CN.md
npm notice 924B  README.en.md
npm notice 1.0kB README.md
npm notice 2.1kB bin/index.js
npm notice 698B  package.json
npm notice 1.3kB utils/index.js
npm notice === Tarball Details ===
npm notice name:          james-node-tool
npm notice version:       1.0.0
npm notice filename:      james-node-tool-1.0.0.tgz
npm notice package size:  2.9 kB
npm notice unpacked size: 6.3 kB
npm notice shasum:        308eea0c327ea9cd97dfa34be41dbc70b16dd907
npm notice integrity:     sha512-QGoJ873jdX74j[...]Lhf/X/4vKV3EA==
npm notice total files:   7
npm notice 
npm notice Publishing to https://registry.npmjs.org/
+ james-node-tool@1.0.0

到此,包已经发布出去,此时,我们就可以通过npm install -g james-node-tool进行全局安装和使用了。

总结

通过这次实践,对于前端制作node工具库不再那么陌生了,可以造更多的轮子了。 希望整理的这些资料,对各位看官有所帮助,欢迎各位在评论区讨论交流!

参考链接

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿