Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@hyperse/html-webpack-plugin-loader

Package Overview
Dependencies
Maintainers
2
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@hyperse/html-webpack-plugin-loader - npm Package Compare versions

Comparing version
1.0.6
to
1.0.7
+224
-88
dist/index.cjs

@@ -22,2 +22,185 @@ 'use strict';

};
var sortDocument = (document) => {
const sortedDocument = { ...document };
const html = document.childNodes.find(
(node) => node.nodeName === "html"
);
if (!html) {
return sortedDocument;
}
const head = html.childNodes.find(
(node) => node.nodeName === "head"
);
const body = html.childNodes.find(
(node) => node.nodeName === "body"
);
if (head) {
sortNodesByPosition(head, ["link", "style"], (node) => {
const elementNode = node;
const hasId = elementNode.attrs?.find((attr) => attr.name === "id");
const hasOrder = elementNode.attrs?.find(
(attr) => attr.name === "data-order"
);
const hasPosition = elementNode.attrs?.find(
(attr) => attr.name === "data-position"
);
if (!hasId || !hasOrder || !hasPosition) {
return false;
}
if (node.nodeName === "link") {
const isStylesheet = elementNode.attrs?.find(
(attr) => attr.name === "rel" && attr.value === "stylesheet"
);
return !!isStylesheet;
}
return true;
});
sortNodesByPosition(head, ["script"], (node) => {
const elementNode = node;
const hasId = elementNode.attrs?.find((attr) => attr.name === "id");
const hasOrder = elementNode.attrs?.find(
(attr) => attr.name === "data-order"
);
const hasPosition = elementNode.attrs?.find(
(attr) => attr.name === "data-position"
);
return !!(hasId && hasOrder && hasPosition);
});
cleanupSortingAttributes(head);
}
if (body) {
sortNodesByPosition(body, ["script"], (node) => {
const elementNode = node;
const hasId = elementNode.attrs?.find((attr) => attr.name === "id");
const hasOrder = elementNode.attrs?.find(
(attr) => attr.name === "data-order"
);
const hasPosition = elementNode.attrs?.find(
(attr) => attr.name === "data-position"
);
return !!(hasId && hasOrder && hasPosition);
});
cleanupSortingAttributes(body);
}
return sortedDocument;
};
var sortNodesByPosition = (element, nodeNames, filterFn) => {
const beginningNodes = [];
const endNodes = [];
element.childNodes.forEach((node) => {
if (nodeNames.includes(node.nodeName) && filterFn(node)) {
const elementNode = node;
const orderAttr = elementNode.attrs?.find(
(attr) => attr.name === "data-order"
);
const positionAttr = elementNode.attrs?.find(
(attr) => attr.name === "data-position"
);
if (orderAttr && positionAttr) {
const order = parseInt(orderAttr.value, 10) || 0;
const nodeInfo = { node: elementNode, order };
if (positionAttr.value === "beginning") {
beginningNodes.push(nodeInfo);
} else if (positionAttr.value === "end") {
endNodes.push(nodeInfo);
}
}
}
});
[...beginningNodes, ...endNodes].forEach(({ node }) => {
const index = element.childNodes.indexOf(node);
if (index > -1) {
element.childNodes.splice(index, 1);
}
});
beginningNodes.sort((a, b) => a.order - b.order);
endNodes.sort((a, b) => a.order - b.order);
beginningNodes.reverse().forEach(({ node }) => {
element.childNodes.unshift(node);
});
endNodes.forEach(({ node }) => {
element.childNodes.push(node);
});
};
var cleanupSortingAttributes = (element) => {
if (element.attrs) {
element.attrs = element.attrs.filter(
(attr) => attr.name !== "data-order" && attr.name !== "data-position"
);
}
element.childNodes.forEach((node) => {
if (node.nodeName && node.nodeName !== "#text" && node.nodeName !== "#comment") {
const childElement = node;
cleanupSortingAttributes(childElement);
}
});
};
var upsertScripts = (element, scripts) => {
const sortedScripts = [...scripts].sort(
(a, b) => (a.order ?? 0) - (b.order ?? 0)
);
sortedScripts.forEach((script) => {
const existingScriptIndex = element.childNodes.findIndex(
(node) => node.nodeName === "script" && node.attrs?.find(
(attr) => attr.name === "id" && attr.value === script.id
)
);
if (existingScriptIndex > -1) {
element.childNodes.splice(existingScriptIndex, 1);
}
});
const scriptTags = sortedScripts.map((script) => {
const scriptNode = parse5.parseFragment(
`<script id="${script.id}" src="${script.src}" data-order="${script.order}" data-position="${script.position}"></script>`
).childNodes[0];
if (script.type) {
scriptNode.attrs?.push({ name: "type", value: script.type });
}
if (script.async) {
scriptNode.attrs?.push({ name: "async", value: "true" });
}
if (script.defer) {
scriptNode.attrs?.push({ name: "defer", value: "true" });
}
if (script.crossOrigin) {
scriptNode.attrs?.push({
name: "crossorigin",
value: script.crossOrigin
});
}
if (script.integrity) {
scriptNode.attrs?.push({ name: "integrity", value: script.integrity });
}
if (script.nonce) {
scriptNode.attrs?.push({ name: "nonce", value: script.nonce });
}
return scriptNode;
});
const beginningScripts = sortedScripts.reduce((acc, script, index) => {
if (script.position === "beginning") {
acc.push(scriptTags[index]);
}
return acc;
}, []);
const endScripts = sortedScripts.reduce(
(acc, script, index) => {
if (script.position === "end") {
acc.push(scriptTags[index]);
}
return acc;
},
[]
);
beginningScripts.reverse().forEach((scriptNode) => {
element.childNodes.unshift(scriptNode);
});
endScripts.forEach((scriptNode) => {
element.childNodes.push(scriptNode);
});
};
// src/parser/upsertBodySctipts.ts
var upsertBodySctipts = (body, scripts) => {
return upsertScripts(body, scripts);
};
var upsertFavicon = (head, href, rel = "icon", attributes = {}) => {

@@ -36,3 +219,15 @@ const existingLinkIndex = head.childNodes.findIndex(

).childNodes[0];
head.childNodes.push(linkNode);
let insertIndex = 0;
let lastMetaIndex = -1;
for (let i = 0; i < head.childNodes.length; i++) {
if (head.childNodes[i].nodeName === "meta") {
lastMetaIndex = i;
}
}
if (lastMetaIndex >= 0) {
insertIndex = lastMetaIndex + 1;
} else {
insertIndex = 0;
}
head.childNodes.splice(insertIndex, 0, linkNode);
};

@@ -55,3 +250,3 @@ var upsertHeadInlineScripts = (head, scripts) => {

const scriptNode = parse5.parseFragment(
`<script id="${script.id}">${script.content}</script>`
`<script id="${script.id}" data-order="${script.order}" data-position="${script.position}">${script.content}</script>`
).childNodes[0];

@@ -98,3 +293,3 @@ return scriptNode;

const styleNode = parse5.parseFragment(
`<style id="${style.id}">${style.content}</style>`
`<style id="${style.id}" data-order="${style.order}" data-position="${style.position}">${style.content}</style>`
).childNodes[0];

@@ -148,3 +343,5 @@ return styleNode;

);
head.childNodes.unshift(...parsedTags);
parsedTags.reverse().forEach((tag) => {
head.childNodes.unshift(tag);
});
};

@@ -169,3 +366,3 @@ var upsertHeadStyles = (head, styles) => {

const styleNode = parse5.parseFragment(
`<link rel="stylesheet" href="${style.href}" id="${style.id}"></link>`
`<link rel="stylesheet" href="${style.href}" id="${style.id}" data-order="${style.order}" data-position="${style.position}"></link>`
).childNodes[0];

@@ -196,65 +393,2 @@ return styleNode;

};
var upsertScripts = (element, scripts) => {
const sortedScripts = [...scripts].sort(
(a, b) => (a.order ?? 0) - (b.order ?? 0)
);
sortedScripts.forEach((script) => {
const existingScriptIndex = element.childNodes.findIndex(
(node) => node.nodeName === "script" && node.attrs?.find(
(attr) => attr.name === "id" && attr.value === script.id
)
);
if (existingScriptIndex > -1) {
element.childNodes.splice(existingScriptIndex, 1);
}
});
const scriptTags = sortedScripts.map((script) => {
const scriptNode = parse5.parseFragment(
`<script id="${script.id}" src="${script.src}"></script>`
).childNodes[0];
if (script.type) {
scriptNode.attrs?.push({ name: "type", value: script.type });
}
if (script.async) {
scriptNode.attrs?.push({ name: "async", value: "true" });
}
if (script.defer) {
scriptNode.attrs?.push({ name: "defer", value: "true" });
}
if (script.crossOrigin) {
scriptNode.attrs?.push({
name: "crossorigin",
value: script.crossOrigin
});
}
if (script.integrity) {
scriptNode.attrs?.push({ name: "integrity", value: script.integrity });
}
if (script.nonce) {
scriptNode.attrs?.push({ name: "nonce", value: script.nonce });
}
return scriptNode;
});
const beginningScripts = sortedScripts.reduce((acc, script, index) => {
if (script.position === "beginning") {
acc.push(scriptTags[index]);
}
return acc;
}, []);
const endScripts = sortedScripts.reduce(
(acc, script, index) => {
if (script.position === "end") {
acc.push(scriptTags[index]);
}
return acc;
},
[]
);
beginningScripts.reverse().forEach((scriptNode) => {
element.childNodes.unshift(scriptNode);
});
endScripts.forEach((scriptNode) => {
element.childNodes.push(scriptNode);
});
};
var upsertTitle = (head, title) => {

@@ -288,4 +422,5 @@ const titleNode = head.childNodes?.find(

constructor(htmlSource) {
const { document, head, body } = parseDocument(htmlSource);
const { document, head, body, html } = parseDocument(htmlSource);
this.document = document;
this.html = html;
this.head = head;

@@ -295,3 +430,3 @@ this.body = body;

/**
* Upsert the title tag
* Upsert the title tag - inserts at beginning of <head>
* @param title - The title to upsert

@@ -305,3 +440,3 @@ * @returns The TemplateParser instance

/**
* Upsert the favicon tag
* Upsert the favicon tag - inserts at beginning of <head> after title tag
* @param href - The favicon to upsert

@@ -317,4 +452,4 @@ * @param rel - The rel attribute of the favicon tag

/**
* Upsert the head before html tags
* @param tags - The tags to upsert
* Upsert meta tags in <head> - inserts at beginning for SEO priority
* @param tags - The meta tags to upsert
* @returns The TemplateParser instance

@@ -327,4 +462,4 @@ */

/**
* Upsert the head before styles
* @param styles - The styles to upsert
* Upsert external stylesheets in <head> - supports position-based insertion
* @param styles - The external styles to upsert
* @returns The TemplateParser instance

@@ -337,4 +472,4 @@ */

/**
* Upsert the head inline styles
* @param styles - The styles to upsert
* Upsert inline styles in <head> - supports position-based insertion
* @param styles - The inline styles to upsert
* @returns The TemplateParser instance

@@ -347,4 +482,4 @@ */

/**
* Upsert the head before scripts
* @param scripts - The scripts to upsert
* Upsert external scripts in <head> - supports position-based insertion
* @param scripts - The external scripts to upsert
* @returns The TemplateParser instance

@@ -357,4 +492,4 @@ */

/**
* Upsert the inline scripts
* @param scripts - The scripts to upsert
* Upsert inline scripts in <head> - supports position-based insertion
* @param scripts - The inline scripts to upsert
* @returns The TemplateParser instance

@@ -367,3 +502,3 @@ */

/**
* Upsert the body after scripts
* Upsert scripts in <body> - supports position-based insertion, typically at end for performance
* @param scripts - The scripts to upsert

@@ -373,3 +508,3 @@ * @returns The TemplateParser instance

upsertBodyScripts(scripts) {
upsertScripts(this.body, scripts);
upsertBodySctipts(this.body, scripts);
return this;

@@ -382,3 +517,4 @@ }

serialize() {
return parse5.serialize(this.document);
const sortedDocument = sortDocument(this.document);
return parse5.serialize(sortedDocument);
}

@@ -398,4 +534,4 @@ /**

const parser = new TemplateParser(htmlSource);
if (options.title) {
parser.upsertTitleTag(options.title);
if (options.headMetaTags?.length) {
parser.upsertHeadMetaTags(options.headMetaTags);
}

@@ -409,4 +545,4 @@ if (options.favicon) {

}
if (options.headMetaTags?.length) {
parser.upsertHeadMetaTags(options.headMetaTags);
if (options.title) {
parser.upsertTitleTag(options.title);
}

@@ -413,0 +549,0 @@ if (options.headStyles?.length) {

@@ -24,3 +24,3 @@ import { DefaultTreeAdapterTypes } from 'parse5';

*/
order?: number;
order: number;
};

@@ -140,5 +140,6 @@ type StyleItem = HtmlItemBase & {

protected readonly body: DefaultTreeAdapterTypes.Element;
protected readonly html: DefaultTreeAdapterTypes.Element;
constructor(htmlSource: string);
/**
* Upsert the title tag
* Upsert the title tag - inserts at beginning of <head>
* @param title - The title to upsert

@@ -149,3 +150,3 @@ * @returns The TemplateParser instance

/**
* Upsert the favicon tag
* Upsert the favicon tag - inserts at beginning of <head> after title tag
* @param href - The favicon to upsert

@@ -158,4 +159,4 @@ * @param rel - The rel attribute of the favicon tag

/**
* Upsert the head before html tags
* @param tags - The tags to upsert
* Upsert meta tags in <head> - inserts at beginning for SEO priority
* @param tags - The meta tags to upsert
* @returns The TemplateParser instance

@@ -165,4 +166,4 @@ */

/**
* Upsert the head before styles
* @param styles - The styles to upsert
* Upsert external stylesheets in <head> - supports position-based insertion
* @param styles - The external styles to upsert
* @returns The TemplateParser instance

@@ -172,4 +173,4 @@ */

/**
* Upsert the head inline styles
* @param styles - The styles to upsert
* Upsert inline styles in <head> - supports position-based insertion
* @param styles - The inline styles to upsert
* @returns The TemplateParser instance

@@ -179,4 +180,4 @@ */

/**
* Upsert the head before scripts
* @param scripts - The scripts to upsert
* Upsert external scripts in <head> - supports position-based insertion
* @param scripts - The external scripts to upsert
* @returns The TemplateParser instance

@@ -186,4 +187,4 @@ */

/**
* Upsert the inline scripts
* @param scripts - The scripts to upsert
* Upsert inline scripts in <head> - supports position-based insertion
* @param scripts - The inline scripts to upsert
* @returns The TemplateParser instance

@@ -193,3 +194,3 @@ */

/**
* Upsert the body after scripts
* Upsert scripts in <body> - supports position-based insertion, typically at end for performance
* @param scripts - The scripts to upsert

@@ -196,0 +197,0 @@ * @returns The TemplateParser instance

@@ -24,3 +24,3 @@ import { DefaultTreeAdapterTypes } from 'parse5';

*/
order?: number;
order: number;
};

@@ -140,5 +140,6 @@ type StyleItem = HtmlItemBase & {

protected readonly body: DefaultTreeAdapterTypes.Element;
protected readonly html: DefaultTreeAdapterTypes.Element;
constructor(htmlSource: string);
/**
* Upsert the title tag
* Upsert the title tag - inserts at beginning of <head>
* @param title - The title to upsert

@@ -149,3 +150,3 @@ * @returns The TemplateParser instance

/**
* Upsert the favicon tag
* Upsert the favicon tag - inserts at beginning of <head> after title tag
* @param href - The favicon to upsert

@@ -158,4 +159,4 @@ * @param rel - The rel attribute of the favicon tag

/**
* Upsert the head before html tags
* @param tags - The tags to upsert
* Upsert meta tags in <head> - inserts at beginning for SEO priority
* @param tags - The meta tags to upsert
* @returns The TemplateParser instance

@@ -165,4 +166,4 @@ */

/**
* Upsert the head before styles
* @param styles - The styles to upsert
* Upsert external stylesheets in <head> - supports position-based insertion
* @param styles - The external styles to upsert
* @returns The TemplateParser instance

@@ -172,4 +173,4 @@ */

/**
* Upsert the head inline styles
* @param styles - The styles to upsert
* Upsert inline styles in <head> - supports position-based insertion
* @param styles - The inline styles to upsert
* @returns The TemplateParser instance

@@ -179,4 +180,4 @@ */

/**
* Upsert the head before scripts
* @param scripts - The scripts to upsert
* Upsert external scripts in <head> - supports position-based insertion
* @param scripts - The external scripts to upsert
* @returns The TemplateParser instance

@@ -186,4 +187,4 @@ */

/**
* Upsert the inline scripts
* @param scripts - The scripts to upsert
* Upsert inline scripts in <head> - supports position-based insertion
* @param scripts - The inline scripts to upsert
* @returns The TemplateParser instance

@@ -193,3 +194,3 @@ */

/**
* Upsert the body after scripts
* Upsert scripts in <body> - supports position-based insertion, typically at end for performance
* @param scripts - The scripts to upsert

@@ -196,0 +197,0 @@ * @returns The TemplateParser instance

@@ -20,2 +20,185 @@ import { serialize, parseFragment, parse } from 'parse5';

};
var sortDocument = (document) => {
const sortedDocument = { ...document };
const html = document.childNodes.find(
(node) => node.nodeName === "html"
);
if (!html) {
return sortedDocument;
}
const head = html.childNodes.find(
(node) => node.nodeName === "head"
);
const body = html.childNodes.find(
(node) => node.nodeName === "body"
);
if (head) {
sortNodesByPosition(head, ["link", "style"], (node) => {
const elementNode = node;
const hasId = elementNode.attrs?.find((attr) => attr.name === "id");
const hasOrder = elementNode.attrs?.find(
(attr) => attr.name === "data-order"
);
const hasPosition = elementNode.attrs?.find(
(attr) => attr.name === "data-position"
);
if (!hasId || !hasOrder || !hasPosition) {
return false;
}
if (node.nodeName === "link") {
const isStylesheet = elementNode.attrs?.find(
(attr) => attr.name === "rel" && attr.value === "stylesheet"
);
return !!isStylesheet;
}
return true;
});
sortNodesByPosition(head, ["script"], (node) => {
const elementNode = node;
const hasId = elementNode.attrs?.find((attr) => attr.name === "id");
const hasOrder = elementNode.attrs?.find(
(attr) => attr.name === "data-order"
);
const hasPosition = elementNode.attrs?.find(
(attr) => attr.name === "data-position"
);
return !!(hasId && hasOrder && hasPosition);
});
cleanupSortingAttributes(head);
}
if (body) {
sortNodesByPosition(body, ["script"], (node) => {
const elementNode = node;
const hasId = elementNode.attrs?.find((attr) => attr.name === "id");
const hasOrder = elementNode.attrs?.find(
(attr) => attr.name === "data-order"
);
const hasPosition = elementNode.attrs?.find(
(attr) => attr.name === "data-position"
);
return !!(hasId && hasOrder && hasPosition);
});
cleanupSortingAttributes(body);
}
return sortedDocument;
};
var sortNodesByPosition = (element, nodeNames, filterFn) => {
const beginningNodes = [];
const endNodes = [];
element.childNodes.forEach((node) => {
if (nodeNames.includes(node.nodeName) && filterFn(node)) {
const elementNode = node;
const orderAttr = elementNode.attrs?.find(
(attr) => attr.name === "data-order"
);
const positionAttr = elementNode.attrs?.find(
(attr) => attr.name === "data-position"
);
if (orderAttr && positionAttr) {
const order = parseInt(orderAttr.value, 10) || 0;
const nodeInfo = { node: elementNode, order };
if (positionAttr.value === "beginning") {
beginningNodes.push(nodeInfo);
} else if (positionAttr.value === "end") {
endNodes.push(nodeInfo);
}
}
}
});
[...beginningNodes, ...endNodes].forEach(({ node }) => {
const index = element.childNodes.indexOf(node);
if (index > -1) {
element.childNodes.splice(index, 1);
}
});
beginningNodes.sort((a, b) => a.order - b.order);
endNodes.sort((a, b) => a.order - b.order);
beginningNodes.reverse().forEach(({ node }) => {
element.childNodes.unshift(node);
});
endNodes.forEach(({ node }) => {
element.childNodes.push(node);
});
};
var cleanupSortingAttributes = (element) => {
if (element.attrs) {
element.attrs = element.attrs.filter(
(attr) => attr.name !== "data-order" && attr.name !== "data-position"
);
}
element.childNodes.forEach((node) => {
if (node.nodeName && node.nodeName !== "#text" && node.nodeName !== "#comment") {
const childElement = node;
cleanupSortingAttributes(childElement);
}
});
};
var upsertScripts = (element, scripts) => {
const sortedScripts = [...scripts].sort(
(a, b) => (a.order ?? 0) - (b.order ?? 0)
);
sortedScripts.forEach((script) => {
const existingScriptIndex = element.childNodes.findIndex(
(node) => node.nodeName === "script" && node.attrs?.find(
(attr) => attr.name === "id" && attr.value === script.id
)
);
if (existingScriptIndex > -1) {
element.childNodes.splice(existingScriptIndex, 1);
}
});
const scriptTags = sortedScripts.map((script) => {
const scriptNode = parseFragment(
`<script id="${script.id}" src="${script.src}" data-order="${script.order}" data-position="${script.position}"></script>`
).childNodes[0];
if (script.type) {
scriptNode.attrs?.push({ name: "type", value: script.type });
}
if (script.async) {
scriptNode.attrs?.push({ name: "async", value: "true" });
}
if (script.defer) {
scriptNode.attrs?.push({ name: "defer", value: "true" });
}
if (script.crossOrigin) {
scriptNode.attrs?.push({
name: "crossorigin",
value: script.crossOrigin
});
}
if (script.integrity) {
scriptNode.attrs?.push({ name: "integrity", value: script.integrity });
}
if (script.nonce) {
scriptNode.attrs?.push({ name: "nonce", value: script.nonce });
}
return scriptNode;
});
const beginningScripts = sortedScripts.reduce((acc, script, index) => {
if (script.position === "beginning") {
acc.push(scriptTags[index]);
}
return acc;
}, []);
const endScripts = sortedScripts.reduce(
(acc, script, index) => {
if (script.position === "end") {
acc.push(scriptTags[index]);
}
return acc;
},
[]
);
beginningScripts.reverse().forEach((scriptNode) => {
element.childNodes.unshift(scriptNode);
});
endScripts.forEach((scriptNode) => {
element.childNodes.push(scriptNode);
});
};
// src/parser/upsertBodySctipts.ts
var upsertBodySctipts = (body, scripts) => {
return upsertScripts(body, scripts);
};
var upsertFavicon = (head, href, rel = "icon", attributes = {}) => {

@@ -34,3 +217,15 @@ const existingLinkIndex = head.childNodes.findIndex(

).childNodes[0];
head.childNodes.push(linkNode);
let insertIndex = 0;
let lastMetaIndex = -1;
for (let i = 0; i < head.childNodes.length; i++) {
if (head.childNodes[i].nodeName === "meta") {
lastMetaIndex = i;
}
}
if (lastMetaIndex >= 0) {
insertIndex = lastMetaIndex + 1;
} else {
insertIndex = 0;
}
head.childNodes.splice(insertIndex, 0, linkNode);
};

@@ -53,3 +248,3 @@ var upsertHeadInlineScripts = (head, scripts) => {

const scriptNode = parseFragment(
`<script id="${script.id}">${script.content}</script>`
`<script id="${script.id}" data-order="${script.order}" data-position="${script.position}">${script.content}</script>`
).childNodes[0];

@@ -96,3 +291,3 @@ return scriptNode;

const styleNode = parseFragment(
`<style id="${style.id}">${style.content}</style>`
`<style id="${style.id}" data-order="${style.order}" data-position="${style.position}">${style.content}</style>`
).childNodes[0];

@@ -146,3 +341,5 @@ return styleNode;

);
head.childNodes.unshift(...parsedTags);
parsedTags.reverse().forEach((tag) => {
head.childNodes.unshift(tag);
});
};

@@ -167,3 +364,3 @@ var upsertHeadStyles = (head, styles) => {

const styleNode = parseFragment(
`<link rel="stylesheet" href="${style.href}" id="${style.id}"></link>`
`<link rel="stylesheet" href="${style.href}" id="${style.id}" data-order="${style.order}" data-position="${style.position}"></link>`
).childNodes[0];

@@ -194,65 +391,2 @@ return styleNode;

};
var upsertScripts = (element, scripts) => {
const sortedScripts = [...scripts].sort(
(a, b) => (a.order ?? 0) - (b.order ?? 0)
);
sortedScripts.forEach((script) => {
const existingScriptIndex = element.childNodes.findIndex(
(node) => node.nodeName === "script" && node.attrs?.find(
(attr) => attr.name === "id" && attr.value === script.id
)
);
if (existingScriptIndex > -1) {
element.childNodes.splice(existingScriptIndex, 1);
}
});
const scriptTags = sortedScripts.map((script) => {
const scriptNode = parseFragment(
`<script id="${script.id}" src="${script.src}"></script>`
).childNodes[0];
if (script.type) {
scriptNode.attrs?.push({ name: "type", value: script.type });
}
if (script.async) {
scriptNode.attrs?.push({ name: "async", value: "true" });
}
if (script.defer) {
scriptNode.attrs?.push({ name: "defer", value: "true" });
}
if (script.crossOrigin) {
scriptNode.attrs?.push({
name: "crossorigin",
value: script.crossOrigin
});
}
if (script.integrity) {
scriptNode.attrs?.push({ name: "integrity", value: script.integrity });
}
if (script.nonce) {
scriptNode.attrs?.push({ name: "nonce", value: script.nonce });
}
return scriptNode;
});
const beginningScripts = sortedScripts.reduce((acc, script, index) => {
if (script.position === "beginning") {
acc.push(scriptTags[index]);
}
return acc;
}, []);
const endScripts = sortedScripts.reduce(
(acc, script, index) => {
if (script.position === "end") {
acc.push(scriptTags[index]);
}
return acc;
},
[]
);
beginningScripts.reverse().forEach((scriptNode) => {
element.childNodes.unshift(scriptNode);
});
endScripts.forEach((scriptNode) => {
element.childNodes.push(scriptNode);
});
};
var upsertTitle = (head, title) => {

@@ -286,4 +420,5 @@ const titleNode = head.childNodes?.find(

constructor(htmlSource) {
const { document, head, body } = parseDocument(htmlSource);
const { document, head, body, html } = parseDocument(htmlSource);
this.document = document;
this.html = html;
this.head = head;

@@ -293,3 +428,3 @@ this.body = body;

/**
* Upsert the title tag
* Upsert the title tag - inserts at beginning of <head>
* @param title - The title to upsert

@@ -303,3 +438,3 @@ * @returns The TemplateParser instance

/**
* Upsert the favicon tag
* Upsert the favicon tag - inserts at beginning of <head> after title tag
* @param href - The favicon to upsert

@@ -315,4 +450,4 @@ * @param rel - The rel attribute of the favicon tag

/**
* Upsert the head before html tags
* @param tags - The tags to upsert
* Upsert meta tags in <head> - inserts at beginning for SEO priority
* @param tags - The meta tags to upsert
* @returns The TemplateParser instance

@@ -325,4 +460,4 @@ */

/**
* Upsert the head before styles
* @param styles - The styles to upsert
* Upsert external stylesheets in <head> - supports position-based insertion
* @param styles - The external styles to upsert
* @returns The TemplateParser instance

@@ -335,4 +470,4 @@ */

/**
* Upsert the head inline styles
* @param styles - The styles to upsert
* Upsert inline styles in <head> - supports position-based insertion
* @param styles - The inline styles to upsert
* @returns The TemplateParser instance

@@ -345,4 +480,4 @@ */

/**
* Upsert the head before scripts
* @param scripts - The scripts to upsert
* Upsert external scripts in <head> - supports position-based insertion
* @param scripts - The external scripts to upsert
* @returns The TemplateParser instance

@@ -355,4 +490,4 @@ */

/**
* Upsert the inline scripts
* @param scripts - The scripts to upsert
* Upsert inline scripts in <head> - supports position-based insertion
* @param scripts - The inline scripts to upsert
* @returns The TemplateParser instance

@@ -365,3 +500,3 @@ */

/**
* Upsert the body after scripts
* Upsert scripts in <body> - supports position-based insertion, typically at end for performance
* @param scripts - The scripts to upsert

@@ -371,3 +506,3 @@ * @returns The TemplateParser instance

upsertBodyScripts(scripts) {
upsertScripts(this.body, scripts);
upsertBodySctipts(this.body, scripts);
return this;

@@ -380,3 +515,4 @@ }

serialize() {
return serialize(this.document);
const sortedDocument = sortDocument(this.document);
return serialize(sortedDocument);
}

@@ -396,4 +532,4 @@ /**

const parser = new TemplateParser(htmlSource);
if (options.title) {
parser.upsertTitleTag(options.title);
if (options.headMetaTags?.length) {
parser.upsertHeadMetaTags(options.headMetaTags);
}

@@ -407,4 +543,4 @@ if (options.favicon) {

}
if (options.headMetaTags?.length) {
parser.upsertHeadMetaTags(options.headMetaTags);
if (options.title) {
parser.upsertTitleTag(options.title);
}

@@ -411,0 +547,0 @@ if (options.headStyles?.length) {

@@ -27,2 +27,185 @@ 'use strict';

};
var sortDocument = (document) => {
const sortedDocument = { ...document };
const html = document.childNodes.find(
(node) => node.nodeName === "html"
);
if (!html) {
return sortedDocument;
}
const head = html.childNodes.find(
(node) => node.nodeName === "head"
);
const body = html.childNodes.find(
(node) => node.nodeName === "body"
);
if (head) {
sortNodesByPosition(head, ["link", "style"], (node) => {
const elementNode = node;
const hasId = elementNode.attrs?.find((attr) => attr.name === "id");
const hasOrder = elementNode.attrs?.find(
(attr) => attr.name === "data-order"
);
const hasPosition = elementNode.attrs?.find(
(attr) => attr.name === "data-position"
);
if (!hasId || !hasOrder || !hasPosition) {
return false;
}
if (node.nodeName === "link") {
const isStylesheet = elementNode.attrs?.find(
(attr) => attr.name === "rel" && attr.value === "stylesheet"
);
return !!isStylesheet;
}
return true;
});
sortNodesByPosition(head, ["script"], (node) => {
const elementNode = node;
const hasId = elementNode.attrs?.find((attr) => attr.name === "id");
const hasOrder = elementNode.attrs?.find(
(attr) => attr.name === "data-order"
);
const hasPosition = elementNode.attrs?.find(
(attr) => attr.name === "data-position"
);
return !!(hasId && hasOrder && hasPosition);
});
cleanupSortingAttributes(head);
}
if (body) {
sortNodesByPosition(body, ["script"], (node) => {
const elementNode = node;
const hasId = elementNode.attrs?.find((attr) => attr.name === "id");
const hasOrder = elementNode.attrs?.find(
(attr) => attr.name === "data-order"
);
const hasPosition = elementNode.attrs?.find(
(attr) => attr.name === "data-position"
);
return !!(hasId && hasOrder && hasPosition);
});
cleanupSortingAttributes(body);
}
return sortedDocument;
};
var sortNodesByPosition = (element, nodeNames, filterFn) => {
const beginningNodes = [];
const endNodes = [];
element.childNodes.forEach((node) => {
if (nodeNames.includes(node.nodeName) && filterFn(node)) {
const elementNode = node;
const orderAttr = elementNode.attrs?.find(
(attr) => attr.name === "data-order"
);
const positionAttr = elementNode.attrs?.find(
(attr) => attr.name === "data-position"
);
if (orderAttr && positionAttr) {
const order = parseInt(orderAttr.value, 10) || 0;
const nodeInfo = { node: elementNode, order };
if (positionAttr.value === "beginning") {
beginningNodes.push(nodeInfo);
} else if (positionAttr.value === "end") {
endNodes.push(nodeInfo);
}
}
}
});
[...beginningNodes, ...endNodes].forEach(({ node }) => {
const index = element.childNodes.indexOf(node);
if (index > -1) {
element.childNodes.splice(index, 1);
}
});
beginningNodes.sort((a, b) => a.order - b.order);
endNodes.sort((a, b) => a.order - b.order);
beginningNodes.reverse().forEach(({ node }) => {
element.childNodes.unshift(node);
});
endNodes.forEach(({ node }) => {
element.childNodes.push(node);
});
};
var cleanupSortingAttributes = (element) => {
if (element.attrs) {
element.attrs = element.attrs.filter(
(attr) => attr.name !== "data-order" && attr.name !== "data-position"
);
}
element.childNodes.forEach((node) => {
if (node.nodeName && node.nodeName !== "#text" && node.nodeName !== "#comment") {
const childElement = node;
cleanupSortingAttributes(childElement);
}
});
};
var upsertScripts = (element, scripts) => {
const sortedScripts = [...scripts].sort(
(a, b) => (a.order ?? 0) - (b.order ?? 0)
);
sortedScripts.forEach((script) => {
const existingScriptIndex = element.childNodes.findIndex(
(node) => node.nodeName === "script" && node.attrs?.find(
(attr) => attr.name === "id" && attr.value === script.id
)
);
if (existingScriptIndex > -1) {
element.childNodes.splice(existingScriptIndex, 1);
}
});
const scriptTags = sortedScripts.map((script) => {
const scriptNode = parse5.parseFragment(
`<script id="${script.id}" src="${script.src}" data-order="${script.order}" data-position="${script.position}"></script>`
).childNodes[0];
if (script.type) {
scriptNode.attrs?.push({ name: "type", value: script.type });
}
if (script.async) {
scriptNode.attrs?.push({ name: "async", value: "true" });
}
if (script.defer) {
scriptNode.attrs?.push({ name: "defer", value: "true" });
}
if (script.crossOrigin) {
scriptNode.attrs?.push({
name: "crossorigin",
value: script.crossOrigin
});
}
if (script.integrity) {
scriptNode.attrs?.push({ name: "integrity", value: script.integrity });
}
if (script.nonce) {
scriptNode.attrs?.push({ name: "nonce", value: script.nonce });
}
return scriptNode;
});
const beginningScripts = sortedScripts.reduce((acc, script, index) => {
if (script.position === "beginning") {
acc.push(scriptTags[index]);
}
return acc;
}, []);
const endScripts = sortedScripts.reduce(
(acc, script, index) => {
if (script.position === "end") {
acc.push(scriptTags[index]);
}
return acc;
},
[]
);
beginningScripts.reverse().forEach((scriptNode) => {
element.childNodes.unshift(scriptNode);
});
endScripts.forEach((scriptNode) => {
element.childNodes.push(scriptNode);
});
};
// src/parser/upsertBodySctipts.ts
var upsertBodySctipts = (body, scripts) => {
return upsertScripts(body, scripts);
};
var upsertFavicon = (head, href, rel = "icon", attributes = {}) => {

@@ -41,3 +224,15 @@ const existingLinkIndex = head.childNodes.findIndex(

).childNodes[0];
head.childNodes.push(linkNode);
let insertIndex = 0;
let lastMetaIndex = -1;
for (let i = 0; i < head.childNodes.length; i++) {
if (head.childNodes[i].nodeName === "meta") {
lastMetaIndex = i;
}
}
if (lastMetaIndex >= 0) {
insertIndex = lastMetaIndex + 1;
} else {
insertIndex = 0;
}
head.childNodes.splice(insertIndex, 0, linkNode);
};

@@ -60,3 +255,3 @@ var upsertHeadInlineScripts = (head, scripts) => {

const scriptNode = parse5.parseFragment(
`<script id="${script.id}">${script.content}</script>`
`<script id="${script.id}" data-order="${script.order}" data-position="${script.position}">${script.content}</script>`
).childNodes[0];

@@ -103,3 +298,3 @@ return scriptNode;

const styleNode = parse5.parseFragment(
`<style id="${style.id}">${style.content}</style>`
`<style id="${style.id}" data-order="${style.order}" data-position="${style.position}">${style.content}</style>`
).childNodes[0];

@@ -153,3 +348,5 @@ return styleNode;

);
head.childNodes.unshift(...parsedTags);
parsedTags.reverse().forEach((tag) => {
head.childNodes.unshift(tag);
});
};

@@ -174,3 +371,3 @@ var upsertHeadStyles = (head, styles) => {

const styleNode = parse5.parseFragment(
`<link rel="stylesheet" href="${style.href}" id="${style.id}"></link>`
`<link rel="stylesheet" href="${style.href}" id="${style.id}" data-order="${style.order}" data-position="${style.position}"></link>`
).childNodes[0];

@@ -201,65 +398,2 @@ return styleNode;

};
var upsertScripts = (element, scripts) => {
const sortedScripts = [...scripts].sort(
(a, b) => (a.order ?? 0) - (b.order ?? 0)
);
sortedScripts.forEach((script) => {
const existingScriptIndex = element.childNodes.findIndex(
(node) => node.nodeName === "script" && node.attrs?.find(
(attr) => attr.name === "id" && attr.value === script.id
)
);
if (existingScriptIndex > -1) {
element.childNodes.splice(existingScriptIndex, 1);
}
});
const scriptTags = sortedScripts.map((script) => {
const scriptNode = parse5.parseFragment(
`<script id="${script.id}" src="${script.src}"></script>`
).childNodes[0];
if (script.type) {
scriptNode.attrs?.push({ name: "type", value: script.type });
}
if (script.async) {
scriptNode.attrs?.push({ name: "async", value: "true" });
}
if (script.defer) {
scriptNode.attrs?.push({ name: "defer", value: "true" });
}
if (script.crossOrigin) {
scriptNode.attrs?.push({
name: "crossorigin",
value: script.crossOrigin
});
}
if (script.integrity) {
scriptNode.attrs?.push({ name: "integrity", value: script.integrity });
}
if (script.nonce) {
scriptNode.attrs?.push({ name: "nonce", value: script.nonce });
}
return scriptNode;
});
const beginningScripts = sortedScripts.reduce((acc, script, index) => {
if (script.position === "beginning") {
acc.push(scriptTags[index]);
}
return acc;
}, []);
const endScripts = sortedScripts.reduce(
(acc, script, index) => {
if (script.position === "end") {
acc.push(scriptTags[index]);
}
return acc;
},
[]
);
beginningScripts.reverse().forEach((scriptNode) => {
element.childNodes.unshift(scriptNode);
});
endScripts.forEach((scriptNode) => {
element.childNodes.push(scriptNode);
});
};
var upsertTitle = (head, title) => {

@@ -293,4 +427,5 @@ const titleNode = head.childNodes?.find(

constructor(htmlSource) {
const { document, head, body } = parseDocument(htmlSource);
const { document, head, body, html } = parseDocument(htmlSource);
this.document = document;
this.html = html;
this.head = head;

@@ -300,3 +435,3 @@ this.body = body;

/**
* Upsert the title tag
* Upsert the title tag - inserts at beginning of <head>
* @param title - The title to upsert

@@ -310,3 +445,3 @@ * @returns The TemplateParser instance

/**
* Upsert the favicon tag
* Upsert the favicon tag - inserts at beginning of <head> after title tag
* @param href - The favicon to upsert

@@ -322,4 +457,4 @@ * @param rel - The rel attribute of the favicon tag

/**
* Upsert the head before html tags
* @param tags - The tags to upsert
* Upsert meta tags in <head> - inserts at beginning for SEO priority
* @param tags - The meta tags to upsert
* @returns The TemplateParser instance

@@ -332,4 +467,4 @@ */

/**
* Upsert the head before styles
* @param styles - The styles to upsert
* Upsert external stylesheets in <head> - supports position-based insertion
* @param styles - The external styles to upsert
* @returns The TemplateParser instance

@@ -342,4 +477,4 @@ */

/**
* Upsert the head inline styles
* @param styles - The styles to upsert
* Upsert inline styles in <head> - supports position-based insertion
* @param styles - The inline styles to upsert
* @returns The TemplateParser instance

@@ -352,4 +487,4 @@ */

/**
* Upsert the head before scripts
* @param scripts - The scripts to upsert
* Upsert external scripts in <head> - supports position-based insertion
* @param scripts - The external scripts to upsert
* @returns The TemplateParser instance

@@ -362,4 +497,4 @@ */

/**
* Upsert the inline scripts
* @param scripts - The scripts to upsert
* Upsert inline scripts in <head> - supports position-based insertion
* @param scripts - The inline scripts to upsert
* @returns The TemplateParser instance

@@ -372,3 +507,3 @@ */

/**
* Upsert the body after scripts
* Upsert scripts in <body> - supports position-based insertion, typically at end for performance
* @param scripts - The scripts to upsert

@@ -378,3 +513,3 @@ * @returns The TemplateParser instance

upsertBodyScripts(scripts) {
upsertScripts(this.body, scripts);
upsertBodySctipts(this.body, scripts);
return this;

@@ -387,3 +522,4 @@ }

serialize() {
return parse5.serialize(this.document);
const sortedDocument = sortDocument(this.document);
return parse5.serialize(sortedDocument);
}

@@ -403,4 +539,4 @@ /**

const parser = new TemplateParser(htmlSource);
if (options.title) {
parser.upsertTitleTag(options.title);
if (options.headMetaTags?.length) {
parser.upsertHeadMetaTags(options.headMetaTags);
}

@@ -414,4 +550,4 @@ if (options.favicon) {

}
if (options.headMetaTags?.length) {
parser.upsertHeadMetaTags(options.headMetaTags);
if (options.title) {
parser.upsertTitleTag(options.title);
}

@@ -418,0 +554,0 @@ if (options.headStyles?.length) {

{
"name": "@hyperse/html-webpack-plugin-loader",
"version": "1.0.6",
"version": "1.0.7",
"description": "A custom template loader that parses HTML templates for the `html-webpack-plugin` package",

@@ -58,25 +58,25 @@ "keywords": [

"dependencies": {
"parse5": "^7.3.0"
"parse5": "^8.0.0"
},
"devDependencies": {
"@changesets/changelog-github": "^0.5.1",
"@changesets/cli": "^2.29.4",
"@changesets/cli": "^2.29.6",
"@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.1",
"@hyperse/eslint-config-hyperse": "^1.4.5",
"@hyperse/eslint-config-hyperse": "^1.4.8",
"@hyperse/ts-node": "^1.0.3",
"@types/node": "^24.0.1",
"@types/node": "^24.3.0",
"commitizen": "^4.3.1",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^9.28.0",
"html-webpack-plugin": "^5.6.3",
"eslint": "^9.34.0",
"html-webpack-plugin": "^5.6.4",
"husky": "^9.1.7",
"lint-staged": "^16.1.0",
"lint-staged": "^16.1.6",
"npm-run-all": "^4.1.5",
"tsup": "^8.5.0",
"typescript": "^5.8.3",
"vitest": "^3.2.3",
"webpack": "^5.99.9"
"typescript": "^5.9.2",
"vitest": "^3.2.4",
"webpack": "^5.101.3"
},
"packageManager": "yarn@4.9.2",
"packageManager": "yarn@4.9.4",
"engines": {

@@ -83,0 +83,0 @@ "node": ">=20"

@@ -386,3 +386,2 @@ # @hyperse/html-webpack-plugin-loader

1. **Template Organization**:
- Keep your HTML templates in a dedicated directory (e.g., `src/templates/`)

@@ -392,3 +391,2 @@ - Use consistent naming conventions for your template files

2. **Environment-specific Configuration**:
- Use webpack's environment configuration to manage different settings for development and production

@@ -398,3 +396,2 @@ - Consider using environment variables for sensitive or environment-specific values

3. **Performance Optimization**:
- Use `position: 'end'` for non-critical scripts and styles

@@ -405,3 +402,2 @@ - Use `position: 'beginning'` for critical resources

4. **Security**:
- Always use `integrity` checks for external resources when possible

@@ -408,0 +404,0 @@ - Use `nonce` for inline scripts when implementing CSP