318 lines
9.9 KiB
JavaScript
318 lines
9.9 KiB
JavaScript
|
|
import { S as ScriptNetworkEvents } from './unhead.AvDFlk_u.mjs';
|
||
|
|
|
||
|
|
function NOOP() {
|
||
|
|
}
|
||
|
|
function replayRecordings(target, stack) {
|
||
|
|
stack.forEach((recordings) => {
|
||
|
|
let context = target;
|
||
|
|
let prevContext = target;
|
||
|
|
recordings.forEach(({ type, key, args }) => {
|
||
|
|
if (type === "get") {
|
||
|
|
prevContext = context;
|
||
|
|
context = context[key];
|
||
|
|
} else if (type === "apply") {
|
||
|
|
context = Reflect.apply(context, prevContext, args);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function walk(root, path) {
|
||
|
|
let owner;
|
||
|
|
let value = root;
|
||
|
|
for (const key of path) {
|
||
|
|
if (value == null)
|
||
|
|
return { owner: void 0, value: void 0 };
|
||
|
|
owner = value;
|
||
|
|
value = value[key];
|
||
|
|
}
|
||
|
|
return { owner, value };
|
||
|
|
}
|
||
|
|
function createScriptProxy(initial = {}) {
|
||
|
|
const stack = [];
|
||
|
|
let instance;
|
||
|
|
let stackIdx = -1;
|
||
|
|
function node(path) {
|
||
|
|
const children = /* @__PURE__ */ new Map();
|
||
|
|
return new Proxy(NOOP, {
|
||
|
|
get(_, prop) {
|
||
|
|
if (instance) {
|
||
|
|
const { value } = walk(instance, path);
|
||
|
|
const result = value == null ? void 0 : Reflect.get(value, prop, value);
|
||
|
|
if (typeof result !== "function")
|
||
|
|
return result;
|
||
|
|
} else if (!path.length) {
|
||
|
|
const result = Reflect.get(initial, prop);
|
||
|
|
if (typeof result !== "undefined")
|
||
|
|
return result;
|
||
|
|
stackIdx++;
|
||
|
|
stack[stackIdx] = [{ type: "get", key: prop }];
|
||
|
|
} else {
|
||
|
|
stack[stackIdx].push({ type: "get", key: prop });
|
||
|
|
}
|
||
|
|
let child = children.get(prop);
|
||
|
|
if (!child) {
|
||
|
|
child = node([...path, prop]);
|
||
|
|
children.set(prop, child);
|
||
|
|
}
|
||
|
|
return child;
|
||
|
|
},
|
||
|
|
apply(_, __, args) {
|
||
|
|
if (instance) {
|
||
|
|
const { owner, value } = walk(instance, path);
|
||
|
|
if (typeof value === "function")
|
||
|
|
Reflect.apply(value, owner, args);
|
||
|
|
} else {
|
||
|
|
stack[stackIdx].push({ type: "apply", key: "", args });
|
||
|
|
}
|
||
|
|
return void 0;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
proxy: node([]),
|
||
|
|
stack,
|
||
|
|
resolve(api) {
|
||
|
|
instance = api;
|
||
|
|
replayRecordings(api, stack);
|
||
|
|
stack.length = 0;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function resolveScriptKey(input) {
|
||
|
|
return input.key || input.src || (typeof input.innerHTML === "string" ? input.innerHTML : "");
|
||
|
|
}
|
||
|
|
const PreconnectServerModes = ["preconnect", "dns-prefetch"];
|
||
|
|
function useScript(head, _input, _options) {
|
||
|
|
const input = typeof _input === "string" ? { src: _input } : _input;
|
||
|
|
const options = _options || {};
|
||
|
|
const id = resolveScriptKey(input);
|
||
|
|
const scripts = head._scripts || (head._scripts = /* @__PURE__ */ Object.create(null));
|
||
|
|
const prevScript = Object.prototype.hasOwnProperty.call(scripts, id) ? scripts[id] : void 0;
|
||
|
|
if (prevScript) {
|
||
|
|
prevScript.setupTriggerHandler(options.trigger);
|
||
|
|
return prevScript;
|
||
|
|
}
|
||
|
|
options.beforeInit?.();
|
||
|
|
const syncStatus = (s) => {
|
||
|
|
script.status = s;
|
||
|
|
head.hooks.callHook(`script:updated`, hookCtx);
|
||
|
|
};
|
||
|
|
ScriptNetworkEvents.forEach((fn) => {
|
||
|
|
const k = fn;
|
||
|
|
const _fn = typeof input[k] === "function" ? input[k].bind(options.eventContext) : null;
|
||
|
|
input[k] = (e) => {
|
||
|
|
if (script.status === "removed")
|
||
|
|
return;
|
||
|
|
syncStatus(fn === "onload" ? "loaded" : fn === "onerror" ? "error" : "loading");
|
||
|
|
_fn?.(e);
|
||
|
|
};
|
||
|
|
});
|
||
|
|
const _cbs = { loaded: [], error: [] };
|
||
|
|
const _uniqueCbs = /* @__PURE__ */ new Set();
|
||
|
|
const _registerCb = (key, cb, options2) => {
|
||
|
|
if (head.ssr) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
let uniqueKey;
|
||
|
|
if (options2?.key) {
|
||
|
|
uniqueKey = `${key}:${options2.key}`;
|
||
|
|
if (_uniqueCbs.has(uniqueKey)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
_uniqueCbs.add(uniqueKey);
|
||
|
|
}
|
||
|
|
if (_cbs[key]) {
|
||
|
|
_cbs[key].push(cb);
|
||
|
|
return () => {
|
||
|
|
const idx = _cbs[key]?.indexOf(cb) ?? -1;
|
||
|
|
if (idx !== -1)
|
||
|
|
_cbs[key]?.splice(idx, 1);
|
||
|
|
if (uniqueKey)
|
||
|
|
_uniqueCbs.delete(uniqueKey);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
if (key === "loaded" && script.status === "loaded")
|
||
|
|
cb(script.instance);
|
||
|
|
else if (key === "error" && script.status === "error")
|
||
|
|
cb();
|
||
|
|
return () => {
|
||
|
|
if (uniqueKey)
|
||
|
|
_uniqueCbs.delete(uniqueKey);
|
||
|
|
};
|
||
|
|
};
|
||
|
|
const loadPromise = new Promise((resolve) => {
|
||
|
|
if (head.ssr)
|
||
|
|
return;
|
||
|
|
const emit = (api) => queueMicrotask(() => resolve(api));
|
||
|
|
const _ = head.hooks.hook("script:updated", ({ script: updatedScript }) => {
|
||
|
|
if (updatedScript !== script)
|
||
|
|
return;
|
||
|
|
const status = updatedScript.status;
|
||
|
|
if (status === "loaded" || status === "error" || status === "removed") {
|
||
|
|
if (status === "loaded") {
|
||
|
|
if (typeof options.use === "function") {
|
||
|
|
const api = options.use();
|
||
|
|
if (api) {
|
||
|
|
emit(api);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
emit({});
|
||
|
|
}
|
||
|
|
} else if (status === "error") {
|
||
|
|
resolve(false);
|
||
|
|
} else if (status === "removed") {
|
||
|
|
resolve(false);
|
||
|
|
}
|
||
|
|
_();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
const script = {
|
||
|
|
_loadPromise: loadPromise,
|
||
|
|
instance: !head.ssr && options?.use?.() || null,
|
||
|
|
proxy: null,
|
||
|
|
id,
|
||
|
|
status: "awaitingLoad",
|
||
|
|
remove() {
|
||
|
|
const hadEntry = !!script.entry;
|
||
|
|
script._triggerAbortControllers?.forEach((ac) => ac.abort());
|
||
|
|
script._triggerAbortControllers?.clear();
|
||
|
|
script._triggerPromises = [];
|
||
|
|
script._warmupEl?.dispose();
|
||
|
|
script._warmupEl = void 0;
|
||
|
|
if (script.entry) {
|
||
|
|
script.entry.dispose();
|
||
|
|
script.entry = void 0;
|
||
|
|
}
|
||
|
|
if (scripts[id] === script)
|
||
|
|
delete scripts[id];
|
||
|
|
if (script.status !== "removed")
|
||
|
|
syncStatus("removed");
|
||
|
|
return hadEntry;
|
||
|
|
},
|
||
|
|
warmup(rel) {
|
||
|
|
const { src } = input;
|
||
|
|
const isCrossOrigin = !src.startsWith("/") || src.startsWith("//");
|
||
|
|
const isPreconnect = rel && PreconnectServerModes.includes(rel);
|
||
|
|
let href = src;
|
||
|
|
if (!rel || isPreconnect && !isCrossOrigin) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (isPreconnect) {
|
||
|
|
const $url = new URL(src);
|
||
|
|
href = `${$url.protocol}//${$url.host}`;
|
||
|
|
}
|
||
|
|
const link = {
|
||
|
|
href,
|
||
|
|
rel,
|
||
|
|
crossorigin: typeof input.crossorigin !== "undefined" ? input.crossorigin : isCrossOrigin ? "anonymous" : void 0,
|
||
|
|
referrerpolicy: typeof input.referrerpolicy !== "undefined" ? input.referrerpolicy : isCrossOrigin ? "no-referrer" : void 0,
|
||
|
|
fetchpriority: typeof input.fetchpriority !== "undefined" ? input.fetchpriority : "low",
|
||
|
|
integrity: input.integrity,
|
||
|
|
as: rel === "preload" ? "script" : void 0
|
||
|
|
};
|
||
|
|
script._warmupEl = head.push({ link: [link] }, { head, tagPriority: "high" });
|
||
|
|
return script._warmupEl;
|
||
|
|
},
|
||
|
|
load(cb) {
|
||
|
|
if (script.status === "removed")
|
||
|
|
return loadPromise;
|
||
|
|
script._triggerAbortControllers?.forEach((ac) => ac.abort());
|
||
|
|
script._triggerAbortControllers?.clear();
|
||
|
|
script._triggerPromises = [];
|
||
|
|
if (!script.entry) {
|
||
|
|
syncStatus("loading");
|
||
|
|
const defaults = {
|
||
|
|
defer: true,
|
||
|
|
fetchpriority: "low"
|
||
|
|
};
|
||
|
|
if (input.src && (input.src.startsWith("http") || input.src.startsWith("//"))) {
|
||
|
|
defaults.crossorigin = "anonymous";
|
||
|
|
defaults.referrerpolicy = "no-referrer";
|
||
|
|
}
|
||
|
|
script.entry = head.push({
|
||
|
|
script: [{ ...defaults, ...input }]
|
||
|
|
}, options);
|
||
|
|
}
|
||
|
|
if (cb)
|
||
|
|
_registerCb("loaded", cb);
|
||
|
|
return loadPromise;
|
||
|
|
},
|
||
|
|
onLoaded(cb, options2) {
|
||
|
|
return _registerCb("loaded", cb, options2);
|
||
|
|
},
|
||
|
|
onError(cb, options2) {
|
||
|
|
return _registerCb("error", cb, options2);
|
||
|
|
},
|
||
|
|
setupTriggerHandler(trigger) {
|
||
|
|
if (script.status !== "awaitingLoad") {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if ((typeof trigger === "undefined" || trigger === "client") && !head.ssr || trigger === "server") {
|
||
|
|
script.load();
|
||
|
|
} else if (trigger instanceof Promise) {
|
||
|
|
if (head.ssr) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const abortController = new AbortController();
|
||
|
|
script._triggerAbortControllers = script._triggerAbortControllers || /* @__PURE__ */ new Set();
|
||
|
|
script._triggerAbortControllers.add(abortController);
|
||
|
|
const abortPromise = new Promise((resolve) => {
|
||
|
|
abortController.signal.addEventListener("abort", () => {
|
||
|
|
script._triggerAbortControllers?.delete(abortController);
|
||
|
|
resolve();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
script._triggerAbortController = abortController;
|
||
|
|
script._triggerPromises = script._triggerPromises || [];
|
||
|
|
const triggerPromise = Promise.race([
|
||
|
|
trigger.then((v) => typeof v === "undefined" || v ? script.load : void 0),
|
||
|
|
abortPromise
|
||
|
|
]).catch(() => {
|
||
|
|
}).then((res) => {
|
||
|
|
res?.();
|
||
|
|
}).finally(() => {
|
||
|
|
script._triggerAbortControllers?.delete(abortController);
|
||
|
|
const idx = script._triggerPromises?.indexOf(triggerPromise) ?? -1;
|
||
|
|
if (idx !== -1)
|
||
|
|
script._triggerPromises?.splice(idx, 1);
|
||
|
|
});
|
||
|
|
script._triggerPromises.push(triggerPromise);
|
||
|
|
} else if (typeof trigger === "function") {
|
||
|
|
trigger(script.load);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
_cbs
|
||
|
|
};
|
||
|
|
loadPromise.then((api) => {
|
||
|
|
if (api !== false) {
|
||
|
|
script.instance = api;
|
||
|
|
_cbs.loaded?.forEach((cb) => cb(api));
|
||
|
|
_cbs.loaded = null;
|
||
|
|
} else {
|
||
|
|
if (script.status === "error")
|
||
|
|
_cbs.error?.forEach((cb) => cb());
|
||
|
|
_cbs.loaded = null;
|
||
|
|
_cbs.error = null;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
const hookCtx = { script };
|
||
|
|
script.setupTriggerHandler(options.trigger);
|
||
|
|
if (options.use) {
|
||
|
|
const { proxy, resolve } = createScriptProxy(head.ssr ? {} : options.use() || {});
|
||
|
|
script.proxy = proxy;
|
||
|
|
script.onLoaded(resolve);
|
||
|
|
}
|
||
|
|
if (!options.warmupStrategy && (typeof options.trigger === "undefined" || options.trigger === "client")) {
|
||
|
|
options.warmupStrategy = "preload";
|
||
|
|
}
|
||
|
|
if (options.warmupStrategy) {
|
||
|
|
script.warmup(options.warmupStrategy);
|
||
|
|
}
|
||
|
|
scripts[id] = script;
|
||
|
|
return script;
|
||
|
|
}
|
||
|
|
|
||
|
|
export { resolveScriptKey as r, useScript as u };
|