-
Notifications
You must be signed in to change notification settings - Fork 2
/
server.mjs
108 lines (98 loc) · 2.79 KB
/
server.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import { Readable } from 'node:stream'
const renderEmpty = Symbol('renderEmpty')
const renderFull = Symbol('renderFull')
const htmlEscape = Symbol('htmlEscape')
const serializeAttrs = Symbol('serializeAttrs')
const empty = ['base', 'link', 'meta']
const content = ['title', 'style', 'script']
class Head {
constructor (head) {
Object.assign(this, head)
}
stream () {
return Readable.from(this)
}
* [Symbol.iterator] () {
let elem
let fragment = ''
for (elem of empty) {
if (Array.isArray(this[elem])) {
for (const item of this[elem]) {
yield this[renderEmpty](elem, item)
}
} else if (this[elem]) {
yield this[renderEmpty](elem, this[elem])
}
}
for (elem of content) {
if (Array.isArray(this[elem])) {
for (const item of this[elem]) {
for (const element of this[renderFull](elem, this[elem])) {
yield element
}
}
} else if (this[elem]) {
for (const element of this[renderFull](elem, this[elem])) {
yield element
}
}
}
}
render () {
let fragment = ''
for (const chunk of this) {
fragment += chunk
}
return fragment
}
* [renderFull] (elem, item) {
if (typeof item === 'string') {
yield `<${elem}>${item}</${elem}>\n`
} else {
if (Array.isArray(item)) {
const [attrs, content] = item
const attrKeys = Object.keys(attrs)
if (attrKeys.length) {
yield `<${elem} ${this[serializeAttrs](attrKeys, attrs)}>${content || ''}</${elem}>\n`
} else {
yield `<${elem}>${content || ''}</${elem}>\n`
}
} else if (typeof item === 'object' && item !== null) {
const attrKeys = Object.keys(item)
if (attrKeys.length) {
yield `<${elem} ${this[serializeAttrs](attrKeys, item)}></${elem}>\n`
} else {
yield `<${elem}></${elem}>\n`
}
}
}
}
[renderEmpty] (elem, item) {
let fragment = ''
const attrKeys = Object.keys(item)
if (attrKeys.length) {
return `<${elem} ${this[serializeAttrs](attrKeys, item)}>\n`
} else {
return `<${elem}>\n`
}
}
[serializeAttrs] (keys, source) {
let serialized = ''
const lastKey = keys.length - 1
for (let i = 0; i < lastKey; i++) {
serialized += `${keys[i]}="${this[htmlEscape](source[keys[i]])}" `
}
return `${serialized}${keys[lastKey]}="${this[htmlEscape](source[keys[lastKey]])}"`
}
// MIT licensed, taken from https://github.com/sindresorhus/stringify-attributes
[htmlEscape] (str) {
return (str || '')
.toString()
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>')
}
}
export default Head