"ignore" and "once" attributes, improved performance

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2023-10-06 04:50:20 +07:00
parent f4d37ebf3a
commit 6074da9a52

View File

@ -1,181 +1,244 @@
"use strict"; "use strict";
if (typeof window.reinitializer !== "function") { if (typeof window.reinitializer !== "function") {
// Not initialized // Not initialized
// Initialize of the class in global namespace // Initialize of the class in global namespace
window.reinitializer = class reinitializer { window.reinitializer = class reinitializer {
/** /**
* Parent element for location <link> elements * Parent element for location <link> elements
*/ */
css = document.head; css = document.head;
/**
* Parent element for location <script> elements
*/
js = document.body;
/** /**
* Target element for searching new <link> and <script> elements * Parent element for location <script> elements
*/ */
root = document.body.getElementsByTagName("main")[0]; js = document.body;
/** /**
* Instance of the observer * Target element for searching new <link> and <script> elements
*/ */
observer = new MutationObserver(() => this.handle(this.root)); root = document.body.getElementsByTagName("main")[0];
/** /**
* Construct * Instance of the observer
* */
* @param {object} root Entry point observer = new MutationObserver(() => this.handle(this.root));
*/
constructor(root) {
// Initialize of the root element
this.root = root ?? this.root;
}
/** /**
* Reinitialize <link> and <script> elements * Construct
* *
* @return {bool} Processing status * @param {object} root Entry point
*/ */
handle() { constructor(root) {
// Check for a dublicate execute launch // Initialize of the root element
if (this.started) return false; this.root = root ?? this.root;
}
// Initialization an observation status /**
this.started = true; * Reinitialize <link> and <script> elements
*
* @return {bool} Processing status
*/
handle() {
// Check for a dublicate execute launch
if (this.started) return false;
for ( // Initialization an observation status
let links; this.started = true;
(links = this.root.getElementsByTagName("link")).length > 0;
) {
// Enumeration <link> elements
// Initialization of the <link> element for (
const link = links[0]; let links;
(links = this.root.getElementsByTagName("link")).length > 0;
) {
// Enumeration <link> elements
// Initialization link of the <link> element // Initialization of the <link> element
const href = link.getAttribute("href"); const link = links[0];
// Stop listening if (link.getAttribute("data-reinitializer-ignore") === "true") {
this.stop(); // Marked as ignored
// Delete outdated <link> element from the document // Move element
link.remove(); this.css.appendChild(link);
// Start listening continue;
this.start(); }
// Deleting outdated elements // Initialization link of the <link> element
for ( const href = link.getAttribute("href");
const element of this.css.querySelectorAll(
`script[href="${href}"]`,
)
) element.remove();
// Initialization of new <link> element if (
const element = document.createElement("link"); link.getAttribute("data-reinitializer-once") === "true" &&
element.setAttribute("href", href); this.css.querySelector(`:scope > link[href="${href}"]`)
element.setAttribute("rel", "stylesheet"); ) {
// Marked as executing once and already executed
// Write new element // Stop listening
this.css.appendChild(element); this.stop();
}
for ( // Delete outdated <link> element from the document
let scripts; link.remove();
(scripts = this.root.getElementsByTagName("script")).length > 0;
) {
// Enumeration of <script> elements
// Initialization of the <script> element // Start listening
const script = scripts[0]; this.start();
// Initialization link of the <script> element continue;
const src = script.getAttribute("src"); }
// Initialization text of the <script> element // Initialization outerHTML of the <script> element
const text = script.textContent; const html = script.outerHTML;
// Stop listening // Stop listening
this.stop(); this.stop();
// Delete outdated <script> element from the document // Delete outdated <link> element from the document
script.remove(); link.remove();
// Start listening // Start listening
this.start(); this.start();
// Initialization of new <script> element // Deleting outdated elements
const element = document.createElement("script"); for (const element of this.css.querySelectorAll(
`:scope > link[href="${href}"]`
)) element.remove();
if (typeof src === "string") { // Initialization of new <link> element
// File const element = document.createElement("link");
element.setAttribute("href", href);
element.setAttribute("rel", "stylesheet");
// Deleting outdated elements // Write new element
for ( this.css.appendChild(element);
const element of this.js.querySelectorAll( }
`script[src="${src}"]`,
)
) element.remove();
// Copy link from outdated <script> element for (
element.setAttribute("src", src); let scripts;
} else { (scripts = this.root.getElementsByTagName("script")).length > 0;
// Script ) {
// Enumeration of <script> elements
// Deleting outdated elements // Initialization of the <script> element
for ( const script = scripts[0];
const element of Array.from(
this.js.getElementsByTagName("script"),
)
.filter((e) => e.textContent === text)
) {
element.remove();
}
// Copy text from outdated <script> element if (script.getAttribute("data-reinitializer-ignore") === "true") {
element.textContent = text; // Marked as ignored
}
// Write new <script> element to end of <body> element // Move element
this.js.appendChild(element); this.js.appendChild(script);
}
// Initialize of observation status continue;
this.started = false; }
// Return (success) // Initialization link of the <script> element
return true; const src = script.getAttribute("src");
}
/** // Initialization text of the <script> element
* Start observation const text = script.textContent;
*
* @return {void}
*/
start() {
this.observer.observe(this.root, {
childList: true,
});
}
/** if (
* Stop observation script.getAttribute("data-reinitializer-once") === "true" &&
* (this.js.querySelector(`:scope > script[src="${src}"]`) ||
* @return {void} Array.from(this.js.querySelectorAll(`:scope > script`)).filter(
*/ (e) => e.textContent === text
stop() { ).length > 0)
this.observer.disconnect(); ) {
} // Marked as executing once and already executed
};
// Stop listening
this.stop();
// Delete outdated <script> element from the document
script.remove();
// Start listening
this.start();
continue;
}
// Initialization outerHTML of the <script> element
const html = script.outerHTML;
// Stop listening
this.stop();
// Delete outdated <script> element from the document
script.remove();
// Start listening
this.start();
// Initialization of new <script> element
const element = document.createElement("script");
if (typeof src === "string") {
// File
// Deleting outdated elements
for (const element of this.js.querySelectorAll(
`:scope > script[src="${src}"]`
))
element.remove();
// Copy link from outdated <script> element
element.setAttribute("src", src);
// Write a type of <script> element
element.setAttribute('type', 'text/javascript');
} else {
// Script
// Deleting outdated elements
for (const element of Array.from(
this.js.querySelectorAll(`:scope > script`)
).filter((e) => e.textContent === text)) {
element.remove();
}
// Copy text from outdated <script> element
element.textContent = text;
}
// Write the new <script> element to end of <body> element
this.js.appendChild(element);
// Write content to the new <script> element
element.outerHTML = html;
}
// Initialize of observation status
this.started = false;
// Return (success)
return true;
}
/**
* Start observation
*
* @return {void}
*/
start() {
this.observer.observe(this.root, {
childList: true,
});
}
/**
* Stop observation
*
* @return {void}
*/
stop() {
this.observer.disconnect();
}
};
} }
// Вызов события: "инициализировано" // Вызов события: "инициализировано"
document.dispatchEvent( document.dispatchEvent(
new CustomEvent("reinitializer.initialized", { new CustomEvent("reinitializer.initialized", {
detail: { reinitializer: window.reinitializer }, detail: { reinitializer: window.reinitializer },
}), })
); );