UNPKG

7.47 kBJavaScriptView Raw
1'use strict';
2const Serializer = require('parse5/lib/serializer');
3/**
4 * CustomSerializer Class
5 *
6 * It uses the serializer from parse5 and overrides the serialize functions for handling
7 * the tags that are passed via handleTags and piping.
8 *
9 * All the default nodes like <div>, <head> go to parse5 serializer
10 * and the rest are handled in this class.
11 *
12 * Node - Represets DOM Tree
13 */
14module.exports = class CustomSerializer extends Serializer {
15 constructor(
16 node,
17 { treeAdapter, fullRendering, slotMap, handleTags, pipeTags }
18 ) {
19 super(node, { treeAdapter });
20 this.fullRendering = fullRendering;
21 this.slotMap = slotMap;
22 this.handleTags = handleTags;
23 this.pipeTags = pipeTags;
24 this.isPipeInserted = false;
25 this.lastChildInserted = false;
26 this.defaultSlotsInserted = false;
27 this.serializedList = [];
28 this._serializeNode = this._serializeNode.bind(this);
29 }
30
31 /**
32 * Push the serialized content in to the serializedList.
33 *
34 * this.html - serialized contents exposed by parse5
35 */
36 pushBuffer() {
37 if (this.html !== '') {
38 this.serializedList.push(Buffer.from(this.html));
39 this.html = '';
40 }
41 }
42
43 /**
44 * Extract the serialized HTML content and reset the serialized buffer.
45 *
46 * @returns {String}
47 */
48 getHtmlContent() {
49 let temp = '';
50 if (this.html !== '') {
51 temp = this.html;
52 this.html = '';
53 }
54 return temp;
55 }
56
57 /**
58 * Overidden the serialize function of parse5 Serializer
59 *
60 * this.startNode - Denotes the root node
61 * @returns {Array}
62 */
63 serialize() {
64 this._serializeChildNodes(this.startNode);
65 this.pushBuffer();
66 return this.serializedList;
67 }
68
69 /**
70 * Checks if the node satifies the placeholder for `piping`
71 *
72 * @returns {Boolean}
73 */
74 _isPipeNode(node) {
75 return this.pipeTags.includes(node.name);
76 }
77
78 /**
79 * Checks if the node is either slot node / script type=slot
80 *
81 * @returns {Boolean}
82 */
83 _isSlotNode(node) {
84 const { attribs = {}, name } = node;
85 return (
86 name === 'slot' || (name === 'script' && attribs.type === 'slot')
87 );
88 }
89
90 /**
91 * Checks if the node is one of the nodes passed through handleTags
92 *
93 * @returns {Boolean}
94 */
95 _isSpecialNode(node) {
96 const { attribs = {}, name } = node;
97 return (
98 this.handleTags.includes(name) ||
99 (name === 'script' && this.handleTags.includes(attribs.type))
100 );
101 }
102
103 /**
104 * Checks if the node is the lastChild of <body>
105 *
106 * @returns {Boolean}
107 */
108 _isLastChildOfBody(node) {
109 const { parentNode: { name, lastChild } } = node;
110 return name === 'body' && Object.is(node, lastChild);
111 }
112
113 /**
114 * Serialize the nodes passed via handleTags
115 *
116 * @param {object} node
117 *
118 * // Input
119 * <fragment src="http://example.com" async primary></fragment>
120 *
121 * // Output
122 * {
123 * name: 'fragment',
124 * attributes: {
125 * async: '',
126 * primary: ''
127 * },
128 * }
129 */
130 _serializeSpecial(node) {
131 this.pushBuffer();
132 let handledObj;
133 const { name, attribs: attributes } = node;
134 if (this.handleTags.includes(name)) {
135 handledObj = Object.assign({}, { name: name, attributes });
136 this.serializedList.push(handledObj);
137 this._serializeChildNodes(node);
138 this.pushBuffer();
139 } else {
140 // For handling the script type other than text/javascript
141 this._serializeChildNodes(node);
142 handledObj = Object.assign(
143 {},
144 {
145 name: attributes.type,
146 attributes,
147 textContent: this.getHtmlContent()
148 }
149 );
150 this.serializedList.push(handledObj);
151 }
152 this.serializedList.push({ closingTag: name });
153 }
154
155 /**
156 * Serialize the slot nodes from the slot map
157 *
158 * @param {object} node
159 */
160 _serializeSlot(node) {
161 const slotName = node.attribs.name;
162 if (slotName) {
163 const childNodes = this.treeAdapter.getChildNodes(node);
164 const slots = this.slotMap.has(slotName)
165 ? this.slotMap.get(slotName)
166 : childNodes;
167 slots && slots.forEach(this._serializeNode);
168 } else {
169 // Handling duplicate slots
170 if (this.defaultSlotsInserted) {
171 console.warn(
172 'Encountered duplicate Unnamed slots in the template - Skipping the node'
173 );
174 return;
175 }
176 const defaultSlots = this.slotMap.get('default');
177 this.defaultSlotsInserted = true;
178 defaultSlots && defaultSlots.forEach(this._serializeNode);
179 }
180 }
181
182 /**
183 * Insert the pipe placeholder and serialize the node
184 *
185 * @param {object} node
186 */
187 _serializePipe(node) {
188 this.pushBuffer();
189 this.serializedList.push({ placeholder: 'pipe' });
190 this.isPipeInserted = true;
191 this._serializeNode(node);
192 }
193
194 /**
195 * Serialize the nodes in default slot from slot map and insert async placeholder.
196 *
197 * should happen before closing the body.
198 */
199 _serializeRest() {
200 this.lastChildInserted = true;
201 if (!this.defaultSlotsInserted) {
202 const defaultSlots = this.slotMap.get('default');
203 defaultSlots && defaultSlots.forEach(this._serializeNode);
204 }
205 this.pushBuffer();
206 this.serializedList.push({ placeholder: 'async' });
207 }
208
209 /**
210 * Serialize all the children of a parent node.
211 *
212 * @param {object} parentNode
213 */
214 _serializeChildNodes(parentNode) {
215 const childNodes = this.treeAdapter.getChildNodes(parentNode);
216 childNodes && childNodes.forEach(this._serializeNode);
217 }
218
219 /**
220 * Serialize the node based on their type
221 *
222 * @param {object} currentNode
223 */
224 _serializeNode(currentNode) {
225 if (
226 this.fullRendering &&
227 !this.isPipeInserted &&
228 this._isPipeNode(currentNode)
229 ) {
230 this._serializePipe(currentNode);
231 } else if (this._isSpecialNode(currentNode)) {
232 this._serializeSpecial(currentNode);
233 } else if (this._isSlotNode(currentNode)) {
234 this._serializeSlot(currentNode);
235 } else if (this.treeAdapter.isElementNode(currentNode)) {
236 this._serializeElement(currentNode);
237 } else if (this.treeAdapter.isTextNode(currentNode)) {
238 this._serializeTextNode(currentNode);
239 } else if (this.treeAdapter.isCommentNode(currentNode)) {
240 this._serializeCommentNode(currentNode);
241 } else if (this.treeAdapter.isDocumentTypeNode(currentNode)) {
242 this._serializeDocumentTypeNode(currentNode);
243 }
244 // Push default slots and async placeholder before body
245 if (
246 this.fullRendering &&
247 !this.lastChildInserted &&
248 this._isLastChildOfBody(currentNode)
249 ) {
250 this._serializeRest();
251 }
252 }
253};