npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

savekit

v1.0.0

Published

现代化的浏览器端文件保存库,TypeScript 实现,支持 File System Access API、进度回调、可取消下载

Downloads

131

Readme

savekit

现代化的浏览器端文件保存库 · TypeScript · 零依赖 · ESM 优先 · Tree-shakeable

npm version CI bundle size license

🎮 在线演示 → https://dingh123.github.io/savekit/ · English


savekit 是对经典 FileSaver.js 的现代重写,针对 2026 年的浏览器环境去掉了 IE / 旧 Edge / 老 iOS WebView 的兼容包袱,并补齐了:

  • TypeScript 原生类型,严格模式编译
  • Promise + 回调await save(...)onStart / onProgress / onSuccess / onError / onAbort 两套 API 并存
  • 下载进度:远程 URL 拉取过程支持字节级进度回调
  • 可取消AbortSignal 一键中断下载或写入
  • File System Access API:Chromium 89+ 弹出真正的"另存为"对话框,并支持流式写入
  • 多种输入Blob | File | ArrayBuffer | ArrayBufferView | ReadableStream | string | { url }
  • 零依赖、零副作用,ESM/CJS 双格式产物

安装

pnpm add savekit
# 或 npm install savekit
# 或 yarn add savekit

快速上手

import { save } from 'savekit';

// 1. 保存文本
await save('hello, world', { filename: 'hello.txt' });

// 2. 保存 Blob
const blob = new Blob([JSON.stringify({ a: 1 })], { type: 'application/json' });
await save(blob, { filename: 'data.json' });

// 3. 下载远程文件并保存(带进度)
await save(
  { url: 'https://example.com/big.zip' },
  {
    filename: 'big.zip',
    onProgress: (e) => {
      if (e.phase === 'downloading' && e.total) {
        console.log(`${((e.loaded / e.total) * 100).toFixed(1)}%`);
      }
    },
  },
);

API

save(data, options?)

function save(data: SaveData, options?: SaveOptions): Promise<SaveResult>;

saveAs(data, filename?, options?)

兼容原 FileSaver.js 的签名,便于迁移:

import { saveAs } from 'savekit';
await saveAs(blob, 'report.pdf');

SaveData —— 支持的输入类型

| 类型 | 说明 | |---|---| | Blob / File | 直接保存 | | ArrayBuffer / ArrayBufferView | 自动包成 Blob | | ReadableStream<Uint8Array> | 流式保存(File System Access 路径下不中转 Blob) | | string | 当作文本,默认 MIME text/plain;charset=utf-8 | | { url: string } | 先下载,再保存;区别于上面的 string,避免歧义 |

SaveOptions

| 字段 | 类型 | 默认 | 说明 | |---|---|---|---| | filename | string | — | 文件名。缺失时根据 URL / MIME 推断;自动清洗 Windows 非法字符 | | mimeType | string | — | 覆盖输入自带的 MIME | | autoBom | boolean | false | 对 UTF-8 文本/XML 注入 BOM(兼容 Excel 等) | | signal | AbortSignal | — | 用于取消下载或写入 | | preferFilePicker | boolean | true | 优先使用 File System Access API 弹"另存为"对话框 | | pickerTypes | FilePickerAcceptType[] | — | 传给 showSaveFilePicker 的文件类型 | | onStart | (info) => void | — | 策略选定、文件名确定后触发一次 | | onProgress | (event) => void | — | 下载 / 写入阶段持续触发 | | onSuccess | (result) => void | — | 成功时触发一次 | | onError | (error) => void | — | 失败(非用户取消)时触发一次 | | onAbort | () => void | — | 用户取消 picker 或 signal.abort() 时触发一次 |

SaveProgressEvent

interface SaveProgressEvent {
  loaded: number;
  total?: number;
  phase: 'normalizing' | 'downloading' | 'picking' | 'writing' | 'done';
}

SaveResult

interface SaveResult {
  filename: string;
  bytes: number;
  method: 'file-system-access' | 'anchor-download' | 'data-url';
  aborted: boolean;
  durationMs: number;
}

回调与 Promise 的关系

回调与 await 是并存的:

try {
  const result = await save(data, {
    onProgress: (e) => updateBar(e),
    onSuccess: (r) => toast.success(`已保存 ${r.filename}`),
    onError: (e) => toast.error(e.message),
    onAbort: () => toast.info('已取消'),
  });
  // result.aborted === false
} catch (err) {
  // err 是 SaveError / SaveAbortError / SaveDownloadError 之一
}

终态三选一互斥:要么 onSuccess、要么 onAbort、要么 onError。回调内部抛错不会影响保存流程,会被吞掉并打到 console.error

错误类型

import { SaveError, SaveAbortError, SaveDownloadError } from 'savekit';
  • SaveError —— 基类
  • SaveAbortError extends SaveError —— 用户取消或 signal 中断;.reason'user' | 'signal'
  • SaveDownloadError extends SaveError —— 远程下载失败;.url.status

保存策略的选择

在浏览器中,save() 会按以下优先级选择策略:

  1. file-system-access(推荐):可用时通过 window.showSaveFilePicker 弹出系统级"另存为"对话框,并支持流式写入,对大文件友好
  2. anchor-download<a download> + URL.createObjectURL,覆盖绝大多数现代浏览器
  3. data-urlFileReader + data: URL 兜底,主要为 iOS Safari 老版本

返回的 SaveResult.method 字段告诉你实际走了哪条路径,便于埋点。

file-saver 迁移

- import { saveAs } from 'file-saver';
+ import { saveAs } from 'savekit';

  saveAs(blob, 'report.pdf');

差异点:

  • saveAs(string) 在旧库里被当作 URL;本库视为文本内容。要下载远程文件请改用 save({ url }, ...)
  • saveAs 现在返回 Promise,原同步签名同样工作。
  • 不再支持 IE / 旧 Edge。

浏览器兼容

| 浏览器 | 最低版本 | 走的策略 | |---|---|---| | Chrome / Edge | 90+ | file-system-access | | Firefox | 100+ | anchor-download | | Safari | 14+ | anchor-download | | iOS Safari | 13+ | anchor-download / data-url |

Node.js / SSR 环境下 save() 会抛 SaveError('No available save strategy'),请仅在客户端调用。

开发

pnpm install
pnpm test           # 单测
pnpm typecheck      # tsc --noEmit
pnpm lint           # biome check
pnpm build          # 出 dist/
pnpm playground     # 本地手测页面(在线演示同源代码)

发版流程

本仓库采用 tag 触发的发布模式:日常提交只会跑 CI 与构建演示页,不会发包。需要发版时:

# 1. 确认 package.json 的 version 是目标版本
# 2. 提交所有改动后,执行:
pnpm release:tag

release:tag 会读取 package.jsonversion,创建 vX.Y.Z 的带注解 tag 并推送到远端。GitHub Actions 上的 release.yml 监听到 tag 后会自动跑完整流水线并以 --provenance 方式发布到 npm。

Playground 演示页会在每次 push 到 main 时由 deploy-playground.yml 自动构建并部署到 GitHub Pages。

致谢

灵感与原始实现来自 Eli GreyFileSaver.js(MIT)。

License

MIT