267 lines
8.0 KiB
JavaScript
267 lines
8.0 KiB
JavaScript
import { b as ScriptNetworkEvents } from './unhead.yem5I2v_.mjs';
|
|
|
|
function createNoopedRecordingProxy(instance = {}) {
|
|
const stack = [];
|
|
let stackIdx = -1;
|
|
const handler = (reuseStack = false) => ({
|
|
get(_, prop, receiver) {
|
|
if (!reuseStack) {
|
|
const v = Reflect.get(_, prop, receiver);
|
|
if (typeof v !== "undefined") {
|
|
return v;
|
|
}
|
|
stackIdx++;
|
|
stack[stackIdx] = [];
|
|
}
|
|
stack[stackIdx].push({ type: "get", key: prop });
|
|
return new Proxy(() => {
|
|
}, handler(true));
|
|
},
|
|
apply(_, __, args) {
|
|
stack[stackIdx].push({ type: "apply", key: "", args });
|
|
return void 0;
|
|
}
|
|
});
|
|
return {
|
|
proxy: new Proxy(instance || {}, handler()),
|
|
stack
|
|
};
|
|
}
|
|
function createForwardingProxy(target) {
|
|
const handler = {
|
|
get(_, prop, receiver) {
|
|
const v = Reflect.get(_, prop, receiver);
|
|
if (typeof v === "object") {
|
|
return new Proxy(v, handler);
|
|
}
|
|
return v;
|
|
},
|
|
apply(_, __, args) {
|
|
Reflect.apply(_, __, args);
|
|
return void 0;
|
|
}
|
|
};
|
|
return new Proxy(target, handler);
|
|
}
|
|
function replayProxyRecordings(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 = context.call(prevContext, ...args);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
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 prevScript = head._scripts?.[id];
|
|
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) => {
|
|
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;
|
|
}
|
|
if (options2?.key) {
|
|
const key2 = `${options2?.key}:${options2.key}`;
|
|
if (_uniqueCbs.has(key2)) {
|
|
return;
|
|
}
|
|
_uniqueCbs.add(key2);
|
|
}
|
|
if (_cbs[key]) {
|
|
const i = _cbs[key].push(cb);
|
|
return () => _cbs[key]?.splice(i - 1, 1);
|
|
}
|
|
cb(script.instance);
|
|
return () => {
|
|
};
|
|
};
|
|
const loadPromise = new Promise((resolve) => {
|
|
if (head.ssr)
|
|
return;
|
|
const emit = (api) => requestAnimationFrame(() => resolve(api));
|
|
const _ = head.hooks.hook("script:updated", ({ script: script2 }) => {
|
|
const status = script2.status;
|
|
if (script2.id === id && (status === "loaded" || status === "error")) {
|
|
if (status === "loaded") {
|
|
if (typeof options.use === "function") {
|
|
const api = options.use();
|
|
if (api) {
|
|
emit(api);
|
|
}
|
|
} else {
|
|
emit({});
|
|
}
|
|
} else if (status === "error") {
|
|
resolve(false);
|
|
}
|
|
_();
|
|
}
|
|
});
|
|
});
|
|
const script = {
|
|
_loadPromise: loadPromise,
|
|
instance: !head.ssr && options?.use?.() || null,
|
|
proxy: null,
|
|
id,
|
|
status: "awaitingLoad",
|
|
remove() {
|
|
script._triggerAbortController?.abort();
|
|
script._triggerPromises = [];
|
|
script._warmupEl?.dispose();
|
|
if (script.entry) {
|
|
script.entry.dispose();
|
|
script.entry = void 0;
|
|
syncStatus("removed");
|
|
delete head._scripts?.[id];
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
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) {
|
|
script._triggerAbortController?.abort();
|
|
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;
|
|
}
|
|
if (!script._triggerAbortController) {
|
|
script._triggerAbortController = new AbortController();
|
|
script._triggerAbortPromise = new Promise((resolve) => {
|
|
script._triggerAbortController.signal.addEventListener("abort", () => {
|
|
script._triggerAbortController = null;
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
script._triggerPromises = script._triggerPromises || [];
|
|
const idx = script._triggerPromises.push(Promise.race([
|
|
trigger.then((v) => typeof v === "undefined" || v ? script.load : void 0),
|
|
script._triggerAbortPromise
|
|
]).catch(() => {
|
|
}).then((res) => {
|
|
res?.();
|
|
}).finally(() => {
|
|
script._triggerPromises?.splice(idx, 1);
|
|
}));
|
|
} 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 {
|
|
_cbs.error?.forEach((cb) => cb());
|
|
_cbs.error = null;
|
|
}
|
|
});
|
|
const hookCtx = { script };
|
|
script.setupTriggerHandler(options.trigger);
|
|
if (options.use) {
|
|
const { proxy, stack } = createNoopedRecordingProxy(head.ssr ? {} : options.use() || {});
|
|
script.proxy = proxy;
|
|
script.onLoaded((instance) => {
|
|
replayProxyRecordings(instance, stack);
|
|
script.proxy = createForwardingProxy(instance);
|
|
});
|
|
}
|
|
if (!options.warmupStrategy && (typeof options.trigger === "undefined" || options.trigger === "client")) {
|
|
options.warmupStrategy = "preload";
|
|
}
|
|
if (options.warmupStrategy) {
|
|
script.warmup(options.warmupStrategy);
|
|
}
|
|
head._scripts = Object.assign(head._scripts || {}, { [id]: script });
|
|
return script;
|
|
}
|
|
|
|
export { resolveScriptKey as r, useScript as u };
|