@hyperse/html-webpack-plugin-loader
Advanced tools
+224
-88
@@ -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) { |
+15
-14
@@ -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 |
+15
-14
@@ -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 |
+224
-88
@@ -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) { |
+224
-88
@@ -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) { |
+12
-12
| { | ||
| "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" |
+0
-4
@@ -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 |
79307
24.76%1866
27.81%415
-0.95%+ Added
+ Added
- Removed
- Removed
Updated