forked from zalando/tailor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
serializer.js
253 lines (240 loc) · 7.3 KB
/
serializer.js
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
'use strict';
const Serializer = require('parse5/lib/serializer');
/**
* CustomSerializer Class
*
* It uses the serializer from parse5 and overrides the serialize functions for handling
* the tags that are passed via handleTags and piping.
*
* All the default nodes like <div>, <head> go to parse5 serializer
* and the rest are handled in this class.
*
* Node - Represets DOM Tree
*/
module.exports = class CustomSerializer extends Serializer {
constructor(
node,
{ treeAdapter, fullRendering, slotMap, handleTags, pipeTags }
) {
super(node, { treeAdapter });
this.fullRendering = fullRendering;
this.slotMap = slotMap;
this.handleTags = handleTags;
this.pipeTags = pipeTags;
this.isPipeInserted = false;
this.lastChildInserted = false;
this.defaultSlotsInserted = false;
this.serializedList = [];
this._serializeNode = this._serializeNode.bind(this);
}
/**
* Push the serialized content in to the serializedList.
*
* this.html - serialized contents exposed by parse5
*/
pushBuffer() {
if (this.html !== '') {
this.serializedList.push(Buffer.from(this.html));
this.html = '';
}
}
/**
* Extract the serialized HTML content and reset the serialized buffer.
*
* @returns {String}
*/
getHtmlContent() {
let temp = '';
if (this.html !== '') {
temp = this.html;
this.html = '';
}
return temp;
}
/**
* Overidden the serialize function of parse5 Serializer
*
* this.startNode - Denotes the root node
* @returns {Array}
*/
serialize() {
this._serializeChildNodes(this.startNode);
this.pushBuffer();
return this.serializedList;
}
/**
* Checks if the node satifies the placeholder for `piping`
*
* @returns {Boolean}
*/
_isPipeNode(node) {
return this.pipeTags.includes(node.name);
}
/**
* Checks if the node is either slot node / script type=slot
*
* @returns {Boolean}
*/
_isSlotNode(node) {
const { attribs = {}, name } = node;
return (
name === 'slot' || (name === 'script' && attribs.type === 'slot')
);
}
/**
* Checks if the node is one of the nodes passed through handleTags
*
* @returns {Boolean}
*/
_isSpecialNode(node) {
const { attribs = {}, name } = node;
return (
this.handleTags.includes(name) ||
(name === 'script' && this.handleTags.includes(attribs.type))
);
}
/**
* Checks if the node is the lastChild of <body>
*
* @returns {Boolean}
*/
_isLastChildOfBody(node) {
const { parentNode: { name, lastChild } } = node;
return name === 'body' && Object.is(node, lastChild);
}
/**
* Serialize the nodes passed via handleTags
*
* @param {object} node
*
* // Input
* <fragment src="http://example.com" async primary></fragment>
*
* // Output
* {
* name: 'fragment',
* attributes: {
* async: '',
* primary: ''
* },
* }
*/
_serializeSpecial(node) {
this.pushBuffer();
let handledObj;
const { name, attribs: attributes } = node;
if (this.handleTags.includes(name)) {
handledObj = Object.assign({}, { name: name, attributes });
this.serializedList.push(handledObj);
this._serializeChildNodes(node);
this.pushBuffer();
} else {
// For handling the script type other than text/javascript
this._serializeChildNodes(node);
handledObj = Object.assign(
{},
{
name: attributes.type,
attributes,
textContent: this.getHtmlContent()
}
);
this.serializedList.push(handledObj);
}
this.serializedList.push({ closingTag: name });
}
/**
* Serialize the slot nodes from the slot map
*
* @param {object} node
*/
_serializeSlot(node) {
const slotName = node.attribs.name;
if (slotName) {
const childNodes = this.treeAdapter.getChildNodes(node);
const slots = this.slotMap.has(slotName)
? this.slotMap.get(slotName)
: childNodes;
slots && slots.forEach(this._serializeNode);
} else {
// Handling duplicate slots
if (this.defaultSlotsInserted) {
console.warn(
'Encountered duplicate Unnamed slots in the template - Skipping the node'
);
return;
}
const defaultSlots = this.slotMap.get('default');
this.defaultSlotsInserted = true;
defaultSlots && defaultSlots.forEach(this._serializeNode);
}
}
/**
* Insert the pipe placeholder and serialize the node
*
* @param {object} node
*/
_serializePipe(node) {
this.pushBuffer();
this.serializedList.push({ placeholder: 'pipe' });
this.isPipeInserted = true;
this._serializeNode(node);
}
/**
* Serialize the nodes in default slot from slot map and insert async placeholder.
*
* should happen before closing the body.
*/
_serializeRest() {
this.lastChildInserted = true;
if (!this.defaultSlotsInserted) {
const defaultSlots = this.slotMap.get('default');
defaultSlots && defaultSlots.forEach(this._serializeNode);
}
this.pushBuffer();
this.serializedList.push({ placeholder: 'async' });
}
/**
* Serialize all the children of a parent node.
*
* @param {object} parentNode
*/
_serializeChildNodes(parentNode) {
const childNodes = this.treeAdapter.getChildNodes(parentNode);
childNodes && childNodes.forEach(this._serializeNode);
}
/**
* Serialize the node based on their type
*
* @param {object} currentNode
*/
_serializeNode(currentNode) {
if (
this.fullRendering &&
!this.isPipeInserted &&
this._isPipeNode(currentNode)
) {
this._serializePipe(currentNode);
} else if (this._isSpecialNode(currentNode)) {
this._serializeSpecial(currentNode);
} else if (this._isSlotNode(currentNode)) {
this._serializeSlot(currentNode);
} else if (this.treeAdapter.isElementNode(currentNode)) {
this._serializeElement(currentNode);
} else if (this.treeAdapter.isTextNode(currentNode)) {
this._serializeTextNode(currentNode);
} else if (this.treeAdapter.isCommentNode(currentNode)) {
this._serializeCommentNode(currentNode);
} else if (this.treeAdapter.isDocumentTypeNode(currentNode)) {
this._serializeDocumentTypeNode(currentNode);
}
// Push default slots and async placeholder before body
if (
this.fullRendering &&
!this.lastChildInserted &&
this._isLastChildOfBody(currentNode)
) {
this._serializeRest();
}
}
};