npm.io
3.0.0 • Published 1 month ago

modern-file-saver

Licence
ISC
Version
3.0.0
Deps
0
Size
46 kB
Vulns
0
Weekly
0
Stars
3

modern-file-saver

A modern file saving library for browsers that uses the File System Access API when available and falls back to the traditional download method when necessary.

NPM Version NPM Downloads

Table of Contents

Features

  • Modern browser support with File System Access API
  • Automatic fallback for older browsers
  • Safe file handling
  • Multiple input formats supported
  • Enhanced base64 support
  • TypeScript support
  • Zero dependencies
  • Tiny size (~3.1 kB minified, ~1.5 kB gzipped)

Installation

npm install modern-file-saver

Usage

import { saveFile } from 'modern-file-saver';

// Basic usage
await saveFile('Hello World', { fileName: 'hello.txt' });
Bundle Variants

The library provides both regular and minified bundles. You can choose which one to use based on your needs:

// Regular bundle (default)
import { saveFile } from 'modern-file-saver';

// Minified bundle
import { saveFile } from 'modern-file-saver/min';

Both variants are available in CommonJS and ES Module formats and include TypeScript type definitions.

Supported Input Types

The library supports various input formats like strings, base64, blobs, objects, and more. See the Examples section for detailed usage examples.

type InputType =
    | string // Plain text, base64, or data URLs
    | Blob // Binary data with type information
    | ArrayBuffer // Raw binary data
    | Uint8Array // Binary data
    | URLSearchParams // Form data as URL parameters
    | FormData // Form dataserialised as application/x-www-form-urlencoded
    | object; // Will be JSON.stringified
Options
interface SaveOptions {
    // Default: input.name (if File) or 'download'
    fileName?: string;

    // Default: based on input type
    // - text/plain for strings
    // - application/json for objects
    // - application/octet-stream for binary data
    // - application/x-www-form-urlencoded for URLSearchParams and FormData
    mimeType?: string;

    // Default: true
    // When true, uses File System Access API's native save dialog in supporting browsers
    // When false, forces the traditional download method
    promptSaveAs?: boolean;

    // Default: false
    // When true, treats string input as base64 encoded data
    isBase64?: boolean;

    // Default: 'none'
    // Enable debug logging to console
    logLevel?: 'debug' | 'none';
}
Base64 Handling

The library supports three ways to handle base64 data:

  1. Data URLs (automatic detection):
// Data URL is automatically detected and decoded
await saveFile('data:text/plain;base64,SGVsbG8gV29ybGQ=', {
    fileName: 'hello.txt'
});
  1. Raw base64 with flag:
// Raw base64 string needs the isBase64 flag
await saveFile('SGVsbG8gV29ybGQ=', {
    fileName: 'hello.txt',
    isBase64: true,
    mimeType: 'text/plain'
});
  1. Plain text (default):
// Without isBase64 flag, base64-looking strings are treated as plain text
await saveFile('SGVsbG8gV29ybGQ=', {
    fileName: 'encoded.txt'
});
Debug Logging

Enable debug logging to understand the file saving process:

await saveFile(data, {
    fileName: 'data.json',
    logLevel: 'debug' // Will show operations in console
});

Debug logs are prefixed with [modern-file-saver] and include information about:

  • Input type detection
  • Blob conversion
  • API selection (File System Access API vs fallback)
  • Error handling

Browser Support

Modern Browsers (Chrome, Edge)
  • Full support with File System Access API
  • Native save dialog
  • Secure context (HTTPS) required
Other Browsers (Firefox, Safari)
  • Automatic fallback to traditional download method
  • Compatible with all supported input types
  • No special requirements
Legacy Browsers
  • Requires modern JavaScript features (ES2024)

Note: The published bundle is compiled to ES2024 – supported by all current evergreen browsers (Chrome, Edge, Firefox incl. ESR, Safari). The engines.node field requires Node.js 20+ for development/build only; the runtime is browser-only.

Limitations
  • File System Access API:
    • Not available in iframes
    • Requires secure context (HTTPS)
    • User permission required
  • Base64:
    • Large base64 strings may impact performance
    • Memory usage proportional to data size
  • FormData:
    • File inputs not supported in FormData
    • All values converted to strings

Examples

Basic Usage
// String (plain text)
await saveFile('Hello World', { fileName: 'hello.txt' });

// String (base64 with explicit flag)
await saveFile('SGVsbG8gV29ybGQ=', {
    fileName: 'decoded.txt',
    isBase64: true,
    mimeType: 'text/plain'
});

// String (data URL)
await saveFile('data:text/plain;base64,SGVsbG8gV29ybGQ=', {
    fileName: 'data.txt'
});

// Blob
const blob = new Blob(['Hello World'], { type: 'text/plain' });
await saveFile(blob, { fileName: 'blob.txt' });

// ArrayBuffer
const buffer = new TextEncoder().encode('Hello World').buffer;
await saveFile(buffer, { fileName: 'buffer.txt' });

// Uint8Array
const uint8 = new TextEncoder().encode('Hello World');
await saveFile(uint8, { fileName: 'binary.txt' });

// URLSearchParams
const params = new URLSearchParams({ hello: 'world' });
await saveFile(params, { fileName: 'params.txt' });

// FormData
const formData = new FormData();
formData.append('hello', 'world');
await saveFile(formData, { fileName: 'form.txt' });
Advanced Examples
// Save File object (the filename will be used automatically)
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async event => {
    const file = event.target.files[0];
    await saveFile(file); // will use file.name as fileName

    // Or override with custom filename
    await saveFile(file, { fileName: 'custom.txt' });
});

// Save object as JSON (automatic conversion)
const data = {
    users: [
        { id: 1, name: 'John', role: 'admin' },
        { id: 2, name: 'Jane', role: 'user' }
    ],
    metadata: {
        version: '1.0',
        exported: new Date().toISOString()
    }
};
// Object will be automatically stringified
await saveFile(data, { fileName: 'data.json' });

// CSV export
const csvData = [
    ['id', 'name', 'email'],
    ['1', 'John Doe', 'john@example.com'],
    ['2', 'Jane Smith', 'jane@example.com']
]
    .map(row => row.join(','))
    .join('\n');

await saveFile(csvData, {
    fileName: 'users.csv',
    mimeType: 'text/csv'
});

// API Response saving
try {
    const response = await fetch('https://api.example.com/data');
    const blob = await response.blob();
    await saveFile(blob, {
        fileName: 'api-data.json',
        mimeType: 'application/json'
    });
} catch (error) {
    console.error('Failed to save API data:', error);
}

// Canvas export (with data URL handling)
const canvas = document.querySelector('canvas');
if (canvas) {
    const dataUrl = canvas.toDataURL('image/png');
    await saveFile(dataUrl, {
        fileName: 'canvas-export.png'
    });
}

// Binary data handling with MIME type override
const response = await fetch('https://example.com/data');
const arrayBuffer = await response.arrayBuffer();
await saveFile(arrayBuffer, {
    fileName: 'data.bin',
    mimeType: 'application/octet-stream'
});

// Force legacy download method
await saveFile(data, {
    fileName: 'legacy.txt',
    promptSaveAs: false // Bypasses File System Access API
});

Migrating from file-saver

file-saver has not had a release since November 2020 and several modern-browser concerns are not addressed. modern-file-saver is a zero-dependency alternative that adds File System Access API support (native save dialog in Chrome/Edge) with automatic fallback for other browsers, and ships with first-class TypeScript types and ES Modules.

file-saver modern-file-saver
Last release 2.0.5 (November 2020) Active
File System Access API
TypeScript types (via @types) built-in
Native ESM build (UMD only)
exports / sideEffects field
Bundle size (min + gzip) ~1.3 kB ~1.5 kB
Zero dependencies
npm provenance

modern-file-saver is slightly larger because it supports more input types (objects, base64, data URLs, FormData, URLSearchParams, plain text, …) and a debug logger; file-saver accepts Blob and URL strings (auto-fetched via XHR).

API Comparison
// file-saver
import { saveAs } from 'file-saver';
saveAs(blob, 'hello.txt');

// modern-file-saver
import { saveFile } from 'modern-file-saver';
await saveFile(blob, { fileName: 'hello.txt' });

The main differences:

  • saveFile is async (returns Promise<void>) – it awaits the File System Access API dialog and cleans up resources correctly
  • URL strings are not fetched automatically – fetch the response yourself and pass the Blob for full control over error handling
  • fileName is passed as part of the options object instead of a second positional argument

Development

This repo uses pnpm as its package manager. The version is pinned via the packageManager field in package.json and is activated automatically by Corepack (shipped with Node.js):

# Enable Corepack once (no-op if already enabled)
corepack enable

# Install dependencies
pnpm install

# Run tests in Chrome and Firefox
pnpm test

# Run tests in watch mode
pnpm run test:watch

# Build the library
pnpm run build

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

When your change warrants a release (bug fix, new feature, breaking change), please include a changeset:

pnpm exec changeset

This will prompt you for the change type (patch / minor / major) and a short description that will appear in the changelog.

License

ISC

Keywords