Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ longer be used.
<!-- YAML
added: v10.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64611
description: The `data` argument supports {Blob}.
- version:
- v21.1.0
- v20.10.0
Expand All @@ -196,7 +199,7 @@ changes:
strings anymore.
-->

* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable}
* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Blob}
* `options` {Object|string}
* `encoding` {string|null} **Default:** `'utf8'`
* `signal` {AbortSignal|undefined} allows aborting an in-progress writeFile. **Default:** `undefined`
Expand Down Expand Up @@ -996,6 +999,9 @@ the end of the file.
<!-- YAML
added: v10.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64611
description: The `data` argument supports {Blob}.
- version:
- v15.14.0
- v14.18.0
Expand All @@ -1007,15 +1013,16 @@ changes:
strings anymore.
-->

* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable}
* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Blob}
* `options` {Object|string}
* `encoding` {string|null} The expected character encoding when `data` is a
string. **Default:** `'utf8'`
* `signal` {AbortSignal|undefined} allows aborting an in-progress writeFile. **Default:** `undefined`
* Returns: {Promise}

Asynchronously writes data to a file, replacing the file if it already exists.
`data` can be a string, a buffer, an {AsyncIterable}, or an {Iterable} object.
`data` can be a string, a buffer, a {Blob}, an {AsyncIterable}, or an {Iterable}
object.
The promise is fulfilled with no arguments upon success.

If `options` is a string, then it specifies the `encoding`.
Expand Down Expand Up @@ -1233,6 +1240,10 @@ the error raised if the file is not accessible.
<!-- YAML
added: v10.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64611
description: The `data` argument now supports typed arrays, `DataView`,
`AsyncIterable`, `Iterable`, `Stream`, and {Blob}.
- version:
- v21.1.0
- v20.10.0
Expand All @@ -1246,7 +1257,7 @@ changes:
-->

* `path` {string|Buffer|URL|FileHandle} filename or {FileHandle}
* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable}
* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Blob}
* `options` {Object|string}
* `encoding` {string|null} **Default:** `'utf8'`
* `mode` {integer} **Default:** `0o666`
Expand All @@ -1256,7 +1267,8 @@ changes:
* Returns: {Promise} Fulfills with `undefined` upon success.

Asynchronously append data to a file, creating the file if it does not yet
`data` can be a string, a buffer, an {AsyncIterable}, or an {Iterable} object.
exist. `data` can be a string, a buffer, a {Blob}, an {AsyncIterable}, or an
{Iterable} object.

If `options` is a string, then it specifies the `encoding`.

Expand Down Expand Up @@ -2247,6 +2259,9 @@ All the [caveats][] for `fs.watch()` also apply to `fsPromises.watch()`.
<!-- YAML
added: v10.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64611
description: The `data` argument supports {Blob}.
- version:
- v21.0.0
- v20.10.0
Expand All @@ -2270,7 +2285,7 @@ changes:
-->

* `file` {string|Buffer|URL|FileHandle} filename or `FileHandle`
* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable}
* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Blob}
* `options` {Object|string}
* `encoding` {string|null} **Default:** `'utf8'`
* `mode` {integer} **Default:** `0o666`
Expand All @@ -2282,9 +2297,10 @@ changes:
* Returns: {Promise} Fulfills with `undefined` upon success.

Asynchronously writes data to a file, replacing the file if it already exists.
`data` can be a string, a buffer, an {AsyncIterable}, or an {Iterable} object.
`data` can be a string, a buffer, a {Blob}, an {AsyncIterable}, or an {Iterable}
object.

The `encoding` option is ignored if `data` is a buffer.
The `encoding` option is ignored if `data` is a buffer or a {Blob}.

If `options` is a string, then it specifies the encoding.

Expand Down
7 changes: 6 additions & 1 deletion lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const {
},
} = require('internal/errors');
const { isArrayBufferView } = require('internal/util/types');
const { isBlob } = require('internal/blob');

const {
constants: {
Expand Down Expand Up @@ -2090,11 +2091,15 @@ async function writeFile(path, data, options) {

const flag = options.flag || 'w';

if (!isArrayBufferView(data) && !isCustomIterable(data)) {
if (!isArrayBufferView(data) && !isCustomIterable(data) && !isBlob(data)) {
validateStringAfterArrayBufferView(data, 'data');
data = Buffer.from(data, options.encoding || 'utf8');
}

if (isBlob(data)) {
data = data.stream();
}

validateAbortSignal(options.signal);
if (path instanceof FileHandle)
return writeFileHandle(path, data, options.signal, options.encoding);
Expand Down
115 changes: 115 additions & 0 deletions test/parallel/test-fs-writefile-blob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
'use strict';

const common = require('../common');
const fs = require('fs');
const fsPromises = fs.promises;
const path = require('path');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const { Blob } = require('buffer');

tmpdir.refresh();

const dest = path.resolve(tmpdir.path, 'tmp.txt');
const otherDest = path.resolve(tmpdir.path, 'tmp-2.txt');

const text = 'Hello, Node.js Core!';
const textBlob = new Blob([text]);

// A binary blob whose content is not valid UTF-8, to ensure binary data is
// preserved verbatim and not corrupted by any encoding handling.
const binaryInput = Buffer.from(
Array.from({ length: 1024 }, (_, i) => i % 256));
const binaryBlob = new Blob([binaryInput]);

// A blob larger than the internal writeFile chunk size so that the chunked
// write loop inside writeFileHandle() is exercised.
const largeString = 'dogs running'.repeat(64 * 1024);
const largeBlob = new Blob([largeString]);

// An empty blob.
const emptyBlob = new Blob([]);

async function doWriteFileBlob() {
await fsPromises.writeFile(dest, textBlob);
const data = fs.readFileSync(dest, 'utf8');
assert.strictEqual(data, text);
}

async function doWriteFileEmptyBlob() {
await fsPromises.writeFile(dest, emptyBlob);
const data = fs.readFileSync(dest);
assert.strictEqual(data.length, 0);
}

async function doWriteFileBinaryBlob() {
await fsPromises.writeFile(dest, binaryBlob);
const data = fs.readFileSync(dest);
assert.deepStrictEqual(data, binaryInput);
}

async function doWriteFileLargeBlob() {
await fsPromises.writeFile(dest, largeBlob);
const data = fs.readFileSync(dest, 'utf8');
assert.strictEqual(data, largeString);
}

async function doAppendFileBlob() {
await fsPromises.writeFile(dest, textBlob);
await fsPromises.appendFile(dest, new Blob([' appended']));
const data = fs.readFileSync(dest, 'utf8');
assert.strictEqual(data, `${text} appended`);
}

async function doFileHandleWriteFileBlob() {
const handle = await fsPromises.open(dest, 'w+');
try {
await handle.writeFile(textBlob);
const data = fs.readFileSync(dest, 'utf8');
assert.strictEqual(data, text);
} finally {
await handle.close();
}
}

async function doFileHandleAppendFileBlob() {
const handle = await fsPromises.open(dest, 'w+');
try {
await handle.writeFile(new Blob(['hello']));
await handle.appendFile(new Blob([' world']));
const data = fs.readFileSync(dest, 'utf8');
assert.strictEqual(data, 'hello world');
} finally {
await handle.close();
}
}

// Binary data must be preserved even when an encoding option is provided,
// because Blob chunks are already typed arrays and the encoding is ignored.
async function doWriteFileBinaryBlobWithEncoding() {
await fsPromises.writeFile(dest, binaryBlob, 'utf8');
const data = fs.readFileSync(dest);
assert.deepStrictEqual(data, binaryInput);
}

async function doWriteBlobWithCancel() {
const controller = new AbortController();
const { signal } = controller;
process.nextTick(() => controller.abort());
await assert.rejects(
fsPromises.writeFile(otherDest, textBlob, { signal }),
{ name: 'AbortError' }
);
}

(async () => {
await doWriteFileBlob();
await doWriteFileEmptyBlob();
await doWriteFileBinaryBlob();
await doWriteFileLargeBlob();
await doAppendFileBlob();
await doFileHandleWriteFileBlob();
await doFileHandleAppendFileBlob();
await doWriteFileBinaryBlobWithEncoding();
await doWriteBlobWithCancel();
})().then(common.mustCall());
Loading