mirror of
https://github.com/denoland/deno.git
synced 2024-12-25 16:49:18 -05:00
b40086fd7d
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.
420 lines
13 KiB
TypeScript
420 lines
13 KiB
TypeScript
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
|
|
|
|
// deno-lint-ignore camelcase
|
|
import * as async_wrap from "internal:deno_node/internal_binding/async_wrap.ts";
|
|
import { ERR_ASYNC_CALLBACK } from "internal:deno_node/internal/errors.ts";
|
|
export {
|
|
asyncIdSymbol,
|
|
ownerSymbol,
|
|
} from "internal:deno_node/internal_binding/symbols.ts";
|
|
|
|
interface ActiveHooks {
|
|
array: AsyncHook[];
|
|
// deno-lint-ignore camelcase
|
|
call_depth: number;
|
|
// deno-lint-ignore camelcase
|
|
tmp_array: AsyncHook[] | null;
|
|
// deno-lint-ignore camelcase
|
|
tmp_fields: number[] | null;
|
|
}
|
|
|
|
// Properties in active_hooks are used to keep track of the set of hooks being
|
|
// executed in case another hook is enabled/disabled. The new set of hooks is
|
|
// then restored once the active set of hooks is finished executing.
|
|
// deno-lint-ignore camelcase
|
|
const active_hooks: ActiveHooks = {
|
|
// Array of all AsyncHooks that will be iterated whenever an async event
|
|
// fires. Using var instead of (preferably const) in order to assign
|
|
// active_hooks.tmp_array if a hook is enabled/disabled during hook
|
|
// execution.
|
|
array: [],
|
|
// Use a counter to track nested calls of async hook callbacks and make sure
|
|
// the active_hooks.array isn't altered mid execution.
|
|
// deno-lint-ignore camelcase
|
|
call_depth: 0,
|
|
// Use to temporarily store and updated active_hooks.array if the user
|
|
// enables or disables a hook while hooks are being processed. If a hook is
|
|
// enabled() or disabled() during hook execution then the current set of
|
|
// active hooks is duplicated and set equal to active_hooks.tmp_array. Any
|
|
// subsequent changes are on the duplicated array. When all hooks have
|
|
// completed executing active_hooks.tmp_array is assigned to
|
|
// active_hooks.array.
|
|
// deno-lint-ignore camelcase
|
|
tmp_array: null,
|
|
// Keep track of the field counts held in active_hooks.tmp_array. Because the
|
|
// async_hook_fields can't be reassigned, store each uint32 in an array that
|
|
// is written back to async_hook_fields when active_hooks.array is restored.
|
|
// deno-lint-ignore camelcase
|
|
tmp_fields: null,
|
|
};
|
|
|
|
export const registerDestroyHook = async_wrap.registerDestroyHook;
|
|
const {
|
|
// deno-lint-ignore camelcase
|
|
async_hook_fields,
|
|
// deno-lint-ignore camelcase
|
|
asyncIdFields: async_id_fields,
|
|
newAsyncId,
|
|
constants,
|
|
} = async_wrap;
|
|
export { newAsyncId };
|
|
const {
|
|
kInit,
|
|
kBefore,
|
|
kAfter,
|
|
kDestroy,
|
|
kPromiseResolve,
|
|
kTotals,
|
|
kCheck,
|
|
kDefaultTriggerAsyncId,
|
|
kStackLength,
|
|
} = constants;
|
|
|
|
// deno-lint-ignore camelcase
|
|
const resource_symbol = Symbol("resource");
|
|
// deno-lint-ignore camelcase
|
|
export const async_id_symbol = Symbol("trigger_async_id");
|
|
// deno-lint-ignore camelcase
|
|
export const trigger_async_id_symbol = Symbol("trigger_async_id");
|
|
// deno-lint-ignore camelcase
|
|
export const init_symbol = Symbol("init");
|
|
// deno-lint-ignore camelcase
|
|
export const before_symbol = Symbol("before");
|
|
// deno-lint-ignore camelcase
|
|
export const after_symbol = Symbol("after");
|
|
// deno-lint-ignore camelcase
|
|
export const destroy_symbol = Symbol("destroy");
|
|
// deno-lint-ignore camelcase
|
|
export const promise_resolve_symbol = Symbol("promiseResolve");
|
|
|
|
export const symbols = {
|
|
// deno-lint-ignore camelcase
|
|
async_id_symbol,
|
|
// deno-lint-ignore camelcase
|
|
trigger_async_id_symbol,
|
|
// deno-lint-ignore camelcase
|
|
init_symbol,
|
|
// deno-lint-ignore camelcase
|
|
before_symbol,
|
|
// deno-lint-ignore camelcase
|
|
after_symbol,
|
|
// deno-lint-ignore camelcase
|
|
destroy_symbol,
|
|
// deno-lint-ignore camelcase
|
|
promise_resolve_symbol,
|
|
};
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
function lookupPublicResource(resource: any) {
|
|
if (typeof resource !== "object" || resource === null) return resource;
|
|
// TODO(addaleax): Merge this with owner_symbol and use it across all
|
|
// AsyncWrap instances.
|
|
const publicResource = resource[resource_symbol];
|
|
if (publicResource !== undefined) {
|
|
return publicResource;
|
|
}
|
|
return resource;
|
|
}
|
|
|
|
// Used by C++ to call all init() callbacks. Because some state can be setup
|
|
// from C++ there's no need to perform all the same operations as in
|
|
// emitInitScript.
|
|
function emitInitNative(
|
|
asyncId: number,
|
|
// deno-lint-ignore no-explicit-any
|
|
type: any,
|
|
triggerAsyncId: number,
|
|
// deno-lint-ignore no-explicit-any
|
|
resource: any,
|
|
) {
|
|
active_hooks.call_depth += 1;
|
|
resource = lookupPublicResource(resource);
|
|
// Use a single try/catch for all hooks to avoid setting up one per iteration.
|
|
try {
|
|
for (let i = 0; i < active_hooks.array.length; i++) {
|
|
if (typeof active_hooks.array[i][init_symbol] === "function") {
|
|
active_hooks.array[i][init_symbol](
|
|
asyncId,
|
|
type,
|
|
triggerAsyncId,
|
|
resource,
|
|
);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
throw e;
|
|
} finally {
|
|
active_hooks.call_depth -= 1;
|
|
}
|
|
|
|
// Hooks can only be restored if there have been no recursive hook calls.
|
|
// Also the active hooks do not need to be restored if enable()/disable()
|
|
// weren't called during hook execution, in which case active_hooks.tmp_array
|
|
// will be null.
|
|
if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
|
|
restoreActiveHooks();
|
|
}
|
|
}
|
|
|
|
function getHookArrays(): [AsyncHook[], number[] | Uint32Array] {
|
|
if (active_hooks.call_depth === 0) {
|
|
return [active_hooks.array, async_hook_fields];
|
|
}
|
|
// If this hook is being enabled while in the middle of processing the array
|
|
// of currently active hooks then duplicate the current set of active hooks
|
|
// and store this there. This shouldn't fire until the next time hooks are
|
|
// processed.
|
|
if (active_hooks.tmp_array === null) {
|
|
storeActiveHooks();
|
|
}
|
|
return [active_hooks.tmp_array!, active_hooks.tmp_fields!];
|
|
}
|
|
|
|
function storeActiveHooks() {
|
|
active_hooks.tmp_array = active_hooks.array.slice();
|
|
// Don't want to make the assumption that kInit to kDestroy are indexes 0 to
|
|
// 4. So do this the long way.
|
|
active_hooks.tmp_fields = [];
|
|
copyHooks(active_hooks.tmp_fields, async_hook_fields);
|
|
}
|
|
|
|
function copyHooks(
|
|
destination: number[] | Uint32Array,
|
|
source: number[] | Uint32Array,
|
|
) {
|
|
destination[kInit] = source[kInit];
|
|
destination[kBefore] = source[kBefore];
|
|
destination[kAfter] = source[kAfter];
|
|
destination[kDestroy] = source[kDestroy];
|
|
destination[kPromiseResolve] = source[kPromiseResolve];
|
|
}
|
|
|
|
// Then restore the correct hooks array in case any hooks were added/removed
|
|
// during hook callback execution.
|
|
function restoreActiveHooks() {
|
|
active_hooks.array = active_hooks.tmp_array!;
|
|
copyHooks(async_hook_fields, active_hooks.tmp_fields!);
|
|
|
|
active_hooks.tmp_array = null;
|
|
active_hooks.tmp_fields = null;
|
|
}
|
|
|
|
// deno-lint-ignore no-unused-vars
|
|
let wantPromiseHook = false;
|
|
function enableHooks() {
|
|
async_hook_fields[kCheck] += 1;
|
|
|
|
// TODO(kt3k): Uncomment this
|
|
// setCallbackTrampoline(callbackTrampoline);
|
|
}
|
|
|
|
function disableHooks() {
|
|
async_hook_fields[kCheck] -= 1;
|
|
|
|
wantPromiseHook = false;
|
|
|
|
// TODO(kt3k): Uncomment the below
|
|
// setCallbackTrampoline();
|
|
|
|
// Delay the call to `disablePromiseHook()` because we might currently be
|
|
// between the `before` and `after` calls of a Promise.
|
|
// TODO(kt3k): Uncomment the below
|
|
// enqueueMicrotask(disablePromiseHookIfNecessary);
|
|
}
|
|
|
|
// Return the triggerAsyncId meant for the constructor calling it. It's up to
|
|
// the user to safeguard this call and make sure it's zero'd out when the
|
|
// constructor is complete.
|
|
export function getDefaultTriggerAsyncId() {
|
|
const defaultTriggerAsyncId =
|
|
async_id_fields[async_wrap.UidFields.kDefaultTriggerAsyncId];
|
|
// If defaultTriggerAsyncId isn't set, use the executionAsyncId
|
|
if (defaultTriggerAsyncId < 0) {
|
|
return async_id_fields[async_wrap.UidFields.kExecutionAsyncId];
|
|
}
|
|
return defaultTriggerAsyncId;
|
|
}
|
|
|
|
export function defaultTriggerAsyncIdScope(
|
|
triggerAsyncId: number | undefined,
|
|
// deno-lint-ignore no-explicit-any
|
|
block: (...arg: any[]) => void,
|
|
...args: unknown[]
|
|
) {
|
|
if (triggerAsyncId === undefined) {
|
|
return block.apply(null, args);
|
|
}
|
|
// CHECK(NumberIsSafeInteger(triggerAsyncId))
|
|
// CHECK(triggerAsyncId > 0)
|
|
const oldDefaultTriggerAsyncId = async_id_fields[kDefaultTriggerAsyncId];
|
|
async_id_fields[kDefaultTriggerAsyncId] = triggerAsyncId;
|
|
|
|
try {
|
|
return block.apply(null, args);
|
|
} finally {
|
|
async_id_fields[kDefaultTriggerAsyncId] = oldDefaultTriggerAsyncId;
|
|
}
|
|
}
|
|
|
|
function hasHooks(key: number) {
|
|
return async_hook_fields[key] > 0;
|
|
}
|
|
|
|
export function enabledHooksExist() {
|
|
return hasHooks(kCheck);
|
|
}
|
|
|
|
export function initHooksExist() {
|
|
return hasHooks(kInit);
|
|
}
|
|
|
|
export function afterHooksExist() {
|
|
return hasHooks(kAfter);
|
|
}
|
|
|
|
export function destroyHooksExist() {
|
|
return hasHooks(kDestroy);
|
|
}
|
|
|
|
export function promiseResolveHooksExist() {
|
|
return hasHooks(kPromiseResolve);
|
|
}
|
|
|
|
function emitInitScript(
|
|
asyncId: number,
|
|
// deno-lint-ignore no-explicit-any
|
|
type: any,
|
|
triggerAsyncId: number,
|
|
// deno-lint-ignore no-explicit-any
|
|
resource: any,
|
|
) {
|
|
// Short circuit all checks for the common case. Which is that no hooks have
|
|
// been set. Do this to remove performance impact for embedders (and core).
|
|
if (!hasHooks(kInit)) {
|
|
return;
|
|
}
|
|
|
|
if (triggerAsyncId === null) {
|
|
triggerAsyncId = getDefaultTriggerAsyncId();
|
|
}
|
|
|
|
emitInitNative(asyncId, type, triggerAsyncId, resource);
|
|
}
|
|
export { emitInitScript as emitInit };
|
|
|
|
export function hasAsyncIdStack() {
|
|
return hasHooks(kStackLength);
|
|
}
|
|
|
|
export { constants };
|
|
|
|
type Fn = (...args: unknown[]) => unknown;
|
|
|
|
export class AsyncHook {
|
|
[init_symbol]: Fn;
|
|
[before_symbol]: Fn;
|
|
[after_symbol]: Fn;
|
|
[destroy_symbol]: Fn;
|
|
[promise_resolve_symbol]: Fn;
|
|
|
|
constructor({
|
|
init,
|
|
before,
|
|
after,
|
|
destroy,
|
|
promiseResolve,
|
|
}: {
|
|
init: Fn;
|
|
before: Fn;
|
|
after: Fn;
|
|
destroy: Fn;
|
|
promiseResolve: Fn;
|
|
}) {
|
|
if (init !== undefined && typeof init !== "function") {
|
|
throw new ERR_ASYNC_CALLBACK("hook.init");
|
|
}
|
|
if (before !== undefined && typeof before !== "function") {
|
|
throw new ERR_ASYNC_CALLBACK("hook.before");
|
|
}
|
|
if (after !== undefined && typeof after !== "function") {
|
|
throw new ERR_ASYNC_CALLBACK("hook.after");
|
|
}
|
|
if (destroy !== undefined && typeof destroy !== "function") {
|
|
throw new ERR_ASYNC_CALLBACK("hook.destroy");
|
|
}
|
|
if (promiseResolve !== undefined && typeof promiseResolve !== "function") {
|
|
throw new ERR_ASYNC_CALLBACK("hook.promiseResolve");
|
|
}
|
|
|
|
this[init_symbol] = init;
|
|
this[before_symbol] = before;
|
|
this[after_symbol] = after;
|
|
this[destroy_symbol] = destroy;
|
|
this[promise_resolve_symbol] = promiseResolve;
|
|
}
|
|
|
|
enable() {
|
|
// The set of callbacks for a hook should be the same regardless of whether
|
|
// enable()/disable() are run during their execution. The following
|
|
// references are reassigned to the tmp arrays if a hook is currently being
|
|
// processed.
|
|
// deno-lint-ignore camelcase
|
|
const { 0: hooks_array, 1: hook_fields } = getHookArrays();
|
|
|
|
// Each hook is only allowed to be added once.
|
|
if (hooks_array.includes(this)) {
|
|
return this;
|
|
}
|
|
|
|
// deno-lint-ignore camelcase
|
|
const prev_kTotals = hook_fields[kTotals];
|
|
|
|
// createHook() has already enforced that the callbacks are all functions,
|
|
// so here simply increment the count of whether each callbacks exists or
|
|
// not.
|
|
hook_fields[kTotals] = hook_fields[kInit] += +!!this[init_symbol];
|
|
hook_fields[kTotals] += hook_fields[kBefore] += +!!this[before_symbol];
|
|
hook_fields[kTotals] += hook_fields[kAfter] += +!!this[after_symbol];
|
|
hook_fields[kTotals] += hook_fields[kDestroy] += +!!this[destroy_symbol];
|
|
hook_fields[kTotals] += hook_fields[kPromiseResolve] +=
|
|
+!!this[promise_resolve_symbol];
|
|
hooks_array.push(this);
|
|
|
|
if (prev_kTotals === 0 && hook_fields[kTotals] > 0) {
|
|
enableHooks();
|
|
}
|
|
|
|
// TODO(kt3k): Uncomment the below
|
|
// updatePromiseHookMode();
|
|
|
|
return this;
|
|
}
|
|
|
|
disable() {
|
|
// deno-lint-ignore camelcase
|
|
const { 0: hooks_array, 1: hook_fields } = getHookArrays();
|
|
|
|
const index = hooks_array.indexOf(this);
|
|
if (index === -1) {
|
|
return this;
|
|
}
|
|
|
|
// deno-lint-ignore camelcase
|
|
const prev_kTotals = hook_fields[kTotals];
|
|
|
|
hook_fields[kTotals] = hook_fields[kInit] -= +!!this[init_symbol];
|
|
hook_fields[kTotals] += hook_fields[kBefore] -= +!!this[before_symbol];
|
|
hook_fields[kTotals] += hook_fields[kAfter] -= +!!this[after_symbol];
|
|
hook_fields[kTotals] += hook_fields[kDestroy] -= +!!this[destroy_symbol];
|
|
hook_fields[kTotals] += hook_fields[kPromiseResolve] -=
|
|
+!!this[promise_resolve_symbol];
|
|
hooks_array.splice(index, 1);
|
|
|
|
if (prev_kTotals > 0 && hook_fields[kTotals] === 0) {
|
|
disableHooks();
|
|
}
|
|
|
|
return this;
|
|
}
|
|
}
|