2023-02-14 11:38:45 -05:00
|
|
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
refactor(core): include_js_files! 'dir' option doesn't change specifiers (#18019)
This commit changes "include_js_files!" macro from "deno_core"
in a way that "dir" option doesn't cause specifiers to be rewritten
to include it.
Example:
```
include_js_files! {
dir "js",
"hello.js",
}
```
The above definition required embedders to use:
`import ... from "internal:<ext_name>/js/hello.js"`.
But with this change, the "js" directory in which the files are stored
is an implementation detail, which for embedders results in:
`import ... from "internal:<ext_name>/hello.js"`.
The directory the files are stored in, is an implementation detail and
in some cases might result in a significant size difference for the
snapshot. As an example, in "deno_node" extension, we store the
source code in "polyfills" directory; which resulted in each specifier
to look like "internal:deno_node/polyfills/<module_name>", but with
this change it's "internal:deno_node/<module_name>".
Given that "deno_node" has over 100 files, many of them having
several import specifiers to the same extension, this change removes
10 characters from each import specifier.
2023-03-04 21:31:38 -05:00
|
|
|
import { Encodings } from "internal:deno_node/_utils.ts";
|
|
|
|
import { fromFileUrl } from "internal:deno_node/path.ts";
|
|
|
|
import { Buffer } from "internal:deno_node/buffer.ts";
|
2023-02-14 11:38:45 -05:00
|
|
|
import {
|
|
|
|
CallbackWithError,
|
|
|
|
checkEncoding,
|
|
|
|
getEncoding,
|
|
|
|
getOpenOptions,
|
|
|
|
isFileOptions,
|
|
|
|
WriteFileOptions,
|
refactor(core): include_js_files! 'dir' option doesn't change specifiers (#18019)
This commit changes "include_js_files!" macro from "deno_core"
in a way that "dir" option doesn't cause specifiers to be rewritten
to include it.
Example:
```
include_js_files! {
dir "js",
"hello.js",
}
```
The above definition required embedders to use:
`import ... from "internal:<ext_name>/js/hello.js"`.
But with this change, the "js" directory in which the files are stored
is an implementation detail, which for embedders results in:
`import ... from "internal:<ext_name>/hello.js"`.
The directory the files are stored in, is an implementation detail and
in some cases might result in a significant size difference for the
snapshot. As an example, in "deno_node" extension, we store the
source code in "polyfills" directory; which resulted in each specifier
to look like "internal:deno_node/polyfills/<module_name>", but with
this change it's "internal:deno_node/<module_name>".
Given that "deno_node" has over 100 files, many of them having
several import specifiers to the same extension, this change removes
10 characters from each import specifier.
2023-03-04 21:31:38 -05:00
|
|
|
} from "internal:deno_node/_fs/_fs_common.ts";
|
|
|
|
import { isWindows } from "internal:deno_node/_util/os.ts";
|
2023-02-14 11:38:45 -05:00
|
|
|
import {
|
|
|
|
AbortError,
|
|
|
|
denoErrorToNodeError,
|
refactor(core): include_js_files! 'dir' option doesn't change specifiers (#18019)
This commit changes "include_js_files!" macro from "deno_core"
in a way that "dir" option doesn't cause specifiers to be rewritten
to include it.
Example:
```
include_js_files! {
dir "js",
"hello.js",
}
```
The above definition required embedders to use:
`import ... from "internal:<ext_name>/js/hello.js"`.
But with this change, the "js" directory in which the files are stored
is an implementation detail, which for embedders results in:
`import ... from "internal:<ext_name>/hello.js"`.
The directory the files are stored in, is an implementation detail and
in some cases might result in a significant size difference for the
snapshot. As an example, in "deno_node" extension, we store the
source code in "polyfills" directory; which resulted in each specifier
to look like "internal:deno_node/polyfills/<module_name>", but with
this change it's "internal:deno_node/<module_name>".
Given that "deno_node" has over 100 files, many of them having
several import specifiers to the same extension, this change removes
10 characters from each import specifier.
2023-03-04 21:31:38 -05:00
|
|
|
} from "internal:deno_node/internal/errors.ts";
|
2023-02-14 11:38:45 -05:00
|
|
|
import {
|
|
|
|
showStringCoercionDeprecation,
|
|
|
|
validateStringAfterArrayBufferView,
|
refactor(core): include_js_files! 'dir' option doesn't change specifiers (#18019)
This commit changes "include_js_files!" macro from "deno_core"
in a way that "dir" option doesn't cause specifiers to be rewritten
to include it.
Example:
```
include_js_files! {
dir "js",
"hello.js",
}
```
The above definition required embedders to use:
`import ... from "internal:<ext_name>/js/hello.js"`.
But with this change, the "js" directory in which the files are stored
is an implementation detail, which for embedders results in:
`import ... from "internal:<ext_name>/hello.js"`.
The directory the files are stored in, is an implementation detail and
in some cases might result in a significant size difference for the
snapshot. As an example, in "deno_node" extension, we store the
source code in "polyfills" directory; which resulted in each specifier
to look like "internal:deno_node/polyfills/<module_name>", but with
this change it's "internal:deno_node/<module_name>".
Given that "deno_node" has over 100 files, many of them having
several import specifiers to the same extension, this change removes
10 characters from each import specifier.
2023-03-04 21:31:38 -05:00
|
|
|
} from "internal:deno_node/internal/fs/utils.mjs";
|
|
|
|
import { promisify } from "internal:deno_node/internal/util.mjs";
|
2023-02-14 11:38:45 -05:00
|
|
|
|
|
|
|
interface Writer {
|
|
|
|
write(p: Uint8Array): Promise<number>;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function writeFile(
|
|
|
|
pathOrRid: string | number | URL,
|
|
|
|
// deno-lint-ignore ban-types
|
|
|
|
data: string | Uint8Array | Object,
|
|
|
|
optOrCallback: Encodings | CallbackWithError | WriteFileOptions | undefined,
|
|
|
|
callback?: CallbackWithError,
|
|
|
|
) {
|
|
|
|
const callbackFn: CallbackWithError | undefined =
|
|
|
|
optOrCallback instanceof Function ? optOrCallback : callback;
|
|
|
|
const options: Encodings | WriteFileOptions | undefined =
|
|
|
|
optOrCallback instanceof Function ? undefined : optOrCallback;
|
|
|
|
|
|
|
|
if (!callbackFn) {
|
|
|
|
throw new TypeError("Callback must be a function.");
|
|
|
|
}
|
|
|
|
|
|
|
|
pathOrRid = pathOrRid instanceof URL ? fromFileUrl(pathOrRid) : pathOrRid;
|
|
|
|
|
|
|
|
const flag: string | undefined = isFileOptions(options)
|
|
|
|
? options.flag
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
const mode: number | undefined = isFileOptions(options)
|
|
|
|
? options.mode
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
const encoding = checkEncoding(getEncoding(options)) || "utf8";
|
|
|
|
const openOptions = getOpenOptions(flag || "w");
|
|
|
|
|
|
|
|
if (!ArrayBuffer.isView(data)) {
|
|
|
|
validateStringAfterArrayBufferView(data, "data");
|
|
|
|
if (typeof data !== "string") {
|
|
|
|
showStringCoercionDeprecation();
|
|
|
|
}
|
|
|
|
data = Buffer.from(String(data), encoding);
|
|
|
|
}
|
|
|
|
|
|
|
|
const isRid = typeof pathOrRid === "number";
|
|
|
|
let file;
|
|
|
|
|
|
|
|
let error: Error | null = null;
|
|
|
|
(async () => {
|
|
|
|
try {
|
|
|
|
file = isRid
|
|
|
|
? new Deno.FsFile(pathOrRid as number)
|
|
|
|
: await Deno.open(pathOrRid as string, openOptions);
|
|
|
|
|
|
|
|
// ignore mode because it's not supported on windows
|
|
|
|
// TODO(@bartlomieju): remove `!isWindows` when `Deno.chmod` is supported
|
|
|
|
if (!isRid && mode && !isWindows) {
|
|
|
|
await Deno.chmod(pathOrRid as string, mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
const signal: AbortSignal | undefined = isFileOptions(options)
|
|
|
|
? options.signal
|
|
|
|
: undefined;
|
|
|
|
await writeAll(file, data as Uint8Array, { signal });
|
|
|
|
} catch (e) {
|
|
|
|
error = e instanceof Error
|
|
|
|
? denoErrorToNodeError(e, { syscall: "write" })
|
|
|
|
: new Error("[non-error thrown]");
|
|
|
|
} finally {
|
|
|
|
// Make sure to close resource
|
|
|
|
if (!isRid && file) file.close();
|
|
|
|
callbackFn(error);
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
|
|
|
export const writeFilePromise = promisify(writeFile) as (
|
|
|
|
pathOrRid: string | number | URL,
|
|
|
|
// deno-lint-ignore ban-types
|
|
|
|
data: string | Uint8Array | Object,
|
|
|
|
options?: Encodings | WriteFileOptions,
|
|
|
|
) => Promise<void>;
|
|
|
|
|
|
|
|
export function writeFileSync(
|
|
|
|
pathOrRid: string | number | URL,
|
|
|
|
// deno-lint-ignore ban-types
|
|
|
|
data: string | Uint8Array | Object,
|
|
|
|
options?: Encodings | WriteFileOptions,
|
|
|
|
) {
|
|
|
|
pathOrRid = pathOrRid instanceof URL ? fromFileUrl(pathOrRid) : pathOrRid;
|
|
|
|
|
|
|
|
const flag: string | undefined = isFileOptions(options)
|
|
|
|
? options.flag
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
const mode: number | undefined = isFileOptions(options)
|
|
|
|
? options.mode
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
const encoding = checkEncoding(getEncoding(options)) || "utf8";
|
|
|
|
const openOptions = getOpenOptions(flag || "w");
|
|
|
|
|
|
|
|
if (!ArrayBuffer.isView(data)) {
|
|
|
|
validateStringAfterArrayBufferView(data, "data");
|
|
|
|
if (typeof data !== "string") {
|
|
|
|
showStringCoercionDeprecation();
|
|
|
|
}
|
|
|
|
data = Buffer.from(String(data), encoding);
|
|
|
|
}
|
|
|
|
|
|
|
|
const isRid = typeof pathOrRid === "number";
|
|
|
|
let file;
|
|
|
|
|
|
|
|
let error: Error | null = null;
|
|
|
|
try {
|
|
|
|
file = isRid
|
|
|
|
? new Deno.FsFile(pathOrRid as number)
|
|
|
|
: Deno.openSync(pathOrRid as string, openOptions);
|
|
|
|
|
|
|
|
// ignore mode because it's not supported on windows
|
|
|
|
// TODO(@bartlomieju): remove `!isWindows` when `Deno.chmod` is supported
|
|
|
|
if (!isRid && mode && !isWindows) {
|
|
|
|
Deno.chmodSync(pathOrRid as string, mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(crowlKats): duplicate from runtime/js/13_buffer.js
|
|
|
|
let nwritten = 0;
|
|
|
|
while (nwritten < (data as Uint8Array).length) {
|
|
|
|
nwritten += file.writeSync((data as Uint8Array).subarray(nwritten));
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
error = e instanceof Error
|
|
|
|
? denoErrorToNodeError(e, { syscall: "write" })
|
|
|
|
: new Error("[non-error thrown]");
|
|
|
|
} finally {
|
|
|
|
// Make sure to close resource
|
|
|
|
if (!isRid && file) file.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (error) throw error;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface WriteAllOptions {
|
|
|
|
offset?: number;
|
|
|
|
length?: number;
|
|
|
|
signal?: AbortSignal;
|
|
|
|
}
|
|
|
|
async function writeAll(
|
|
|
|
w: Writer,
|
|
|
|
arr: Uint8Array,
|
|
|
|
options: WriteAllOptions = {},
|
|
|
|
) {
|
|
|
|
const { offset = 0, length = arr.byteLength, signal } = options;
|
|
|
|
checkAborted(signal);
|
|
|
|
|
|
|
|
const written = await w.write(arr.subarray(offset, offset + length));
|
|
|
|
|
|
|
|
if (written === length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await writeAll(w, arr, {
|
|
|
|
offset: offset + written,
|
|
|
|
length: length - written,
|
|
|
|
signal,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkAborted(signal?: AbortSignal) {
|
|
|
|
if (signal?.aborted) {
|
|
|
|
throw new AbortError();
|
|
|
|
}
|
|
|
|
}
|