(() => {
// 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