开发一个问答式的node脚本

前言

我们公司一般有早上知识分享的规定,那天有个同事分享了如何通过Node脚本实现国际化替换
起因是这样的,有一个已经成熟的项目了,突然被要求实现中英文切换。前端中英文切换基本上就是通过
vue-i18n 来实现(不熟悉的可以看一下 使用vue-i18n实现中英文切换) 。

如果在项目刚开始开发的时候就开始做,问题自然不大;但是如果是一个完成的项目做,那工作量有多大可想而知。中英文词库已经做好了,但是替换是一个很艰巨的问题,如果是我的话我可能就傻傻的用 vscode 提供的全局替换功能挨个替换了。

然后就是开头说的那样,同事写了个node脚本 来做这件事,关键这个同事还是java后台。突然认识到了差距,突然绝对自己好傻逼,知识白学了。

不过还好,我之前不会,但是我写完这篇博客后,那我就会了,思路也打开了。感谢这个同事的分享(毕竟很多同事都是随便分享点没什么用的知识)

实现

言归正传,我会基于同事的分享来学习,后面会分享出来一个简单demo

初始化项目

npm init

创建相应文件夹
在这里插入图片描述
script 目录下存放脚本和词典库,src目录就相当于我们项目的业务代码

词库

script目录下创建一个js文件来存放词库

module.exports = {
    'entertainment news': '娱乐新闻',
    'home news': '国内新闻',
    'message settings': '消息设置',
    'privacy settings': '隐私设置'
}

脚本

既然要搞就不能单纯搞个简单的,当然是弄个问答式的。毕竟咱们才是前端吗。

这里推荐一个库:https://github.com/SBoudrias/Inquirer.js,网上挺多教程的可以自己百度

安装

npm install --save inquirer@^8.0.0

简单使用

//引入问答库
const inquirer = require('inquirer');
// 问题
var questions = [
  {
    type: 'input',
    name: 'name',
    message: "你叫什么名字?"
  }
]


inquirer.prompt(questions).then(answers => {
  console.log(`你好 ${answers['name']}!`)
})

在这里插入图片描述

处理词库

首先处理这个字典,key改为value,value改为key,便于我们快速判断字典中是否包含当前中文词组。

let jsonData = require('./word-stock')
/**
 * 字典处理,将key变成value,value变成key便于检索
 */
//key全部为中文的map
let cnKeyMap = new Map();
//key包含特殊字符的map
let otherKeyMap = new Map();

function genMap() {
    for (const key in jsonData) {
        //是否全中文
        if (/^[\u4e00-\u9fa5]+$/.test(jsonData[key])) {
            cnKeyMap.set(jsonData[key], key)
        } else {
            otherKeyMap.set(jsonData[key], key)
        }
    }
    console.log("cnKeyMapSize:" + cnKeyMap.size)
    console.log("otherKeyMap:" + otherKeyMap.size)
}

文件替换逻辑

/**
 * 中英文替换的脚本
 */

//引入问答库
const inquirer = require("inquirer");
//引入fs模块
const fs = require("fs");

let jsonData = require("./word-stock");
/**
 * 字典处理,将key变成value,value变成key便于检索
 */
//key全部为中文的map
let cnKeyMap = new Map();
//key包含特殊字符的map
let otherKeyMap = new Map();

function genMap() {
    for (const key in jsonData) {
        //是否全中文
        if (/^[\u4e00-\u9fa5]+$/.test(jsonData[key])) {
            cnKeyMap.set(jsonData[key], key);
        } else {
            otherKeyMap.set(jsonData[key], key);
        }
    }
    console.log("cnKeyMapSize:" + cnKeyMap.size);
    console.log("otherKeyMap:" + otherKeyMap.size);
}

/**
 * 是否包含中文
 * @param {*} str
 * @returns
 */
function isChinese(str) {
    var reg = /[\u4e00-\u9fa5]/;
    return reg.test(str);
}

/**
 * 是否是注解 左侧有/*或//或<!--,
 * 在他们之后出现的中文就不用替换了
 * @param {*} str
 * @returns
 */
function isNotes(str) {
    let first = str.trimStart();
    return (
        first.startsWith("/*") ||
        str.indexOf("//") != -1 ||
        first.startsWith("<!--")
    );
}


inquirer.prompt([
    {
        type: "input",
        name: "folder",
        message: "请输入完整的文件路径?",
    }
]).then((answers) => {
    //文件路径
    let folder = answers["folder"];
    fs.access(folder, fs.constants.F_OK, (err) => {
        if (err) {
            console.log(`你好, ${folder}不是文件夹或文件!`);
        } else {
            inquirer.prompt([
                {
                    type: "rawlist",
                    name: "fileType",
                    message: "请选择要处理的文件类型",
                    choices: ["All", "vue", "js"],
                },
            ]).then((answers) => {
                //处理字典值
                genMap()
                //文件类型
                let fileType = answers["fileType"];
                replaceWord(folder, fileType);
            })

        }
    });
});

/**
 * 单词替换
 * dir:目录
 * type:需要处理的文件类型
 */

function replaceWord(dir, type) {
    // 记录匹配和没有匹配到的文字
    let matchKey = ''
    let noMatchKey = '';
    //读取当前目录
    fs.readdir(dir, (err, files) => {
        if (err) {
            console.error("读取失败:" + err);
            return;
        }
        //遍历当前目录下的所有文件
        files.forEach((filename) => {
            //拼接文件路径
            let filepath = dir + "/" + filename;
            //判断文件类型
            fs.stat(filepath, (err, stats) => {
                if (err) {
                    console.error("读取失败:" + err);
                    return;
                }
                if (stats.isDirectory()) {
                    //文件夹,继续遍历
                    replaceWord(filepath, type);
                } else {
                    // 文件,读取指定类型的文件
                    let pattList = {
                        All: new RegExp(/[vue,js]$/),
                        vue: new RegExp(/vue$/),
                        js: new RegExp(/js$/)
                    }
                    if (pattList[type].test(filename)) {
                        //如果是指定格式的文件,读取文件并替换词组
                        fs.readFile(filepath, 'utf-8', (err, data) => {
                            //console.log("文件:" + filepath);
                            if (err) {
                                console.error("读取失败:" + err);
                                return;
                            }
                            let lines = [];
                            // 按行读取
                            data.split('\n').forEach((line, index) => {
                                //如果是注释或者这行都没有中文就可以下一行了
                                if (isNotes(line) || !isChinese(line)) {
                                    lines.push(line);
                                    return;
                                }
                                // 首先全词匹配特殊key,如"已开票(元)"这类正则写不来,就循环的看这行有没有这种词就好了,有了就换掉
                                otherKeyMap.forEach((val, key) => {
                                    // replaceAll好像只有15.0.0及以上存在,这个自行百度
                                    line = line.replace(new RegExp(key, 'g'), (match, index) => {
                                        // 判断前边是否有注解,从头开始截取,如果是注解则截取到字符串里一定存在注解符号
                                        if (isNotes(line.slice(0, index))) {
                                            //注解不替换
                                            return match
                                        }
                                        //将匹配到关键字替换为英文
                                        matchKey += '文件' + filepath + ':' + key + "->" + otherKeyMap.get(match) + "\n";
                                        return otherKeyMap.get(match)
                                    })
                                })

                                //匹配中文key,使用正则查找行内的中文词组,有了就替换
                                line = line.replace(/[\u4e00-\u9fa5]+/g, (match, index) => {
                                    if (cnKeyMap.has(match)) {
                                        // 注解不提货
                                        if (isNotes(line.slice(0, index))) {
                                            return match
                                        }
                                        //非注解替换,并记录日志
                                        matchKey += '文件' + filepath + ':' + match + "->" + cnKeyMap.get(match) + "\n";
                                        return cnKeyMap.get(match)
                                    } else {
                                        noMatchKey += '文件' + filepath + ':' + match + "\n";
                                    }
                                    return match
                                })
                                lines.push(line);
                            })
                            //存储日志
                            // console.log(" 匹配字符:\n" + matchKey);
                            // console.error(" 未匹配字符:\n" + noMatchKey);
                            let matchlogpath = './log/匹配.txt'
                            let nomatchlogpath = './log/未匹配.txt'
                            fs.appendFile(matchlogpath, matchKey, 'utf-8', err => {
                                if (err) {
                                    console.log("追加失败!")
                                    return
                                }
                            })
                            fs.appendFile(nomatchlogpath, noMatchKey, 'utf-8', err => {
                                if (err) {
                                    console.log("追加失败!")
                                    return
                                }
                            })
                            //将多行数据重新拼接起来
                            let content = lines.join('\n')
                            //将替换后的文件重新写入文件
                            fs.writeFile(filepath, content, (err) => {
                                if (err) {
                                    console.error(`文件${filepath}写入失败:${err}`)
                                    return
                                }
                                console.log(`文件${filepath}替换成功`)
                            })
                        })

                    }
                }
            });
        });
    })
}

效果图
在这里插入图片描述
注: 关于inquirer的使用没太搞明白,由于文件读取是异步的,导致文件读取还没完成就自动进入第二个提问了,因此目前只能将提问给拆开。

打包

如果只是单纯的一个js文件,是可以直接运行的,但是当你引入了第三方包后,就没法直接运行,否则就会如下图
在这里插入图片描述
所以需要将所引入的依赖一块进行打包。查了一下最简单的是esbuild,一行代码就可以搞定

安装

npm install esbuild

添加命令

 "build": "./node_modules/.bin/esbuild ./script/index.js --bundle --minify --outfile=dist.js --platform=node"

大体意思就是将依赖和项目的启动文件一起打包到dist.js文件中,因此只需要主要

// 启动文件,你项目的启动文件,路径要填写正确
./script/index.js
// 打包后的文件
dist.js

打包

npm run build

打包后的文件
在这里插入图片描述
该文件是可以直接进行运行的
在这里插入图片描述

脚本变成可执行文件

起始之前就研究过了,当时执行完后就直接退出了。我当时一直以为是报错闪退了,直到最近发现是脚本执行完成退出了。。。

用的工具是pkg,好像可以有很多骚操作,不过没找到合适的教程,下面只会介绍如何将node脚本变成可执行文件。

先放一张效果图:
在这里插入图片描述

安装

npm install -g pkg

配置

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "build": "./node_modules/.bin/esbuild ./script/index.js --bundle --minify --outfile=dist.js --platform=node",
  "pkg": "pkg . -t node14-win-x64 --out-path=dist/"
},
"bin": "dist.js",

"pkg": "pkg . -t node14-win-x64 --out-path=dist/" 因为我用的是node14window平台所以这样写的,--out-path=dist/是输出到dist目录下
"bin": "dist.js" 是指定程序的入库文件,pkg会将该文件打包成可执行文件。一定要先进行打包,这样会将所需要的依赖一块打包在一起,而且会进行代码的压缩

打包

npm run pkg

源码下载

关注公众号回复关键词:问答式的node脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无知的小菜鸡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值