使用node开发工作流脚本

之前本来打算学学shell写脚本的,后来发现用PHP或者node等语言都可以实现相关的功能,且逻辑描述更清晰。最近刚好项目需要写一个简化工作流的脚本,因此在此整理一下过去写node脚本的经验。

<!--more-->

1. 获取命令行参数

这种需求是比较常见的

console.log(process.argv)

然后执行命令node index.js -ids 1,2,3

[ '/usr/local/bin/node',
  '/Users/Txm/Desktop/script-demo/build.js',
  '-ids',
  '1,2,3' ]

因此可以通过下面方式获取到命令行参数

var args = process.argv.splice(2);
console.log(args) // ['-ids','1,2,3' ]

2. npm scripts

package.json中添加脚本运行命令是一个比较常见的做法,甚至

npm i 
npm run dev
npm run build

都成了开发的基本步骤了,下面是npm scripts中有几个比较有用的特性,参考

2.1. 钩子

比如下面的配置

{
   "scripts": {
        "build": "node index.js",
        "prebuild": "node pre.js",
        "postbuild": ""
   }, 
}

使用npm run build的时候,会自动先执行prebuild内的逻辑 异步代码 程序会等待预先的钩子所有任务全部执行后才会进行下一步,包括所有所有异步任务

// pre.js
console.log('pre')

setTimeout(() => {
    console.log('pre interval')
}, 2000);

// index.js
console.log('build')

// 输出
pre 
pre interval 

build

因此可以放心地通过钩子来完成一些必要的任务。

这里遇见的一个问题是:如何在钩子函数中传递实际脚本的参数。比如

npm run build -ids 1,2

对应的命令行参数只能在build任务中获取到,在prebuild中是获取不到的,这样的限制是比较有局限。

目前没有查到更好的解决办法,暂通过shelljs进行hack处理,其原理是通过在prebuild.js中调用

shellJS.exec('node build.js')

PS:关于shelljs在下面的常用工具章节会提到。

然后修改build任务为node prebuild.js来实现的,不过这种混淆了语义性,也不是一种很可取的做法,待我再研究一下。

2.2. 环境变量

区分开发环境和生成环境也是十分常见的生产需求,这个实现起来也比较方便

NODE_ENV='production' node build.js

即在指令前设置NODE_ENV变量,然后通过

let nodeEnv = process.env.NODE_ENV

就可以访问到对应的环境变量了。同理,在npm scripts中也可以使用这种方法设置环境变量。

3. 子进程

nodejs默认是单进程的,但可以通过child_process这个模块实现多进程。参考

Node.js 的父进程与衍生的子进程之间会建立 stdin、stdout 和 stderr 的管道。 数据能以非阻塞的方式在管道中流通。 有些程序会在内部使用行缓冲 I/O,虽然这并不影响 Node.js,但发送到子进程的数据可能无法被立即使用。

这里不讨论如何实现多进程,在日常的脚本中,也可以通过child_process来实现一些有趣的功能,比如文件IO、FTP等功能。

const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);

ls.stdout.on('data', (data) => {
  console.log(`输出:${data}`);
});

ls.stderr.on('data', (data) => {
  console.log(`错误:${data}`);
});

ls.on('close', (code) => {
  console.log(`子进程退出码:${code}`);
});

上面这段代码是文档里面的~说实话我对这个模块也不是特别了解,正在摸索中...

4. 常用工具库

node生态圈为我们提供了大量的工具,用于提高开发效率,下面是我目前使用到的一些工具,感觉挺好用的。

4.1. shelljs

有时候需要直接运行命令行指令,shelljs为我们提供了完美的解决方案。

在大多数情况下,使用exec方法就可以解决大部分问题了

let shellJS = require('shelljs)

let command = `npm run build`
shellJS.exec(command)

如果需要连续执行多个指令,或者需要获得某条指令的返回结果,可以参阅shelljs的API文档,里面封装了诸如grepecho等常用命令。

要想高效使用shelljs,应当学习linux指令而不是接口本身。

4.2. js-xlsx

如果脚本有处理excel表格的需求,那么[js-xlsx](https://www.npmjs.com/package/js-xlsx)是一个不错的选择。

下面是我封装的一个常用的工具函数。

let XLSX = require('js-xlsx')
let fs = require('fs')

module.exports = excelPath => {
    const workbook = XLSX.readFile(excelPath)

    const sheetNames = workbook.SheetNames
    let worksheet = workbook.Sheets[sheetNames[0]]
    let rows = XLSX.utils.sheet_to_json(worksheet)

    let row = rows[0]
    let header = Object.keys(row)

    return {
        getHeader() {
            return header
        },
        getRows() {
            return rows
        },
        // 保存数据,貌似库文件本身没有提供相关方法,因此需要自己封装
        saveRows(rows, header, fileName = 'data.xlsx') {
            let _headers = header || this.getHeader()
            let headers = _headers
                .map((v, i) => Object.assign({}, {v: v, position: String.fromCharCode(65 + i) + 1}))
                .reduce((prev, next) => Object.assign({}, prev, {[next.position]: {v: next.v}}), {});
            let data = rows
                .map((v, i) => _headers.map((k, j) => Object.assign({}, {
                    v: v[k],
                    position: String.fromCharCode(65 + j) + (i + 2)
                })))
                .reduce((prev, next) => prev.concat(next))
                .reduce((prev, next) => Object.assign({}, prev, {[next.position]: {v: next.v}}), {});

            let output = Object.assign({}, headers, data);
            let outputPos = Object.keys(output);
            let ref = outputPos[0] + ':' + outputPos[outputPos.length - 1];
            let wb = {
                SheetNames: ['Sheet1'],
                Sheets: {
                    'Sheet1': Object.assign({}, output, {'!ref': ref})
                }
            };

            XLSX.writeFile(wb, fileName);
        },
        // 将表格数据导出为JSON
        saveJson() {
            fs.writeFile('./data.json', JSON.stringify(rows), 'utf-8', function (err) {
                if (err) {
                    throw err
                }
            })
        }
    }
}

4.3. log4js

如果需要生成日志文件,那么log4js你值得拥有~ PS:貌似之前的写JavaScript的调试那篇博客中已经安利过一次了...

let log4js = require("log4js"),
    logger = log4js.getLogger();

logger.level = "info";

logger.info("info wtf");
logger.debug("debug wtf"); // 由于debug的等级低于info,因此该调试代码不会输出

5. 小结

刚到新公司熟悉开发环境,由于公司规模比较大,流程也比较多,因此通过脚本来运行日常工作任务,比如切换开发环境、修改host等功能,提高的工作效率是十分可观的。

虽然工作是写前端,但是没必要把自己约束在某一个固定的岗位上,反正都是写代码嘛,能用代码跑的就不要手动去处理了~

关于shell脚本和linux指令,还有很多需要学习的地方,任重而道远啊。