背景介绍
最开始这是一个用于对比两个xlsx文件内容差异的小工具,随着想法不断迸发,开始了去往大前端的征途。。。
以下环节记录了逐步演进的过程。
项目地址:github.com/jarlion/xls…
目的
对比两个xlsx文件对比他们的内容差异。
初始项目
使用 nodejs 脚本方式实现
初始化
- 新建一个项目目录(xlxs-comparer)
- 创建基本配置
npm init
按照提示填写项目信息,最终项目目录下生成package.json
使用 TS 作为开发语言(可选)
- 全局安装
npm i -g typescript
这时就可以在命令行工具使用 tsc 命令。
- 生成配置(tsconfig.json)
tsc --init
项目目录下生成了tsconfig.json
打开tsconfig.json根据需要修改一下
// 修改目标可以使用新的语法或者api,例如:String.replaceAll()
"target": "es2016",
// 设置源码路径
"rootDir": "./src",
// 方便调试
"sourceMap": true,
// ts 转化后的 js 路径,修改完这里 package.json 的 main 入口可能也需要修改
"outDir": "./bin/",
- 为了方便我在
package.json,添加个脚本
观察*.ts文件变化自动编译为*.js
"scripts": {
"tsc-w": "tsc -w",
...
}
添加完成把这个脚本跑起来就可以开始写代码了。
使用标准的命令方式(可选)
如果不选,可以直接 node xxx.js 启动入口文件;
选了就标准规范一些。
- 安装
npm i -S command
- 使用
例如index.json代码如下,有疑问可以查看commander教程。
import { Command } from 'commander';
import { init } from './main';
/**
* 命令入口
*/
const program = new Command();
program
.name('xlsx-comparer')
.alias('xc')
.description('xlsx 比较')
.version('0.0.2')
.option('-p, --port <number>', '网页端口', '8100')
.option('-s, --source <string>', '源文件 *.xlsx', ',')
.option('-t, --target <string>', '目标文件 *.xlsx')
.option('-k, --key <string>', '主键名 num|str')
.option('-i, --include-columns <string[,]>', '包含的列 string[]', '')
.option('-e, --excludeColumns <string[,]>', '排除的列 string[]', '')
.option('-h, --hide', '隐藏模式, 此模式不显示没有差异的列', false)
.option('-w, --web', '是否启动 Web 服务器', false)
.action((options) => {
init(options);
});
program.parse();
读取本地文件
为了 ts 能很好的提示,安装一下声明
npm i -D @types/nodes
nodejs 的fs模块,有相关 api 例如:
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmdirSync, statSync, unlinkSync, WriteFileOptions, writeFileSync } from "fs";
需要注意的是这些 api 使用的路径要处理好。
- 使用绝对路径(这个妥妥的),但是写起来比较麻烦,不够灵活,例如:
D:\\workspaces\\nodejs\\xlxs-comparer\\test\\a.xlsx - 使用相对路径:
process.cwd()当前项目的路径(推荐)__dirname当前js文件路径(慎用)。别忘了我们的源文件路径src和项目路径bin是不一样的。 由于原生的api用起来有点繁琐自己封装了常用的文件操作方法:src/utils/osUtil.ts
解析 XLSX 文件
使用 xlsx 库可以读取 *.xlsx 并且解析为 json 数据
- 安装
npm i -S xlsx
- 使用
简单封装了读取方法,示例:src/utils/xlsxUtil.ts
还可以自定义表头等等,在此不展开,具体使用请参考xlsx使用教程。
import { readFile, Sheet2JSONOpts, utils } from 'xlsx';
export function readXlsx<R>(path: string, sheetIndex: number = 0, opts?: Sheet2JSONOpts | undefined): R[] {
const wb = readFile(path);
const sheetName: string = wb.SheetNames[sheetIndex];
if (!wb.Sheets[sheetName]) {
console.error(`> !!! 找不到 Sheet ${sheetName}`);
return [];
}
const sheet = wb.Sheets[sheetName];
const rows = utils.sheet_to_json<R>(sheet, opts);
return rows;
}
一阵捣鼓之后,两个 xlsx 文件对比完成了。
添加一个命令到 package.json
"scripts": {
...
"xc": "node ./bin/server/index.js xc -w -s test/a.xlsx -t test/b.xlsx -k A -i B,C",
}
运行之,很好,但是不完美。
因为对比结果是有了,该如何反馈用户?这里出现了分岔:
1)把结果输出文档:例如直接写 *.txt,*.json,*.xlsx,完结散花。
2)继续完成用户体验,见下文。
前后端分离
上文说到,已经得到 xlsx 对比结果,能不能直观的展示给用户呢?
答案是肯定的 nodejs 就是个服务端,跑起来,把生成个 html ,用浏览器打开,完美。
这就是传统 (原始) 的服务端渲染。
WEB 服务端
使用 express 吧,使用方便(教程丰富)
- 安装
npm i -S express
npm i -D @types/express
- 使用
目前只是用作 Web 服务器,怎么简单怎么来,例如:src\server\Web.ts
import express from 'express';
import { Server } from 'http';
/**
* 网页服务器
* 用于回显对比结果
*/
export class Web {
app!: Server;
constructor(root: string, port = 8100, callback?: Function) {
this.app = express()
.use(express.static(root)).listen(port, () => {
console.log(`-> web on http://localhost:${port}`);
if (callback) callback();
});
}
}
PS:为了防止端口冲突,port 改为传入,从
Web 客户端
新建一个 src/client/目录,客户端文件都放进这里。
- 客户端构建
这里可以选择使用vue,react等生客户端文件,怎么简单怎么来,这里不做展开。
使用自己写的src/utils/htmlUtil.ts辅助生成主文件main.html
样式文件,存入src/client/css/
主要处理页面显示效果。 - 部署
把src/client/下的静态文件同步到bin/client/目录。 - 启动服务端
// 启动 web 服务器
if (web) {
new Web(root,
Number(port),
() =>
openUrl(`http://localhost:${port}/main.html`)
);
}
- 打开浏览器访问
noedjs调用命令打开浏览器
/**
* 使用浏览器打开链接
* @param url
*/
export function openUrl(url: string): void {
// 判断平台
switch (process.platform) {
// Mac 使用open
case "darwin":
spawn('open', [url]);
break;
// Windows使用start
case "win32":
exec(`start ${url}`);
// TODO 环境变量缺少 system32/
// console.log(process.env.PATH);
// spawn('start', [url]).on('error', function (err) { console.error(`!!! ${err.stack}`); });
break;
// Linux等使用xdg-open
default:
spawn('xdg-open', [url]);
}
};
下一步