Skip to content

Dashboard 解压 zip 调研

当前的问题

Dashboard 项目里面安装了各种解压 zip 文件的包,有 yauzl,extract-zip,adm-zip ,还有根据系统调用 spawn 的实现。

很混乱,希望通过这次调研,可以统一解压的方式,删除其余实现。

npm 包对比

yauzlextract-zipadm-zipunzipperarchiver
Issues open
Issues closed
Downloads
Bugs
Dependents
Install size
GitHub stars
TypeScript support
Last commit
symlink support✔️✔️

选择的依据:

  • 软连接的支持(编辑器内部有软连接)
  • 较少的依赖
  • 较多的下载量

综合以上数据,yauzl 是下载量最大的,但是它提供的接口相对底层。所以最终选择 extract-zip,它是对 yauzl 的一个简单的封装。对外提供更简单的接口。

系统命令 vs npm 包

由于是 electron 应用,所以我们可以通过调用系统的命令来解压,下面是系统命令和 npm 包解压速度的对比。

每个方式测试了 5 次,取个大概的平均值。

extract-zip系统命令(调用 spawn)
Windows1 分 15 秒左右1 分 10 秒左右
Mac26 秒左右33 秒左右

Mac 原生自带 unzip 命令,Windows 平台是通过 7z 来解压。

7z 提供了命令行使用的版本,下载后缀为 .7z 的版本。注意看描述是: 7-Zip Extra: standalone console version, 7z DLL, Plugin for Far Manager

注意:

dashboard 当前在 Windows 平台是通过 unzip.exe 来解压的。和 7z 对比之后发现 7z 的性能更好,(特别是在需要覆盖目标路径的情况下)所以在 Windows 的测试里以 7z 为标准。

兼容性

npm 包是 nodejs 的实现,本来就是跨平台的,没有兼容性问题。

系统命令是区分平台实现,且 Windows 平台需要携带 7z.exe 文件(828kb)。

测试代码

js
'use strict';
import { spawn } from 'node:child_process';
import extract from 'extract-zip';
import { join } from 'node:path';

const root = process.cwd();

/**
 * @param src
 * @param dist
 * @returns
 */
export function unzipDarwin(src, dist) {
    return new Promise((resolve, reject) => {
        const child = spawn('unzip', ['-o', src, '-d', dist]);

        child.stdout.on('data', () => {}); // 必须消费标准输出,否则可能导致进程阻塞或挂起
        child.stderr.on('data', console.error);

        child.on('close', (code) => {
            if (code === 0) {
                resolve();
            } else {
                reject(code);
            }
        });

        child.on('error', (err) => {
            reject(err);
        });
    });
}

export function unzipWin32(src, dist) {
    return new Promise((resolve, reject) => {
        //const child = spawn(join(root, 'unzip.exe'),['-n', src, '-d', dist]);

        const child = spawn(join(root, '7za-2408.exe'), ['x', '-y', '-aoa', src, `-o${dist}`]);

        child.stdout.on('data', () => {});
        child.stderr.on('data', console.error);

        child.on('close', (code) => {
            if (code === 0) {
                resolve();
            } else {
                reject(code);
            }
        });

        child.on('error', (err) => {
            reject(err);
        });
    });
}

export function unzipNpm(src, dist) {
    return extract(src, { dir: dist });
}
js
import { join } from 'node:path';
import { homedir } from 'os';
import { rimraf } from 'rimraf';
import { unzipDarwin, unzipWin32, unzipNpm } from './unzip.js';

const root = process.cwd();

const downloads = join(homedir(), 'Downloads');
const src = join(downloads, 'unzip-test-file.zip'); // 请在下载目录放置一个用于测试的 zip 文件
const dist = join(root, 'node_modules/0unzip/'); // 解压到 node_modules 不会影响 git

async function testDarwin() {
    if (process.platform !== 'darwin') {
        return;
    }
    await rimraf(dist);
    console.time('unzipDarwin');
    await unzipDarwin(src, dist);
    console.timeEnd('unzipDarwin');
}

async function testWin32() {
    if (process.platform !== 'win32') {
        return;
    }
    await rimraf(dist);
    console.time('unzipWin32');
    await unzipWin32(src, dist);
    console.timeEnd('unzipWin32');
}

async function testNpm() {
    await rimraf(dist);
    console.time('unzipNpm');
    await unzipNpm(src, dist);
    console.timeEnd('unzipNpm');
}

await testDarwin();
await testWin32();
await testNpm();

结论

个人偏向直接使用 npm 包的方式,选择 extract-zip 来当做唯一的解压包。