Skip to content

Commit 0f73904

Browse files
committed
refactor: 💡 API refactor
1 parent 0d7a7a2 commit 0f73904

File tree

8 files changed

+199
-99
lines changed

8 files changed

+199
-99
lines changed

README.md

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,28 +52,73 @@ import { TemplateParser } from '@hyperse/html-webpack-plugin-loader';
5252
// Create a new parser instance
5353
const parser = new TemplateParser(htmlSource);
5454

55-
// Chain methods to modify the template
55+
// Define template options
56+
const templateOptions: TemplateOptions = {
57+
// Set page title
58+
title: 'My Page Title',
59+
60+
// Set website favicon
61+
favicon: '/favicon.ico',
62+
63+
// Set meta tags in head
64+
headMetaTags: ['<meta name="description" content="My page description">'],
65+
66+
// Set styles in head
67+
headStyles: ['<style>body { margin: 0; }</style>'],
68+
69+
// Set scripts in head
70+
headScripts: [
71+
{
72+
id: 'main-js', // Required: Unique script identifier
73+
src: '/main.js', // Required: Script source path
74+
position: 'end', // Required: Script position in head
75+
type: 'text/javascript', // Optional: Script type
76+
async: true, // Optional: Load script asynchronously
77+
defer: false, // Optional: Defer script loading
78+
order: 1, // Optional: Loading order
79+
},
80+
],
81+
82+
// Set inline scripts in head
83+
headInlineScripts: [
84+
{
85+
id: 'inline-script', // Required: Unique script identifier
86+
position: 'end', // Required: Script position in head
87+
content: 'console.log("Hello");', // Required: Script content
88+
order: 2, // Optional: Loading order
89+
},
90+
],
91+
92+
// Set scripts in body
93+
bodyScripts: [
94+
{
95+
id: 'app-js', // Required: Unique script identifier
96+
src: '/app.js', // Required: Script source path
97+
position: 'end', // Required: Script position in body
98+
type: 'text/javascript', // Optional: Script type
99+
async: true, // Optional: Load script asynchronously
100+
defer: false, // Optional: Defer script loading
101+
order: 1, // Optional: Loading order
102+
},
103+
],
104+
};
105+
106+
// Use template options to modify HTML
56107
const modifiedHtml = parser
57-
.upsertTitleTag('My Page Title')
58-
.upsertFaviconTag('/favicon.ico')
59-
.upsertViewportTag(
60-
'<meta name="viewport" content="width=device-width, initial-scale=1">'
61-
)
62-
.upsertHeadMetaTags([
63-
'<meta name="description" content="My page description">',
64-
])
65-
.upsertHeadStyles(['<style>body { margin: 0; }</style>'])
66-
.upsertHeadScripts([{ src: '/main.js' }])
67-
.upsertHeadInlineScripts(['<script>console.log("Hello");</script>'])
68-
.upsertBodyScripts([{ src: '/app.js' }])
108+
.upsertTitleTag(templateOptions.title)
109+
.upsertFaviconTag(templateOptions.favicon)
110+
.upsertHeadMetaTags(templateOptions.headMetaTags)
111+
.upsertHeadStyles(templateOptions.headStyles)
112+
.upsertHeadScripts(templateOptions.headScripts)
113+
.upsertHeadInlineScripts(templateOptions.headInlineScripts)
114+
.upsertBodyScripts(templateOptions.bodyScripts)
69115
.serialize();
70116
```
71117

72118
#### Available Methods
73119

74120
- `upsertTitleTag(title: string)`: Updates or inserts the page title
75121
- `upsertFaviconTag(favicon: string)`: Updates or inserts the favicon link
76-
- `upsertViewportTag(viewport: string)`: Updates or inserts the viewport meta tag
77122
- `upsertHeadMetaTags(tags: string[])`: Updates or inserts meta tags in the head
78123
- `upsertHeadStyles(styles: string[])`: Updates or inserts style tags in the head
79124
- `upsertHeadScripts(scripts: ScriptItem[])`: Updates or inserts script tags in the head

src/parser/TemplateParser.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { DefaultTreeAdapterTypes } from 'parse5';
22
import { serialize } from 'parse5';
3-
import type { ScriptItem } from '../types.js';
3+
import type { ScriptionInlineItem, ScriptItem } from '../types.js';
44
import { parseDocument } from '../utils/parseDocument.js';
55
import { upsertFavicon } from './upsertFavicon.js';
66
import { upsertHeadInlineScripts } from './upsertHeadInlineScripts.js';
@@ -41,16 +41,6 @@ export class TemplateParser {
4141
return this;
4242
}
4343

44-
/**
45-
* Upsert the viewport tag
46-
* @param viewport - The viewport to upsert
47-
* @returns The TemplateParser instance
48-
*/
49-
public upsertViewportTag(viewport: string): TemplateParser {
50-
upsertHeadInlineScripts(this.head, [viewport]);
51-
return this;
52-
}
53-
5444
/**
5545
* Upsert the head before html tags
5646
* @param tags - The tags to upsert
@@ -86,7 +76,9 @@ export class TemplateParser {
8676
* @param scripts - The scripts to upsert
8777
* @returns The TemplateParser instance
8878
*/
89-
public upsertHeadInlineScripts(scripts: string[]): TemplateParser {
79+
public upsertHeadInlineScripts(
80+
scripts: ScriptionInlineItem[]
81+
): TemplateParser {
9082
upsertHeadInlineScripts(this.body, scripts);
9183
return this;
9284
}

src/parser/parseTemplate.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ export const parseTemplate = (
2121
parser.upsertFaviconTag(options.favicon);
2222
}
2323

24-
if (options.viewport) {
25-
parser.upsertViewportTag(options.viewport);
26-
}
27-
2824
if (options.headMetaTags?.length) {
2925
parser.upsertHeadMetaTags(options.headMetaTags);
3026
}
Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type DefaultTreeAdapterTypes, parseFragment } from 'parse5';
2+
import type { ScriptionInlineItem } from '../types.js';
23

34
/**
45
* Upsert the inline scripts
@@ -7,17 +8,16 @@ import { type DefaultTreeAdapterTypes, parseFragment } from 'parse5';
78
*/
89
export const upsertHeadInlineScripts = (
910
head: DefaultTreeAdapterTypes.Element,
10-
scripts: string[]
11+
scripts: ScriptionInlineItem[]
1112
) => {
1213
// Remove existing inline scripts with matching content
1314
scripts.forEach((script) => {
1415
const existingScriptIndex = head.childNodes.findIndex(
1516
(node) =>
1617
node.nodeName === 'script' &&
17-
(
18-
(node as DefaultTreeAdapterTypes.Element)
19-
.childNodes?.[0] as DefaultTreeAdapterTypes.TextNode
20-
)?.value === script
18+
(node as DefaultTreeAdapterTypes.Element).attrs?.find(
19+
(attr) => attr.name === 'id' && attr.value === script.id
20+
)
2121
);
2222

2323
if (existingScriptIndex > -1) {
@@ -26,10 +26,46 @@ export const upsertHeadInlineScripts = (
2626
});
2727

2828
// Add new inline scripts
29-
scripts.forEach((script) => {
30-
const scriptNode = parseFragment(`<script>${script}</script>`)
31-
.childNodes[0] as DefaultTreeAdapterTypes.Element;
29+
// Sort scripts by order (smaller numbers first)
30+
const sortedScripts = [...scripts].sort(
31+
(a, b) => (a.order ?? 0) - (b.order ?? 0)
32+
);
33+
34+
// Create script nodes
35+
const scriptTags = sortedScripts.map((script) => {
36+
const scriptNode = parseFragment(
37+
`<script id="${script.id}">${script.content}</script>`
38+
).childNodes[0] as DefaultTreeAdapterTypes.Element;
39+
return scriptNode;
40+
});
41+
42+
// Split scripts by position
43+
const beginningScripts = sortedScripts.reduce<
44+
DefaultTreeAdapterTypes.Element[]
45+
>((acc, script, index) => {
46+
if (script.position === 'beginning') {
47+
acc.push(scriptTags[index]);
48+
}
49+
return acc;
50+
}, []);
51+
52+
const endScripts = sortedScripts.reduce<DefaultTreeAdapterTypes.Element[]>(
53+
(acc, script, index) => {
54+
if (script.position === 'end') {
55+
acc.push(scriptTags[index]);
56+
}
57+
return acc;
58+
},
59+
[]
60+
);
61+
62+
// Add beginning scripts
63+
beginningScripts.reverse().forEach((scriptNode) => {
64+
head.childNodes.unshift(scriptNode);
65+
});
3266

67+
// Add end scripts
68+
endScripts.forEach((scriptNode) => {
3369
head.childNodes.push(scriptNode);
3470
});
3571
};

src/parser/upsertScripts.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const upsertScripts = (
2121
(node) =>
2222
node.nodeName === 'script' &&
2323
(node as DefaultTreeAdapterTypes.Element).attrs?.find(
24-
(attr) => attr.name === 'src' && attr.value === script.src
24+
(attr) => attr.name === 'id' && attr.value === script.id
2525
)
2626
);
2727

@@ -32,8 +32,9 @@ export const upsertScripts = (
3232

3333
// Create new script nodes
3434
const scriptTags = sortedScripts.map((script) => {
35-
const scriptNode = parseFragment(`<script src="${script.src}"></script>`)
36-
.childNodes[0] as DefaultTreeAdapterTypes.Element;
35+
const scriptNode = parseFragment(
36+
`<script id="${script.id}" src="${script.src}"></script>`
37+
).childNodes[0] as DefaultTreeAdapterTypes.Element;
3738

3839
if (script.type) {
3940
scriptNode.attrs?.push({ name: 'type', value: script.type });

src/types.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
1-
export type ScriptItem = {
1+
/**
2+
* The position of the script
3+
*/
4+
export type Position = 'beginning' | 'end';
5+
6+
/**
7+
* The base script item
8+
*/
9+
export type ScriptItemBase = {
210
/**
3-
* The src of the script
11+
* The id of the script
412
*/
5-
src: string;
13+
id: string;
614
/**
715
* The position of the script, 'beginning' for beginning of head, 'end' for end of head
816
* The position of the script, 'beginning' for beginning of body, 'end' for end of body
917
*/
10-
position: 'beginning' | 'end';
18+
position: Position;
1119
/**
1220
* The order of the script, smaller numbers are loaded first
1321
*/
1422
order?: number;
23+
};
24+
/**
25+
* The inline script item
26+
*/
27+
export type ScriptionInlineItem = ScriptItemBase & {
28+
/**
29+
* The content of the script
30+
*/
31+
content: string;
32+
};
33+
34+
export type ScriptItem = ScriptItemBase & {
35+
/**
36+
* The src of the script, the id
37+
*/
38+
src: string;
1539
/**
1640
* The type of the script
1741
*/
@@ -50,14 +74,7 @@ export interface TemplateOptions {
5074
* The favicon of the page
5175
*/
5276
favicon?: string;
53-
/**
54-
* The viewport of the page
55-
*/
56-
viewport?: string;
57-
/**
58-
* The head inline scripts
59-
*/
60-
headInlineScripts?: string[];
77+
6178
/**
6279
* The head html meta tags of the page
6380
*/
@@ -74,4 +91,8 @@ export interface TemplateOptions {
7491
* The body scripts of the page
7592
*/
7693
bodyScripts?: ScriptItem[];
94+
/**
95+
* The head inline scripts
96+
*/
97+
headInlineScripts?: ScriptionInlineItem[];
7798
}

0 commit comments

Comments
 (0)