1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', { value: true });
|
4 |
|
5 | var preact = require('preact');
|
6 | var CACHE = require('gcache');
|
7 |
|
8 | class Router {}
|
9 |
|
10 | const META_TYPES = ['name', 'httpEquiv', 'charSet', 'itemProp'];
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | function unique() {
|
16 | const tags = [];
|
17 | const metaTypes = [];
|
18 | const metaCategories = {};
|
19 | return h => {
|
20 | switch (h.nodeName) {
|
21 | case 'title':
|
22 | case 'base':
|
23 | if (~tags.indexOf(h.nodeName)) return false
|
24 | tags.push(h.nodeName);
|
25 | break
|
26 | case 'meta':
|
27 | for (let i = 0, len = META_TYPES.length; i < len; i++) {
|
28 | const metatype = META_TYPES[i];
|
29 | if (!h.attributes.hasOwnProperty(metatype)) continue
|
30 | if (metatype === 'charSet') {
|
31 | if (~metaTypes.indexOf(metatype)) return false
|
32 | metaTypes.push(metatype);
|
33 | } else {
|
34 | const category = h.attributes[metatype];
|
35 | const categories = metaCategories[metatype] || [];
|
36 | if (~categories.indexOf(category)) return false
|
37 | categories.push(category);
|
38 | metaCategories[metatype] = categories;
|
39 | }
|
40 | }
|
41 | break
|
42 | }
|
43 | return true
|
44 | }
|
45 | }
|
46 |
|
47 | class Head extends preact.Component {
|
48 | render() {
|
49 | const docProps = this.context._documentProps;
|
50 | let children = this.props.children || [];
|
51 |
|
52 |
|
53 |
|
54 | if (!docProps) {
|
55 | return preact.h('head', {}, children)
|
56 | }
|
57 |
|
58 |
|
59 |
|
60 | if (docProps.heads) {
|
61 | children = []
|
62 | .concat(docProps.heads)
|
63 | .concat(children)
|
64 | .filter(unique());
|
65 | }
|
66 |
|
67 |
|
68 | if (docProps.styles) {
|
69 | children = [].concat(children).concat(docProps.styles);
|
70 | }
|
71 |
|
72 |
|
73 | if (children.length == 0) {
|
74 | return preact.h('head')
|
75 | }
|
76 |
|
77 | return preact.h('head', {}, children)
|
78 | }
|
79 | }
|
80 |
|
81 | class Script extends preact.Component {
|
82 | render() {
|
83 | const docProps = this.context._documentProps;
|
84 |
|
85 |
|
86 |
|
87 | if (!docProps || !docProps.scripts) {
|
88 | return null
|
89 | }
|
90 |
|
91 | return preact.h('div', { id: 'elmo-scripts' }, [
|
92 | docProps.scripts.map(script => preact.h('script', { src: script }))
|
93 | ])
|
94 | }
|
95 | }
|
96 |
|
97 | class Page extends preact.Component {
|
98 | render() {
|
99 | const docProps = this.context._documentProps;
|
100 |
|
101 |
|
102 |
|
103 | if (!docProps || typeof docProps.pageHTML === 'undefined') {
|
104 | return null
|
105 | }
|
106 |
|
107 | return preact.h('div', {
|
108 | id: 'elmo',
|
109 | dangerouslySetInnerHTML: { __html: docProps.pageHTML }
|
110 | })
|
111 | }
|
112 | }
|
113 |
|
114 | class Document extends preact.Component {
|
115 | getChildContext() {
|
116 | return { _documentProps: this.props }
|
117 | }
|
118 |
|
119 |
|
120 |
|
121 | render() {
|
122 | return preact.h('html', {}, [
|
123 | preact.h(Head, {}),
|
124 | preact.h('body', {}, [preact.h(Page, {}), preact.h(Script, {})])
|
125 | ])
|
126 | }
|
127 | }
|
128 |
|
129 |
|
130 | Document.Head = Head;
|
131 | Document.Script = Script;
|
132 | Document.Page = Page;
|
133 |
|
134 | const VALID_TYPES = {
|
135 | title: true,
|
136 | meta: true,
|
137 | base: true,
|
138 | link: true,
|
139 | style: true,
|
140 | script: true
|
141 | };
|
142 | const IS_BROWSER = typeof window !== 'undefined';
|
143 | const MARKER = 'elmo-head';
|
144 | const ATTR_MAP = {
|
145 | acceptCharset: 'accept-charset',
|
146 | className: 'class',
|
147 | htmlFor: 'for',
|
148 | httpEquiv: 'http-equiv'
|
149 | };
|
150 |
|
151 |
|
152 |
|
153 | CACHE.set('heads', []);
|
154 |
|
155 |
|
156 | function update() {
|
157 | if (!IS_BROWSER) return
|
158 | updateClient(CACHE.get('heads'));
|
159 | }
|
160 |
|
161 |
|
162 | function updateClient(headComponents) {
|
163 | const vnodes = flatten(headComponents);
|
164 | const buckets = {};
|
165 |
|
166 |
|
167 | for (let i = 0; i < vnodes.length; i++) {
|
168 | const vnode = vnodes[i];
|
169 | const nodeName = vnode.nodeName;
|
170 | if (typeof nodeName !== 'string') continue
|
171 | if (!VALID_TYPES[nodeName]) continue
|
172 | const bucket = buckets[nodeName] || [];
|
173 | bucket.push(vnode);
|
174 | buckets[nodeName] = bucket;
|
175 | }
|
176 |
|
177 |
|
178 | if (buckets.title) {
|
179 | syncTitle(buckets.title[0]);
|
180 | }
|
181 |
|
182 |
|
183 | for (let type in VALID_TYPES) {
|
184 | if (type === 'title') continue
|
185 | syncElements(type, buckets[type] || []);
|
186 | }
|
187 | }
|
188 |
|
189 |
|
190 | function flatten(headComponents) {
|
191 | let children = [];
|
192 |
|
193 | for (let i = 0; i < headComponents.length; i++) {
|
194 | const head = headComponents[i];
|
195 | if (!head.props || !head.props.children) continue
|
196 | children = children.concat(head.props.children || []);
|
197 | }
|
198 |
|
199 |
|
200 |
|
201 |
|
202 | children = children
|
203 | .reverse()
|
204 | .filter(unique())
|
205 | .reverse();
|
206 |
|
207 | const results = [];
|
208 | for (let i = 0; i < children.length; i++) {
|
209 | const child = children[i];
|
210 |
|
211 | if (typeof child === 'string' || !('nodeName' in child)) {
|
212 | results.push(child);
|
213 | continue
|
214 | }
|
215 |
|
216 | if (!VALID_TYPES[child.nodeName]) {
|
217 | continue
|
218 | }
|
219 |
|
220 |
|
221 | const attrs = child.attributes || {};
|
222 | const className = attrs.className ? `${attrs.className} ${MARKER}` : MARKER;
|
223 | results.push(preact.cloneElement(child, { className }));
|
224 | }
|
225 |
|
226 | return results
|
227 | }
|
228 |
|
229 |
|
230 | function syncTitle(vnode) {
|
231 | const title = [].concat(vnode.children).join('');
|
232 | if (title !== document.title) document.title = title;
|
233 | }
|
234 |
|
235 |
|
236 | function syncElements(type, vnodes) {
|
237 | const headElement = document.getElementsByTagName('head')[0];
|
238 | const oldNodes = Array.prototype.slice.call(
|
239 | headElement.querySelectorAll(type + '.' + MARKER)
|
240 | );
|
241 |
|
242 | const newNodes = [];
|
243 | for (let i = 0; i < vnodes.length; i++) {
|
244 | newNodes.push(vnodeToDOMNode(vnodes[i]));
|
245 | }
|
246 |
|
247 |
|
248 | const dels = [];
|
249 | for (let i = 0; i < oldNodes.length; i++) {
|
250 | const oldNode = oldNodes[i];
|
251 | let found = false;
|
252 | for (let j = 0; j < newNodes.length; j++) {
|
253 | if (oldNode.isEqualNode(newNodes[j])) {
|
254 | found = true;
|
255 | break
|
256 | }
|
257 | }
|
258 | if (!found) {
|
259 | dels.push(oldNode);
|
260 | }
|
261 | }
|
262 |
|
263 |
|
264 | const adds = [];
|
265 | for (let i = 0; i < newNodes.length; i++) {
|
266 | const newNode = newNodes[i];
|
267 | let found = false;
|
268 | for (let j = 0; j < oldNodes.length; j++) {
|
269 | if (newNode.isEqualNode(oldNodes[j])) {
|
270 | found = true;
|
271 | break
|
272 | }
|
273 | }
|
274 | if (!found) {
|
275 | adds.push(newNode);
|
276 | }
|
277 | }
|
278 |
|
279 |
|
280 | for (let i = 0; i < dels.length; i++) {
|
281 | const node = dels[i];
|
282 | if (!node.parentNode) continue
|
283 | node.parentNode.removeChild(node);
|
284 | }
|
285 |
|
286 |
|
287 | for (let i = 0; i < adds.length; i++) {
|
288 | const node = adds[i];
|
289 | headElement.appendChild(node);
|
290 | }
|
291 | }
|
292 |
|
293 |
|
294 | function vnodeToDOMNode(vnode) {
|
295 | const el = document.createElement(vnode.nodeName);
|
296 | const attrs = vnode.attributes || {};
|
297 | const children = vnode.children;
|
298 | for (const p in attrs) {
|
299 | if (!attrs.hasOwnProperty(p)) continue
|
300 | if (p === 'dangerouslySetInnerHTML') continue
|
301 | const attr = ATTR_MAP[p] || p.toLowerCase();
|
302 | el.setAttribute(attr, attrs[p]);
|
303 | }
|
304 | if (attrs['dangerouslySetInnerHTML']) {
|
305 | el.innerHTML = attrs['dangerouslySetInnerHTML'].__html || '';
|
306 | } else if (children) {
|
307 | el.textContent = typeof children === 'string' ? children : children.join('');
|
308 | }
|
309 | return el
|
310 | }
|
311 |
|
312 |
|
313 | class Head$1 extends preact.Component {
|
314 |
|
315 |
|
316 | componentWillMount() {
|
317 | const heads = CACHE.get('heads');
|
318 | CACHE.set('heads', heads.concat(this));
|
319 | update();
|
320 | }
|
321 |
|
322 | static rewind() {
|
323 | const children = flatten(CACHE.get('heads'));
|
324 | CACHE.set('heads', []);
|
325 | return children
|
326 | }
|
327 |
|
328 | componentDidUpdate() {
|
329 | update();
|
330 | }
|
331 |
|
332 | componentWillUnmount() {
|
333 | const heads = CACHE.get('heads');
|
334 | const i = heads.indexOf(this);
|
335 | const updated = [];
|
336 | heads.forEach((head, j) => {
|
337 | if (j === i) return
|
338 | updated.push(head);
|
339 | });
|
340 | CACHE.set('heads', updated);
|
341 | update();
|
342 | }
|
343 |
|
344 | render() {
|
345 | return null
|
346 | }
|
347 | }
|
348 |
|
349 | exports.Document = Document;
|
350 | exports.Head = Head$1;
|
351 | exports.Router = Router;
|