(() => { // src/constant.ts var DIRNAME__CSSI = "cssi"; var FILENAME__CSSI_ENTRY_JSON = "entry"; // src/cssi.ts var ManagedElement = { /** `script` element */ SCRIPT: "script", /** `style` element */ STYLE: "style", /** `link` element */ LINK: "link", /** `noscript` element */ NO_SCRIPT: "noscript" }; var ManagedScriptType = { /** External script */ EXTERNAL: "script:external", /** Inline script */ INLINE: "script:inline" }; var ManagedLinkType = { /** External style */ EXTERNAL: "style:external", /** Inline style */ INLINE: "style:inline" }; var TriggerType = { /** Trigger before loading the document */ BEFORE_LOAD: "beforeload", /** Trigger after loading the document */ AFTER_LOAD: "afterload" }; var DELAY_TIME__AFTER_LOAD_SCRIPTS = 30; var injectNoScript = (managedScript) => { const { html } = managedScript; const noscript = document.createElement("noscript"); noscript.insertAdjacentHTML("beforeend", html); if (document.body) { document.body.insertAdjacentElement("afterbegin", noscript); } else { document.addEventListener("DOMContentLoaded", () => { document.body.insertAdjacentElement("afterbegin", noscript); }); } }; function createScriptElement(scriptWithFlags) { const { type } = scriptWithFlags; const scriptElement = document.createElement("script"); if (type === ManagedScriptType.INLINE && scriptWithFlags.content) { if (scriptWithFlags.scriptType) { scriptElement.type = scriptWithFlags.scriptType; } scriptElement.textContent = scriptWithFlags.content; } else if (type === ManagedScriptType.EXTERNAL && scriptWithFlags.attr) { const { attr: { async, defer, src } } = scriptWithFlags; scriptElement.src = src; if (async) { scriptElement.async = true; } else if (defer) { scriptElement.defer = true; } for (const [key, value] of Object.entries(scriptWithFlags.attr)) { if (key !== "src" && key !== "async" && key !== "defer") { if (typeof value === "string") { scriptElement.setAttribute(key, value); } else if (typeof value === "boolean" && value) { scriptElement.setAttribute(key, ""); } } } } return scriptElement; } function createLinkElement(linkWithFlags) { const linkElement = document.createElement("link"); const { attr: { href, media, rel } } = linkWithFlags; linkElement.href = href; linkElement.rel = rel; if (media) { linkElement.media = media; } return linkElement; } function createStyleElement(styleWithFlags) { const { content } = styleWithFlags; const styleElement = document.createElement("style"); styleElement.textContent = content; return styleElement; } function pickManagedScriptUrl(entryJson, pathname) { for (const entry of entryJson) { if (isPathGroup(toPathGroup(entry.patterns), pathname)) { return entry.url; } } return entryJson[0].url; } var isValidRegExp = (value) => { try { new RegExp(value); return true; } catch (e) { console.error("Invalid regular expression:", e); return false; } }; var toPathGroup = (patterns) => { return patterns.reduce((acc, path) => { if (typeof path === "string") { acc.push(path); } else if (path instanceof Object) { if (path.type === "regexp") { if (isValidRegExp(path.value)) { acc.push(new RegExp(path.value)); } else { throw new TypeError(`Invalid regular expression: ${path.value}`); } } else if (path.type === "string") { acc.push(path.value); } } return acc; }, []); }; var isPathGroup = (groups, path) => { const isExcluded = groups.some((group) => { if (typeof group === "string" && group.startsWith("!")) { const excludePattern = group.slice(1); return path.startsWith(excludePattern); } return false; }); if (isExcluded) { return false; } return groups.some((group) => { if (group instanceof RegExp) { return group.test(path); } return path.startsWith(group); }); }; var loadSequentialScripts = (sequentialScripts, callbacks) => { const { onSequentialScriptLoaded, onError } = callbacks; const errors = []; return sequentialScripts.reduce((promiseChain, scriptConfig) => { return promiseChain.then(() => { return new Promise((resolve, _) => { try { const scriptElement = createScriptElement(scriptConfig); if (scriptConfig.type === ManagedScriptType.EXTERNAL) { scriptElement.onload = () => { console.log( `Script loaded: ${scriptConfig.attr.src}` ); if (onSequentialScriptLoaded) { onSequentialScriptLoaded(scriptConfig); } resolve(); }; scriptElement.onerror = () => { const err = new Error( `Failed to load script: ${scriptConfig.attr.src}` ); if (onError) { onError( err ); } errors.push(err); resolve(); }; } document.head.appendChild(scriptElement); if (scriptConfig.type === ManagedScriptType.INLINE) { resolve(); if (onSequentialScriptLoaded) onSequentialScriptLoaded(scriptConfig); } } catch (error) { errors.push(error); if (onError) onError(error); resolve(); } }); }); }, Promise.resolve()).then(() => { if (errors.length > 0) { console.error(errors); } }); }; var loadParallelScripts = async (parallelScripts, callbacks) => { const { onParallelScriptsLoaded, onError } = callbacks; const fragment = document.createDocumentFragment(); const promises = parallelScripts.map((scriptConfig) => { return new Promise((resolve, reject) => { const scriptElement = createScriptElement(scriptConfig); scriptElement.onload = () => { console.log(`Loaded script: ${scriptConfig.attr.src}`); resolve(); }; scriptElement.onerror = () => { console.error(`Failed to load script: ${scriptConfig.attr.src}`); reject(new Error(`Failed to load script: ${scriptConfig.attr.src}`)); }; fragment.appendChild(scriptElement); }); }); document.head.appendChild(fragment); try { await Promise.all(promises); if (onParallelScriptsLoaded) onParallelScriptsLoaded(parallelScripts); } catch (error) { if (onError) onError(error); } }; var loadSequentialStyles = (sequentialStyles, callbacks) => { const { onSequentialStyleLoaded, onError } = callbacks; const errors = []; return sequentialStyles.reduce((promiseChain, styleConfig) => { return promiseChain.then(() => { return new Promise((resolve, _) => { if (styleConfig.element === ManagedElement.LINK && styleConfig.type === ManagedLinkType.EXTERNAL) { const linkElement = createLinkElement(styleConfig); linkElement.onload = () => { console.log( `Link loaded: ${styleConfig.attr.href}` ); if (onSequentialStyleLoaded) onSequentialStyleLoaded(styleConfig); resolve(); }; linkElement.onerror = () => { const err = new Error(`Failed to load script: ${styleConfig.attr.href}`); errors.push(err); if (onError) onError(err); resolve(); }; document.head.appendChild(linkElement); } if (styleConfig.element === ManagedElement.STYLE && styleConfig.type === ManagedLinkType.INLINE) { const styleElement = createStyleElement(styleConfig); styleElement.textContent = styleConfig.content; document.head.appendChild(styleElement); resolve(); if (onSequentialStyleLoaded) onSequentialStyleLoaded(styleConfig); } }); }); }, Promise.resolve()).then(() => { if (errors.length > 0) { console.error(errors); } }); }; var loadParallelStyles = async (parallelStyles, callbacks) => { const { onParallelStylesLoaded, onError } = callbacks; const fragment = document.createDocumentFragment(); const promises = parallelStyles.map((styleConfig) => { return new Promise((resolve, reject) => { const linkElement = createLinkElement(styleConfig); linkElement.onload = () => { console.log(`Loaded script: ${styleConfig.attr.href}`); resolve(); }; linkElement.onerror = () => { console.error(`Failed to load style: ${styleConfig.attr.href}`); reject(new Error(`Failed to load script: ${styleConfig.attr.href}`)); }; fragment.appendChild(linkElement); }); }); document.head.appendChild(fragment); try { await Promise.all(promises); if (onParallelStylesLoaded) onParallelStylesLoaded(parallelStyles); } catch (error) { if (onError) onError(error); } }; async function loadScripts(scriptsWithFlags, scriptsCallback = {}) { const beforeloadSequentialScripts = []; const afterloadSequentialScripts = []; const sequentialExternalScripts = []; const parallelScripts = []; const sequentialStyles = []; const parallelStyles = []; const noScripts = []; for (const script of scriptsWithFlags) { if (script.element === ManagedElement.NO_SCRIPT) { noScripts.push(script); continue; } if (script.element === ManagedElement.SCRIPT) { if (script.seq && script.type === ManagedScriptType.EXTERNAL) { sequentialExternalScripts.push(script); } else if (script.type === ManagedScriptType.INLINE) { if (script.trigger === TriggerType.BEFORE_LOAD) { beforeloadSequentialScripts.push(script); } else if (script.trigger === TriggerType.AFTER_LOAD) { afterloadSequentialScripts.push(script); } } else { parallelScripts.push(script); } } if (script.element === ManagedElement.LINK) { if (script.seq) { sequentialStyles.push(script); } else { parallelStyles.push(script); } } if (script.element === ManagedElement.STYLE) { sequentialStyles.push(script); } } if (beforeloadSequentialScripts.length > 0) { await loadSequentialScripts(beforeloadSequentialScripts, scriptsCallback); } if (parallelScripts.length > 0) { await loadParallelScripts(parallelScripts, { ...scriptsCallback, onParallelScriptsLoaded: (parallelScripts2) => { if (afterloadSequentialScripts.length > 0) { setTimeout(async () => { await loadSequentialScripts(afterloadSequentialScripts, scriptsCallback); }, DELAY_TIME__AFTER_LOAD_SCRIPTS); } if (scriptsCallback.onParallelScriptsLoaded) { scriptsCallback.onParallelScriptsLoaded(parallelScripts2); } }, onError: (error) => { if (afterloadSequentialScripts.length > 0) { (async () => { await loadSequentialScripts(afterloadSequentialScripts, scriptsCallback); })(); } if (scriptsCallback.onError) { scriptsCallback.onError(error); } } }); } else if (afterloadSequentialScripts.length > 0) { await loadSequentialScripts(afterloadSequentialScripts, scriptsCallback); } if (sequentialExternalScripts.length > 0) { await loadSequentialScripts(sequentialExternalScripts, scriptsCallback); } if (parallelStyles.length > 0) { await loadParallelStyles(parallelStyles, scriptsCallback); } if (sequentialStyles.length > 0) { await loadSequentialStyles(sequentialStyles, scriptsCallback); } if (noScripts.length > 0) { noScripts.forEach(injectNoScript); } } function isManagedHtml(value) { if (typeof value !== "object" || !value) { return false; } return "parent" in value && "html" in value && "position" in value; } function insertAdjacentHtml(managedHtmls) { managedHtmls.forEach((managedHtml) => { try { const { parent, html, position } = managedHtml; const parentElement = document.querySelector(parent); if (parentElement) { parentElement.insertAdjacentHTML(position, html); } else { throw new Error(`Parent element not found: ${parent}`); } } catch (error) { console.error(error); } }); } function filterManagedJson(arr) { return arr.reduce( (acc, item) => { if (isManagedHtml(item)) { acc.managedHtmls.push(item); } else { acc.managedScripts.push(item); } return acc; }, { managedScripts: [], managedHtmls: [] } ); } // src/run-cssi.ts try { (async () => { const state = { domReady: false }; document.addEventListener("DOMContentLoaded", () => { console.log("cssi-js: DOMContentLoaded"); state.domReady = true; }); const entryJson = await fetch(`/${DIRNAME__CSSI}/${FILENAME__CSSI_ENTRY_JSON}.json`); const entries = await entryJson.json(); const url = pickManagedScriptUrl(entries, globalThis.location.pathname); const res = await fetch(url); const managedJson = await res.json(); console.info("cssi-js: configuration loaded"); const { managedScripts, managedHtmls } = filterManagedJson(managedJson); if (state.domReady) { insertAdjacentHtml(managedHtmls); } else { document.addEventListener("DOMContentLoaded", () => { console.info("cssi-js: DOMContentLoaded"); state.domReady = true; insertAdjacentHtml(managedHtmls); }); } loadScripts(managedScripts, { onParallelScriptsLoaded: (parallelScripts) => { console.log("All parallel scripts loaded", parallelScripts); }, onSequentialScriptLoaded: (scriptConfig) => { console.log("Sequential script loaded", scriptConfig); }, onParallelStylesLoaded: (parallelStyles) => { console.log("All parallel styles loaded", parallelStyles); }, onSequentialStyleLoaded: (styleConfig) => { console.log("Sequential style loaded", styleConfig); }, onError: (error) => { console.error("Failed to load scripts", error); } }); })(); } catch (e) { console.error("Error loading scripts", e); } })(); //# sourceMappingURL=cssi.js.map