Skip to content

Repository files navigation

💾 tinysaver

CINPM VERSIONNPM DOWNLOADSLICENSE

Modern replacement of file-saver.js.

📦 Install

npm install tinysaver
yarn add tinysaver
pnpm add tinysaver

🚀 Usage

✨ Basic Usage

import{saveAs,saveAsAsync,saveText,saveJSON,saveCanvas}from'tinysaver'import{saveStream}from'tinysaver/stream'// Save a BlobsaveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt')// Save text contentsaveText('Hello World','greeting.txt')// Save JSON datasaveJSON({name: 'John',age: 30},'data.json',{space: 2})// Save canvas as imageconstcanvas=document.querySelector('canvas')saveCanvas(canvas,'image.png',{quality: 0.95})// Promise-based saveawaitsaveAsAsync(newBlob(['hello async']),'async.txt')// Save from stream/responseconstresponse=awaitfetch('/api/export')awaitsaveStream(response,'export.bin')

🌊 Stream Entry

Stream support is published from a separate entry so applications that only use Blob, URL, text, JSON, or canvas downloads do not bundle the stream implementation.

import{saveStream,StreamDownloader,typeDownloadStreamSource,typeSaveStreamOptions,}from'tinysaver/stream'awaitsaveStream(awaitfetch('/api/export'),'export.bin')constdownloader=newStreamDownloader()awaitdownloader.saveStream(source,'export.bin',options)

saveStream is not exported from tinysaver, and FileDownloader no longer has a saveStream method. Migrate root imports and class usage as follows:

// Beforeimport{FileDownloader,saveStream}from'tinysaver'// Afterimport{saveStream,StreamDownloader}from'tinysaver/stream'

⚙️ With Options

import{saveAs}from'tinysaver'saveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt',{autoBom: true,// Add UTF-8 BOM for text filesclickDelay: 100,// Delay before triggering downloadopenInNewTab: false,// Open in new tab instead of downloadingdisableClick: false,// Disable automatic click simulationonStart(){console.log('Download started')},onComplete(){console.log('Download completed')},onError(err){console.error('Download failed',err)},onProgress(loaded,total){console.log(`${loaded}/${total}`)// total is 0 when size is unknown},timeout: 10_000,// Abort automatically after 10ssignal: abortController.signal,// Manual cancellationfetchOptions: {credentials: 'include'},// For CORS probing requestpreferFileSystemAccess: true,// Use showSaveFilePicker for Blob/stream sourcesonPhaseChange(phase){console.log(phase)// probing/downloading/saving/completed/error/aborted},})

👏 Callbacks

All download methods support lifecycle callbacks:

saveText('content','file.txt',{onStart(){// Called when download process starts},onProgress(loaded,total){// Called during download progressconsole.log(`Downloaded ${loaded}/${total} bytes`)},onComplete(){// Called when download completes},onError(error){// Called when download failsconsole.error(error)},})

🌐 Browser Support

tinysaver targets the following browser versions and newer:

  • Chrome 87
  • Edge 88
  • Firefox 78
  • Safari 14

Internet Explorer is not supported.

📚 API

💾 saveAs(blob, filename?, options?)

Save any Blob or URL as a file. Compatible with FileSaver.js saveAs API.

saveAsAsync(blob, filename?, options?)

Promise-based version of saveAs, suitable for async workflows and explicit error handling.

Parameters:

  • blob - Blob object or URL string
  • filename - Name of the file to save (optional)
  • options - Download options (optional)

📝 saveText(text, filename, options?)

Save text content as a file.

Parameters:

  • text - Text content to save
  • filename - Name of the text file
  • options - Download options with optional mimeType property

📄 saveJSON(data, filename, options?)

Save JSON data as a file.

Parameters:

  • data - JavaScript object or value to save
  • filename - Name of the JSON file
  • options - Download options with optional space property for formatting

🎨 saveCanvas(canvas, filename, options?)

Save HTML canvas as an image file.

Parameters:

  • canvas - HTMLCanvasElement to save
  • filename - Name of the image file
  • options - Download options with optional type and quality properties

🌊 saveStream(source, filename, options?)

Save stream data (ReadableStream, Response, AsyncIterable) as a file. Import it from tinysaver/stream.

When preferFileSystemAccess is enabled and supported, chunks are written directly to the selected file. Other browsers materialize the stream as a browser-managed Blob before starting the download. Abort signals and timeouts cancel the active stream and propagate cancellation to its source.

Blob fallback buffering is limited to 256 MiB by default to protect the browser from unbounded memory growth. Set maxBufferBytes to a positive byte limit when another bound is appropriate for the application.

URL sources always use the browser download transport. File pickers require a transient user gesture and therefore cannot be opened after an asynchronous URL probe or transfer.

✅ Testing

The library includes comprehensive unit tests covering:

  • 📥 FileDownloader core functionality and error handling
  • 🏷️ BOM (Byte Order Mark) insertion for text files
  • 📝 Default filename handling
  • 💾 saveText, saveJSON, and saveCanvas implementations
  • ⚠️ Canvas conversion error handling
  • 🔔 Callback invocation during download lifecycle
  • ⏱️ Timeout/abort behavior and phase callbacks
  • 🌊 Stream-based save flow
  • 📂 File System Access API preferred path

Compatibility Matrix

  • Unit tests run in jsdom for deterministic behavior.
  • Browser tests run in Chromium, Firefox, and WebKit through Playwright.
  • Production builds target Chrome 87, Edge 88, Firefox 78, and Safari 14.
  • Downloads use the standard anchor download attribute and URL API.
  • IE-only and pre-modern browser fallbacks are intentionally excluded.

Run tests with:

pnpm test
pnpm test:browser

🧪 Playground

The Vue playground contains interactive examples for saving text, JSON, canvas artwork, and generated streams directly in the browser.

pnpm playground

Run pnpm playground:build to verify its production bundle.

🙏 Credits

📄 License

MIT License © 2025-PRESENT ntnyq

About

📦 Modern replacement of FileSaver.js.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - ntnyq/tinysaver: :package: Modern replacement of FileSaver.js. · GitHub
Skip to content

Repository files navigation

💾 tinysaver

CINPM VERSIONNPM DOWNLOADSLICENSE

Modern replacement of file-saver.js.

📦 Install

npm install tinysaver
yarn add tinysaver
pnpm add tinysaver

🚀 Usage

✨ Basic Usage

import{saveAs,saveAsAsync,saveText,saveJSON,saveCanvas}from'tinysaver'import{saveStream}from'tinysaver/stream'// Save a BlobsaveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt')// Save text contentsaveText('Hello World','greeting.txt')// Save JSON datasaveJSON({name: 'John',age: 30},'data.json',{space: 2})// Save canvas as imageconstcanvas=document.querySelector('canvas')saveCanvas(canvas,'image.png',{quality: 0.95})// Promise-based saveawaitsaveAsAsync(newBlob(['hello async']),'async.txt')// Save from stream/responseconstresponse=awaitfetch('/api/export')awaitsaveStream(response,'export.bin')

🌊 Stream Entry

Stream support is published from a separate entry so applications that only use Blob, URL, text, JSON, or canvas downloads do not bundle the stream implementation.

import{saveStream,StreamDownloader,typeDownloadStreamSource,typeSaveStreamOptions,}from'tinysaver/stream'awaitsaveStream(awaitfetch('/api/export'),'export.bin')constdownloader=newStreamDownloader()awaitdownloader.saveStream(source,'export.bin',options)

saveStream is not exported from tinysaver, and FileDownloader no longer has a saveStream method. Migrate root imports and class usage as follows:

// Beforeimport{FileDownloader,saveStream}from'tinysaver'// Afterimport{saveStream,StreamDownloader}from'tinysaver/stream'

⚙️ With Options

import{saveAs}from'tinysaver'saveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt',{autoBom: true,// Add UTF-8 BOM for text filesclickDelay: 100,// Delay before triggering downloadopenInNewTab: false,// Open in new tab instead of downloadingdisableClick: false,// Disable automatic click simulationonStart(){console.log('Download started')},onComplete(){console.log('Download completed')},onError(err){console.error('Download failed',err)},onProgress(loaded,total){console.log(`${loaded}/${total}`)// total is 0 when size is unknown},timeout: 10_000,// Abort automatically after 10ssignal: abortController.signal,// Manual cancellationfetchOptions: {credentials: 'include'},// For CORS probing requestpreferFileSystemAccess: true,// Use showSaveFilePicker for Blob/stream sourcesonPhaseChange(phase){console.log(phase)// probing/downloading/saving/completed/error/aborted},})

👏 Callbacks

All download methods support lifecycle callbacks:

saveText('content','file.txt',{onStart(){// Called when download process starts},onProgress(loaded,total){// Called during download progressconsole.log(`Downloaded ${loaded}/${total} bytes`)},onComplete(){// Called when download completes},onError(error){// Called when download failsconsole.error(error)},})

🌐 Browser Support

tinysaver targets the following browser versions and newer:

  • Chrome 87
  • Edge 88
  • Firefox 78
  • Safari 14

Internet Explorer is not supported.

📚 API

💾 saveAs(blob, filename?, options?)

Save any Blob or URL as a file. Compatible with FileSaver.js saveAs API.

saveAsAsync(blob, filename?, options?)

Promise-based version of saveAs, suitable for async workflows and explicit error handling.

Parameters:

  • blob - Blob object or URL string
  • filename - Name of the file to save (optional)
  • options - Download options (optional)

📝 saveText(text, filename, options?)

Save text content as a file.

Parameters:

  • text - Text content to save
  • filename - Name of the text file
  • options - Download options with optional mimeType property

📄 saveJSON(data, filename, options?)

Save JSON data as a file.

Parameters:

  • data - JavaScript object or value to save
  • filename - Name of the JSON file
  • options - Download options with optional space property for formatting

🎨 saveCanvas(canvas, filename, options?)

Save HTML canvas as an image file.

Parameters:

  • canvas - HTMLCanvasElement to save
  • filename - Name of the image file
  • options - Download options with optional type and quality properties

🌊 saveStream(source, filename, options?)

Save stream data (ReadableStream, Response, AsyncIterable) as a file. Import it from tinysaver/stream.

When preferFileSystemAccess is enabled and supported, chunks are written directly to the selected file. Other browsers materialize the stream as a browser-managed Blob before starting the download. Abort signals and timeouts cancel the active stream and propagate cancellation to its source.

Blob fallback buffering is limited to 256 MiB by default to protect the browser from unbounded memory growth. Set maxBufferBytes to a positive byte limit when another bound is appropriate for the application.

URL sources always use the browser download transport. File pickers require a transient user gesture and therefore cannot be opened after an asynchronous URL probe or transfer.

✅ Testing

The library includes comprehensive unit tests covering:

  • 📥 FileDownloader core functionality and error handling
  • 🏷️ BOM (Byte Order Mark) insertion for text files
  • 📝 Default filename handling
  • 💾 saveText, saveJSON, and saveCanvas implementations
  • ⚠️ Canvas conversion error handling
  • 🔔 Callback invocation during download lifecycle
  • ⏱️ Timeout/abort behavior and phase callbacks
  • 🌊 Stream-based save flow
  • 📂 File System Access API preferred path

Compatibility Matrix

  • Unit tests run in jsdom for deterministic behavior.
  • Browser tests run in Chromium, Firefox, and WebKit through Playwright.
  • Production builds target Chrome 87, Edge 88, Firefox 78, and Safari 14.
  • Downloads use the standard anchor download attribute and URL API.
  • IE-only and pre-modern browser fallbacks are intentionally excluded.

Run tests with:

pnpm test
pnpm test:browser

🧪 Playground

The Vue playground contains interactive examples for saving text, JSON, canvas artwork, and generated streams directly in the browser.

pnpm playground

Run pnpm playground:build to verify its production bundle.

🙏 Credits

📄 License

MIT License © 2025-PRESENT ntnyq

About

📦 Modern replacement of FileSaver.js.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - ntnyq/tinysaver: :package: Modern replacement of FileSaver.js. · GitHub
Skip to content

Repository files navigation

💾 tinysaver

CINPM VERSIONNPM DOWNLOADSLICENSE

Modern replacement of file-saver.js.

📦 Install

npm install tinysaver
yarn add tinysaver
pnpm add tinysaver

🚀 Usage

✨ Basic Usage

import{saveAs,saveAsAsync,saveText,saveJSON,saveCanvas}from'tinysaver'import{saveStream}from'tinysaver/stream'// Save a BlobsaveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt')// Save text contentsaveText('Hello World','greeting.txt')// Save JSON datasaveJSON({name: 'John',age: 30},'data.json',{space: 2})// Save canvas as imageconstcanvas=document.querySelector('canvas')saveCanvas(canvas,'image.png',{quality: 0.95})// Promise-based saveawaitsaveAsAsync(newBlob(['hello async']),'async.txt')// Save from stream/responseconstresponse=awaitfetch('/api/export')awaitsaveStream(response,'export.bin')

🌊 Stream Entry

Stream support is published from a separate entry so applications that only use Blob, URL, text, JSON, or canvas downloads do not bundle the stream implementation.

import{saveStream,StreamDownloader,typeDownloadStreamSource,typeSaveStreamOptions,}from'tinysaver/stream'awaitsaveStream(awaitfetch('/api/export'),'export.bin')constdownloader=newStreamDownloader()awaitdownloader.saveStream(source,'export.bin',options)

saveStream is not exported from tinysaver, and FileDownloader no longer has a saveStream method. Migrate root imports and class usage as follows:

// Beforeimport{FileDownloader,saveStream}from'tinysaver'// Afterimport{saveStream,StreamDownloader}from'tinysaver/stream'

⚙️ With Options

import{saveAs}from'tinysaver'saveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt',{autoBom: true,// Add UTF-8 BOM for text filesclickDelay: 100,// Delay before triggering downloadopenInNewTab: false,// Open in new tab instead of downloadingdisableClick: false,// Disable automatic click simulationonStart(){console.log('Download started')},onComplete(){console.log('Download completed')},onError(err){console.error('Download failed',err)},onProgress(loaded,total){console.log(`${loaded}/${total}`)// total is 0 when size is unknown},timeout: 10_000,// Abort automatically after 10ssignal: abortController.signal,// Manual cancellationfetchOptions: {credentials: 'include'},// For CORS probing requestpreferFileSystemAccess: true,// Use showSaveFilePicker for Blob/stream sourcesonPhaseChange(phase){console.log(phase)// probing/downloading/saving/completed/error/aborted},})

👏 Callbacks

All download methods support lifecycle callbacks:

saveText('content','file.txt',{onStart(){// Called when download process starts},onProgress(loaded,total){// Called during download progressconsole.log(`Downloaded ${loaded}/${total} bytes`)},onComplete(){// Called when download completes},onError(error){// Called when download failsconsole.error(error)},})

🌐 Browser Support

tinysaver targets the following browser versions and newer:

  • Chrome 87
  • Edge 88
  • Firefox 78
  • Safari 14

Internet Explorer is not supported.

📚 API

💾 saveAs(blob, filename?, options?)

Save any Blob or URL as a file. Compatible with FileSaver.js saveAs API.

saveAsAsync(blob, filename?, options?)

Promise-based version of saveAs, suitable for async workflows and explicit error handling.

Parameters:

  • blob - Blob object or URL string
  • filename - Name of the file to save (optional)
  • options - Download options (optional)

📝 saveText(text, filename, options?)

Save text content as a file.

Parameters:

  • text - Text content to save
  • filename - Name of the text file
  • options - Download options with optional mimeType property

📄 saveJSON(data, filename, options?)

Save JSON data as a file.

Parameters:

  • data - JavaScript object or value to save
  • filename - Name of the JSON file
  • options - Download options with optional space property for formatting

🎨 saveCanvas(canvas, filename, options?)

Save HTML canvas as an image file.

Parameters:

  • canvas - HTMLCanvasElement to save
  • filename - Name of the image file
  • options - Download options with optional type and quality properties

🌊 saveStream(source, filename, options?)

Save stream data (ReadableStream, Response, AsyncIterable) as a file. Import it from tinysaver/stream.

When preferFileSystemAccess is enabled and supported, chunks are written directly to the selected file. Other browsers materialize the stream as a browser-managed Blob before starting the download. Abort signals and timeouts cancel the active stream and propagate cancellation to its source.

Blob fallback buffering is limited to 256 MiB by default to protect the browser from unbounded memory growth. Set maxBufferBytes to a positive byte limit when another bound is appropriate for the application.

URL sources always use the browser download transport. File pickers require a transient user gesture and therefore cannot be opened after an asynchronous URL probe or transfer.

✅ Testing

The library includes comprehensive unit tests covering:

  • 📥 FileDownloader core functionality and error handling
  • 🏷️ BOM (Byte Order Mark) insertion for text files
  • 📝 Default filename handling
  • 💾 saveText, saveJSON, and saveCanvas implementations
  • ⚠️ Canvas conversion error handling
  • 🔔 Callback invocation during download lifecycle
  • ⏱️ Timeout/abort behavior and phase callbacks
  • 🌊 Stream-based save flow
  • 📂 File System Access API preferred path

Compatibility Matrix

  • Unit tests run in jsdom for deterministic behavior.
  • Browser tests run in Chromium, Firefox, and WebKit through Playwright.
  • Production builds target Chrome 87, Edge 88, Firefox 78, and Safari 14.
  • Downloads use the standard anchor download attribute and URL API.
  • IE-only and pre-modern browser fallbacks are intentionally excluded.

Run tests with:

pnpm test
pnpm test:browser

🧪 Playground

The Vue playground contains interactive examples for saving text, JSON, canvas artwork, and generated streams directly in the browser.

pnpm playground

Run pnpm playground:build to verify its production bundle.

🙏 Credits

📄 License

MIT License © 2025-PRESENT ntnyq

About

📦 Modern replacement of FileSaver.js.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - ntnyq/tinysaver: :package: Modern replacement of FileSaver.js. · GitHub
Skip to content

Repository files navigation

💾 tinysaver

CINPM VERSIONNPM DOWNLOADSLICENSE

Modern replacement of file-saver.js.

📦 Install

npm install tinysaver
yarn add tinysaver
pnpm add tinysaver

🚀 Usage

✨ Basic Usage

import{saveAs,saveAsAsync,saveText,saveJSON,saveCanvas}from'tinysaver'import{saveStream}from'tinysaver/stream'// Save a BlobsaveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt')// Save text contentsaveText('Hello World','greeting.txt')// Save JSON datasaveJSON({name: 'John',age: 30},'data.json',{space: 2})// Save canvas as imageconstcanvas=document.querySelector('canvas')saveCanvas(canvas,'image.png',{quality: 0.95})// Promise-based saveawaitsaveAsAsync(newBlob(['hello async']),'async.txt')// Save from stream/responseconstresponse=awaitfetch('/api/export')awaitsaveStream(response,'export.bin')

🌊 Stream Entry

Stream support is published from a separate entry so applications that only use Blob, URL, text, JSON, or canvas downloads do not bundle the stream implementation.

import{saveStream,StreamDownloader,typeDownloadStreamSource,typeSaveStreamOptions,}from'tinysaver/stream'awaitsaveStream(awaitfetch('/api/export'),'export.bin')constdownloader=newStreamDownloader()awaitdownloader.saveStream(source,'export.bin',options)

saveStream is not exported from tinysaver, and FileDownloader no longer has a saveStream method. Migrate root imports and class usage as follows:

// Beforeimport{FileDownloader,saveStream}from'tinysaver'// Afterimport{saveStream,StreamDownloader}from'tinysaver/stream'

⚙️ With Options

import{saveAs}from'tinysaver'saveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt',{autoBom: true,// Add UTF-8 BOM for text filesclickDelay: 100,// Delay before triggering downloadopenInNewTab: false,// Open in new tab instead of downloadingdisableClick: false,// Disable automatic click simulationonStart(){console.log('Download started')},onComplete(){console.log('Download completed')},onError(err){console.error('Download failed',err)},onProgress(loaded,total){console.log(`${loaded}/${total}`)// total is 0 when size is unknown},timeout: 10_000,// Abort automatically after 10ssignal: abortController.signal,// Manual cancellationfetchOptions: {credentials: 'include'},// For CORS probing requestpreferFileSystemAccess: true,// Use showSaveFilePicker for Blob/stream sourcesonPhaseChange(phase){console.log(phase)// probing/downloading/saving/completed/error/aborted},})

👏 Callbacks

All download methods support lifecycle callbacks:

saveText('content','file.txt',{onStart(){// Called when download process starts},onProgress(loaded,total){// Called during download progressconsole.log(`Downloaded ${loaded}/${total} bytes`)},onComplete(){// Called when download completes},onError(error){// Called when download failsconsole.error(error)},})

🌐 Browser Support

tinysaver targets the following browser versions and newer:

  • Chrome 87
  • Edge 88
  • Firefox 78
  • Safari 14

Internet Explorer is not supported.

📚 API

💾 saveAs(blob, filename?, options?)

Save any Blob or URL as a file. Compatible with FileSaver.js saveAs API.

saveAsAsync(blob, filename?, options?)

Promise-based version of saveAs, suitable for async workflows and explicit error handling.

Parameters:

  • blob - Blob object or URL string
  • filename - Name of the file to save (optional)
  • options - Download options (optional)

📝 saveText(text, filename, options?)

Save text content as a file.

Parameters:

  • text - Text content to save
  • filename - Name of the text file
  • options - Download options with optional mimeType property

📄 saveJSON(data, filename, options?)

Save JSON data as a file.

Parameters:

  • data - JavaScript object or value to save
  • filename - Name of the JSON file
  • options - Download options with optional space property for formatting

🎨 saveCanvas(canvas, filename, options?)

Save HTML canvas as an image file.

Parameters:

  • canvas - HTMLCanvasElement to save
  • filename - Name of the image file
  • options - Download options with optional type and quality properties

🌊 saveStream(source, filename, options?)

Save stream data (ReadableStream, Response, AsyncIterable) as a file. Import it from tinysaver/stream.

When preferFileSystemAccess is enabled and supported, chunks are written directly to the selected file. Other browsers materialize the stream as a browser-managed Blob before starting the download. Abort signals and timeouts cancel the active stream and propagate cancellation to its source.

Blob fallback buffering is limited to 256 MiB by default to protect the browser from unbounded memory growth. Set maxBufferBytes to a positive byte limit when another bound is appropriate for the application.

URL sources always use the browser download transport. File pickers require a transient user gesture and therefore cannot be opened after an asynchronous URL probe or transfer.

✅ Testing

The library includes comprehensive unit tests covering:

  • 📥 FileDownloader core functionality and error handling
  • 🏷️ BOM (Byte Order Mark) insertion for text files
  • 📝 Default filename handling
  • 💾 saveText, saveJSON, and saveCanvas implementations
  • ⚠️ Canvas conversion error handling
  • 🔔 Callback invocation during download lifecycle
  • ⏱️ Timeout/abort behavior and phase callbacks
  • 🌊 Stream-based save flow
  • 📂 File System Access API preferred path

Compatibility Matrix

  • Unit tests run in jsdom for deterministic behavior.
  • Browser tests run in Chromium, Firefox, and WebKit through Playwright.
  • Production builds target Chrome 87, Edge 88, Firefox 78, and Safari 14.
  • Downloads use the standard anchor download attribute and URL API.
  • IE-only and pre-modern browser fallbacks are intentionally excluded.

Run tests with:

pnpm test
pnpm test:browser

🧪 Playground

The Vue playground contains interactive examples for saving text, JSON, canvas artwork, and generated streams directly in the browser.

pnpm playground

Run pnpm playground:build to verify its production bundle.

🙏 Credits

📄 License

MIT License © 2025-PRESENT ntnyq

About

📦 Modern replacement of FileSaver.js.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - ntnyq/tinysaver: :package: Modern replacement of FileSaver.js. · GitHub
Skip to content

Repository files navigation

💾 tinysaver

CINPM VERSIONNPM DOWNLOADSLICENSE

Modern replacement of file-saver.js.

📦 Install

npm install tinysaver
yarn add tinysaver
pnpm add tinysaver

🚀 Usage

✨ Basic Usage

import{saveAs,saveAsAsync,saveText,saveJSON,saveCanvas}from'tinysaver'import{saveStream}from'tinysaver/stream'// Save a BlobsaveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt')// Save text contentsaveText('Hello World','greeting.txt')// Save JSON datasaveJSON({name: 'John',age: 30},'data.json',{space: 2})// Save canvas as imageconstcanvas=document.querySelector('canvas')saveCanvas(canvas,'image.png',{quality: 0.95})// Promise-based saveawaitsaveAsAsync(newBlob(['hello async']),'async.txt')// Save from stream/responseconstresponse=awaitfetch('/api/export')awaitsaveStream(response,'export.bin')

🌊 Stream Entry

Stream support is published from a separate entry so applications that only use Blob, URL, text, JSON, or canvas downloads do not bundle the stream implementation.

import{saveStream,StreamDownloader,typeDownloadStreamSource,typeSaveStreamOptions,}from'tinysaver/stream'awaitsaveStream(awaitfetch('/api/export'),'export.bin')constdownloader=newStreamDownloader()awaitdownloader.saveStream(source,'export.bin',options)

saveStream is not exported from tinysaver, and FileDownloader no longer has a saveStream method. Migrate root imports and class usage as follows:

// Beforeimport{FileDownloader,saveStream}from'tinysaver'// Afterimport{saveStream,StreamDownloader}from'tinysaver/stream'

⚙️ With Options

import{saveAs}from'tinysaver'saveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt',{autoBom: true,// Add UTF-8 BOM for text filesclickDelay: 100,// Delay before triggering downloadopenInNewTab: false,// Open in new tab instead of downloadingdisableClick: false,// Disable automatic click simulationonStart(){console.log('Download started')},onComplete(){console.log('Download completed')},onError(err){console.error('Download failed',err)},onProgress(loaded,total){console.log(`${loaded}/${total}`)// total is 0 when size is unknown},timeout: 10_000,// Abort automatically after 10ssignal: abortController.signal,// Manual cancellationfetchOptions: {credentials: 'include'},// For CORS probing requestpreferFileSystemAccess: true,// Use showSaveFilePicker for Blob/stream sourcesonPhaseChange(phase){console.log(phase)// probing/downloading/saving/completed/error/aborted},})

👏 Callbacks

All download methods support lifecycle callbacks:

saveText('content','file.txt',{onStart(){// Called when download process starts},onProgress(loaded,total){// Called during download progressconsole.log(`Downloaded ${loaded}/${total} bytes`)},onComplete(){// Called when download completes},onError(error){// Called when download failsconsole.error(error)},})

🌐 Browser Support

tinysaver targets the following browser versions and newer:

  • Chrome 87
  • Edge 88
  • Firefox 78
  • Safari 14

Internet Explorer is not supported.

📚 API

💾 saveAs(blob, filename?, options?)

Save any Blob or URL as a file. Compatible with FileSaver.js saveAs API.

saveAsAsync(blob, filename?, options?)

Promise-based version of saveAs, suitable for async workflows and explicit error handling.

Parameters:

  • blob - Blob object or URL string
  • filename - Name of the file to save (optional)
  • options - Download options (optional)

📝 saveText(text, filename, options?)

Save text content as a file.

Parameters:

  • text - Text content to save
  • filename - Name of the text file
  • options - Download options with optional mimeType property

📄 saveJSON(data, filename, options?)

Save JSON data as a file.

Parameters:

  • data - JavaScript object or value to save
  • filename - Name of the JSON file
  • options - Download options with optional space property for formatting

🎨 saveCanvas(canvas, filename, options?)

Save HTML canvas as an image file.

Parameters:

  • canvas - HTMLCanvasElement to save
  • filename - Name of the image file
  • options - Download options with optional type and quality properties

🌊 saveStream(source, filename, options?)

Save stream data (ReadableStream, Response, AsyncIterable) as a file. Import it from tinysaver/stream.

When preferFileSystemAccess is enabled and supported, chunks are written directly to the selected file. Other browsers materialize the stream as a browser-managed Blob before starting the download. Abort signals and timeouts cancel the active stream and propagate cancellation to its source.

Blob fallback buffering is limited to 256 MiB by default to protect the browser from unbounded memory growth. Set maxBufferBytes to a positive byte limit when another bound is appropriate for the application.

URL sources always use the browser download transport. File pickers require a transient user gesture and therefore cannot be opened after an asynchronous URL probe or transfer.

✅ Testing

The library includes comprehensive unit tests covering:

  • 📥 FileDownloader core functionality and error handling
  • 🏷️ BOM (Byte Order Mark) insertion for text files
  • 📝 Default filename handling
  • 💾 saveText, saveJSON, and saveCanvas implementations
  • ⚠️ Canvas conversion error handling
  • 🔔 Callback invocation during download lifecycle
  • ⏱️ Timeout/abort behavior and phase callbacks
  • 🌊 Stream-based save flow
  • 📂 File System Access API preferred path

Compatibility Matrix

  • Unit tests run in jsdom for deterministic behavior.
  • Browser tests run in Chromium, Firefox, and WebKit through Playwright.
  • Production builds target Chrome 87, Edge 88, Firefox 78, and Safari 14.
  • Downloads use the standard anchor download attribute and URL API.
  • IE-only and pre-modern browser fallbacks are intentionally excluded.

Run tests with:

pnpm test
pnpm test:browser

🧪 Playground

The Vue playground contains interactive examples for saving text, JSON, canvas artwork, and generated streams directly in the browser.

pnpm playground

Run pnpm playground:build to verify its production bundle.

🙏 Credits

📄 License

MIT License © 2025-PRESENT ntnyq

About

📦 Modern replacement of FileSaver.js.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - ntnyq/tinysaver: :package: Modern replacement of FileSaver.js. · GitHub
Skip to content

Repository files navigation

💾 tinysaver

CINPM VERSIONNPM DOWNLOADSLICENSE

Modern replacement of file-saver.js.

📦 Install

npm install tinysaver
yarn add tinysaver
pnpm add tinysaver

🚀 Usage

✨ Basic Usage

import{saveAs,saveAsAsync,saveText,saveJSON,saveCanvas}from'tinysaver'import{saveStream}from'tinysaver/stream'// Save a BlobsaveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt')// Save text contentsaveText('Hello World','greeting.txt')// Save JSON datasaveJSON({name: 'John',age: 30},'data.json',{space: 2})// Save canvas as imageconstcanvas=document.querySelector('canvas')saveCanvas(canvas,'image.png',{quality: 0.95})// Promise-based saveawaitsaveAsAsync(newBlob(['hello async']),'async.txt')// Save from stream/responseconstresponse=awaitfetch('/api/export')awaitsaveStream(response,'export.bin')

🌊 Stream Entry

Stream support is published from a separate entry so applications that only use Blob, URL, text, JSON, or canvas downloads do not bundle the stream implementation.

import{saveStream,StreamDownloader,typeDownloadStreamSource,typeSaveStreamOptions,}from'tinysaver/stream'awaitsaveStream(awaitfetch('/api/export'),'export.bin')constdownloader=newStreamDownloader()awaitdownloader.saveStream(source,'export.bin',options)

saveStream is not exported from tinysaver, and FileDownloader no longer has a saveStream method. Migrate root imports and class usage as follows:

// Beforeimport{FileDownloader,saveStream}from'tinysaver'// Afterimport{saveStream,StreamDownloader}from'tinysaver/stream'

⚙️ With Options

import{saveAs}from'tinysaver'saveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt',{autoBom: true,// Add UTF-8 BOM for text filesclickDelay: 100,// Delay before triggering downloadopenInNewTab: false,// Open in new tab instead of downloadingdisableClick: false,// Disable automatic click simulationonStart(){console.log('Download started')},onComplete(){console.log('Download completed')},onError(err){console.error('Download failed',err)},onProgress(loaded,total){console.log(`${loaded}/${total}`)// total is 0 when size is unknown},timeout: 10_000,// Abort automatically after 10ssignal: abortController.signal,// Manual cancellationfetchOptions: {credentials: 'include'},// For CORS probing requestpreferFileSystemAccess: true,// Use showSaveFilePicker for Blob/stream sourcesonPhaseChange(phase){console.log(phase)// probing/downloading/saving/completed/error/aborted},})

👏 Callbacks

All download methods support lifecycle callbacks:

saveText('content','file.txt',{onStart(){// Called when download process starts},onProgress(loaded,total){// Called during download progressconsole.log(`Downloaded ${loaded}/${total} bytes`)},onComplete(){// Called when download completes},onError(error){// Called when download failsconsole.error(error)},})

🌐 Browser Support

tinysaver targets the following browser versions and newer:

  • Chrome 87
  • Edge 88
  • Firefox 78
  • Safari 14

Internet Explorer is not supported.

📚 API

💾 saveAs(blob, filename?, options?)

Save any Blob or URL as a file. Compatible with FileSaver.js saveAs API.

saveAsAsync(blob, filename?, options?)

Promise-based version of saveAs, suitable for async workflows and explicit error handling.

Parameters:

  • blob - Blob object or URL string
  • filename - Name of the file to save (optional)
  • options - Download options (optional)

📝 saveText(text, filename, options?)

Save text content as a file.

Parameters:

  • text - Text content to save
  • filename - Name of the text file
  • options - Download options with optional mimeType property

📄 saveJSON(data, filename, options?)

Save JSON data as a file.

Parameters:

  • data - JavaScript object or value to save
  • filename - Name of the JSON file
  • options - Download options with optional space property for formatting

🎨 saveCanvas(canvas, filename, options?)

Save HTML canvas as an image file.

Parameters:

  • canvas - HTMLCanvasElement to save
  • filename - Name of the image file
  • options - Download options with optional type and quality properties

🌊 saveStream(source, filename, options?)

Save stream data (ReadableStream, Response, AsyncIterable) as a file. Import it from tinysaver/stream.

When preferFileSystemAccess is enabled and supported, chunks are written directly to the selected file. Other browsers materialize the stream as a browser-managed Blob before starting the download. Abort signals and timeouts cancel the active stream and propagate cancellation to its source.

Blob fallback buffering is limited to 256 MiB by default to protect the browser from unbounded memory growth. Set maxBufferBytes to a positive byte limit when another bound is appropriate for the application.

URL sources always use the browser download transport. File pickers require a transient user gesture and therefore cannot be opened after an asynchronous URL probe or transfer.

✅ Testing

The library includes comprehensive unit tests covering:

  • 📥 FileDownloader core functionality and error handling
  • 🏷️ BOM (Byte Order Mark) insertion for text files
  • 📝 Default filename handling
  • 💾 saveText, saveJSON, and saveCanvas implementations
  • ⚠️ Canvas conversion error handling
  • 🔔 Callback invocation during download lifecycle
  • ⏱️ Timeout/abort behavior and phase callbacks
  • 🌊 Stream-based save flow
  • 📂 File System Access API preferred path

Compatibility Matrix

  • Unit tests run in jsdom for deterministic behavior.
  • Browser tests run in Chromium, Firefox, and WebKit through Playwright.
  • Production builds target Chrome 87, Edge 88, Firefox 78, and Safari 14.
  • Downloads use the standard anchor download attribute and URL API.
  • IE-only and pre-modern browser fallbacks are intentionally excluded.

Run tests with:

pnpm test
pnpm test:browser

🧪 Playground

The Vue playground contains interactive examples for saving text, JSON, canvas artwork, and generated streams directly in the browser.

pnpm playground

Run pnpm playground:build to verify its production bundle.

🙏 Credits

📄 License

MIT License © 2025-PRESENT ntnyq

About

📦 Modern replacement of FileSaver.js.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - ntnyq/tinysaver: :package: Modern replacement of FileSaver.js. · GitHub
Skip to content

Repository files navigation

💾 tinysaver

CINPM VERSIONNPM DOWNLOADSLICENSE

Modern replacement of file-saver.js.

📦 Install

npm install tinysaver
yarn add tinysaver
pnpm add tinysaver

🚀 Usage

✨ Basic Usage

import{saveAs,saveAsAsync,saveText,saveJSON,saveCanvas}from'tinysaver'import{saveStream}from'tinysaver/stream'// Save a BlobsaveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt')// Save text contentsaveText('Hello World','greeting.txt')// Save JSON datasaveJSON({name: 'John',age: 30},'data.json',{space: 2})// Save canvas as imageconstcanvas=document.querySelector('canvas')saveCanvas(canvas,'image.png',{quality: 0.95})// Promise-based saveawaitsaveAsAsync(newBlob(['hello async']),'async.txt')// Save from stream/responseconstresponse=awaitfetch('/api/export')awaitsaveStream(response,'export.bin')

🌊 Stream Entry

Stream support is published from a separate entry so applications that only use Blob, URL, text, JSON, or canvas downloads do not bundle the stream implementation.

import{saveStream,StreamDownloader,typeDownloadStreamSource,typeSaveStreamOptions,}from'tinysaver/stream'awaitsaveStream(awaitfetch('/api/export'),'export.bin')constdownloader=newStreamDownloader()awaitdownloader.saveStream(source,'export.bin',options)

saveStream is not exported from tinysaver, and FileDownloader no longer has a saveStream method. Migrate root imports and class usage as follows:

// Beforeimport{FileDownloader,saveStream}from'tinysaver'// Afterimport{saveStream,StreamDownloader}from'tinysaver/stream'

⚙️ With Options

import{saveAs}from'tinysaver'saveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt',{autoBom: true,// Add UTF-8 BOM for text filesclickDelay: 100,// Delay before triggering downloadopenInNewTab: false,// Open in new tab instead of downloadingdisableClick: false,// Disable automatic click simulationonStart(){console.log('Download started')},onComplete(){console.log('Download completed')},onError(err){console.error('Download failed',err)},onProgress(loaded,total){console.log(`${loaded}/${total}`)// total is 0 when size is unknown},timeout: 10_000,// Abort automatically after 10ssignal: abortController.signal,// Manual cancellationfetchOptions: {credentials: 'include'},// For CORS probing requestpreferFileSystemAccess: true,// Use showSaveFilePicker for Blob/stream sourcesonPhaseChange(phase){console.log(phase)// probing/downloading/saving/completed/error/aborted},})

👏 Callbacks

All download methods support lifecycle callbacks:

saveText('content','file.txt',{onStart(){// Called when download process starts},onProgress(loaded,total){// Called during download progressconsole.log(`Downloaded ${loaded}/${total} bytes`)},onComplete(){// Called when download completes},onError(error){// Called when download failsconsole.error(error)},})

🌐 Browser Support

tinysaver targets the following browser versions and newer:

  • Chrome 87
  • Edge 88
  • Firefox 78
  • Safari 14

Internet Explorer is not supported.

📚 API

💾 saveAs(blob, filename?, options?)

Save any Blob or URL as a file. Compatible with FileSaver.js saveAs API.

saveAsAsync(blob, filename?, options?)

Promise-based version of saveAs, suitable for async workflows and explicit error handling.

Parameters:

  • blob - Blob object or URL string
  • filename - Name of the file to save (optional)
  • options - Download options (optional)

📝 saveText(text, filename, options?)

Save text content as a file.

Parameters:

  • text - Text content to save
  • filename - Name of the text file
  • options - Download options with optional mimeType property

📄 saveJSON(data, filename, options?)

Save JSON data as a file.

Parameters:

  • data - JavaScript object or value to save
  • filename - Name of the JSON file
  • options - Download options with optional space property for formatting

🎨 saveCanvas(canvas, filename, options?)

Save HTML canvas as an image file.

Parameters:

  • canvas - HTMLCanvasElement to save
  • filename - Name of the image file
  • options - Download options with optional type and quality properties

🌊 saveStream(source, filename, options?)

Save stream data (ReadableStream, Response, AsyncIterable) as a file. Import it from tinysaver/stream.

When preferFileSystemAccess is enabled and supported, chunks are written directly to the selected file. Other browsers materialize the stream as a browser-managed Blob before starting the download. Abort signals and timeouts cancel the active stream and propagate cancellation to its source.

Blob fallback buffering is limited to 256 MiB by default to protect the browser from unbounded memory growth. Set maxBufferBytes to a positive byte limit when another bound is appropriate for the application.

URL sources always use the browser download transport. File pickers require a transient user gesture and therefore cannot be opened after an asynchronous URL probe or transfer.

✅ Testing

The library includes comprehensive unit tests covering:

  • 📥 FileDownloader core functionality and error handling
  • 🏷️ BOM (Byte Order Mark) insertion for text files
  • 📝 Default filename handling
  • 💾 saveText, saveJSON, and saveCanvas implementations
  • ⚠️ Canvas conversion error handling
  • 🔔 Callback invocation during download lifecycle
  • ⏱️ Timeout/abort behavior and phase callbacks
  • 🌊 Stream-based save flow
  • 📂 File System Access API preferred path

Compatibility Matrix

  • Unit tests run in jsdom for deterministic behavior.
  • Browser tests run in Chromium, Firefox, and WebKit through Playwright.
  • Production builds target Chrome 87, Edge 88, Firefox 78, and Safari 14.
  • Downloads use the standard anchor download attribute and URL API.
  • IE-only and pre-modern browser fallbacks are intentionally excluded.

Run tests with:

pnpm test
pnpm test:browser

🧪 Playground

The Vue playground contains interactive examples for saving text, JSON, canvas artwork, and generated streams directly in the browser.

pnpm playground

Run pnpm playground:build to verify its production bundle.

🙏 Credits

📄 License

MIT License © 2025-PRESENT ntnyq

About

📦 Modern replacement of FileSaver.js.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - ntnyq/tinysaver: :package: Modern replacement of FileSaver.js. · GitHub
Skip to content

Repository files navigation

💾 tinysaver

CINPM VERSIONNPM DOWNLOADSLICENSE

Modern replacement of file-saver.js.

📦 Install

npm install tinysaver
yarn add tinysaver
pnpm add tinysaver

🚀 Usage

✨ Basic Usage

import{saveAs,saveAsAsync,saveText,saveJSON,saveCanvas}from'tinysaver'import{saveStream}from'tinysaver/stream'// Save a BlobsaveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt')// Save text contentsaveText('Hello World','greeting.txt')// Save JSON datasaveJSON({name: 'John',age: 30},'data.json',{space: 2})// Save canvas as imageconstcanvas=document.querySelector('canvas')saveCanvas(canvas,'image.png',{quality: 0.95})// Promise-based saveawaitsaveAsAsync(newBlob(['hello async']),'async.txt')// Save from stream/responseconstresponse=awaitfetch('/api/export')awaitsaveStream(response,'export.bin')

🌊 Stream Entry

Stream support is published from a separate entry so applications that only use Blob, URL, text, JSON, or canvas downloads do not bundle the stream implementation.

import{saveStream,StreamDownloader,typeDownloadStreamSource,typeSaveStreamOptions,}from'tinysaver/stream'awaitsaveStream(awaitfetch('/api/export'),'export.bin')constdownloader=newStreamDownloader()awaitdownloader.saveStream(source,'export.bin',options)

saveStream is not exported from tinysaver, and FileDownloader no longer has a saveStream method. Migrate root imports and class usage as follows:

// Beforeimport{FileDownloader,saveStream}from'tinysaver'// Afterimport{saveStream,StreamDownloader}from'tinysaver/stream'

⚙️ With Options

import{saveAs}from'tinysaver'saveAs(newBlob(['hello world'],{type: 'text/plain'}),'hello-world.txt',{autoBom: true,// Add UTF-8 BOM for text filesclickDelay: 100,// Delay before triggering downloadopenInNewTab: false,// Open in new tab instead of downloadingdisableClick: false,// Disable automatic click simulationonStart(){console.log('Download started')},onComplete(){console.log('Download completed')},onError(err){console.error('Download failed',err)},onProgress(loaded,total){console.log(`${loaded}/${total}`)// total is 0 when size is unknown},timeout: 10_000,// Abort automatically after 10ssignal: abortController.signal,// Manual cancellationfetchOptions: {credentials: 'include'},// For CORS probing requestpreferFileSystemAccess: true,// Use showSaveFilePicker for Blob/stream sourcesonPhaseChange(phase){console.log(phase)// probing/downloading/saving/completed/error/aborted},})

👏 Callbacks

All download methods support lifecycle callbacks:

saveText('content','file.txt',{onStart(){// Called when download process starts},onProgress(loaded,total){// Called during download progressconsole.log(`Downloaded ${loaded}/${total} bytes`)},onComplete(){// Called when download completes},onError(error){// Called when download failsconsole.error(error)},})

🌐 Browser Support

tinysaver targets the following browser versions and newer:

  • Chrome 87
  • Edge 88
  • Firefox 78
  • Safari 14

Internet Explorer is not supported.

📚 API

💾 saveAs(blob, filename?, options?)

Save any Blob or URL as a file. Compatible with FileSaver.js saveAs API.

saveAsAsync(blob, filename?, options?)

Promise-based version of saveAs, suitable for async workflows and explicit error handling.

Parameters:

  • blob - Blob object or URL string
  • filename - Name of the file to save (optional)
  • options - Download options (optional)

📝 saveText(text, filename, options?)

Save text content as a file.

Parameters:

  • text - Text content to save
  • filename - Name of the text file
  • options - Download options with optional mimeType property

📄 saveJSON(data, filename, options?)

Save JSON data as a file.

Parameters:

  • data - JavaScript object or value to save
  • filename - Name of the JSON file
  • options - Download options with optional space property for formatting

🎨 saveCanvas(canvas, filename, options?)

Save HTML canvas as an image file.

Parameters:

  • canvas - HTMLCanvasElement to save
  • filename - Name of the image file
  • options - Download options with optional type and quality properties

🌊 saveStream(source, filename, options?)

Save stream data (ReadableStream, Response, AsyncIterable) as a file. Import it from tinysaver/stream.

When preferFileSystemAccess is enabled and supported, chunks are written directly to the selected file. Other browsers materialize the stream as a browser-managed Blob before starting the download. Abort signals and timeouts cancel the active stream and propagate cancellation to its source.

Blob fallback buffering is limited to 256 MiB by default to protect the browser from unbounded memory growth. Set maxBufferBytes to a positive byte limit when another bound is appropriate for the application.

URL sources always use the browser download transport. File pickers require a transient user gesture and therefore cannot be opened after an asynchronous URL probe or transfer.

✅ Testing

The library includes comprehensive unit tests covering:

  • 📥 FileDownloader core functionality and error handling
  • 🏷️ BOM (Byte Order Mark) insertion for text files
  • 📝 Default filename handling
  • 💾 saveText, saveJSON, and saveCanvas implementations
  • ⚠️ Canvas conversion error handling
  • 🔔 Callback invocation during download lifecycle
  • ⏱️ Timeout/abort behavior and phase callbacks
  • 🌊 Stream-based save flow
  • 📂 File System Access API preferred path

Compatibility Matrix

  • Unit tests run in jsdom for deterministic behavior.
  • Browser tests run in Chromium, Firefox, and WebKit through Playwright.
  • Production builds target Chrome 87, Edge 88, Firefox 78, and Safari 14.
  • Downloads use the standard anchor download attribute and URL API.
  • IE-only and pre-modern browser fallbacks are intentionally excluded.

Run tests with:

pnpm test
pnpm test:browser

🧪 Playground

The Vue playground contains interactive examples for saving text, JSON, canvas artwork, and generated streams directly in the browser.

pnpm playground

Run pnpm playground:build to verify its production bundle.

🙏 Credits

📄 License

MIT License © 2025-PRESENT ntnyq

About

📦 Modern replacement of FileSaver.js.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages