UNPKG

9.23 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
6
7const Asset = require('../Asset');
8
9const api = require('posthtml/lib/api');
10
11const urlJoin = require('../utils/urlJoin');
12
13const render = require('posthtml-render');
14
15const posthtmlTransform = require('../transforms/posthtml');
16
17const htmlnanoTransform = require('../transforms/htmlnano');
18
19const isURL = require('../utils/is-url'); // A list of all attributes that may produce a dependency
20// Based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
21
22
23const ATTRS = {
24 src: ['script', 'img', 'audio', 'video', 'source', 'track', 'iframe', 'embed'],
25 href: ['link', 'a', 'use'],
26 srcset: ['img', 'source'],
27 poster: ['video'],
28 'xlink:href': ['use', 'image'],
29 content: ['meta'],
30 data: ['object']
31}; // A list of metadata that should produce a dependency
32// Based on:
33// - http://schema.org/
34// - http://ogp.me
35// - https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/markup
36// - https://msdn.microsoft.com/en-us/library/dn255024.aspx
37
38const META = {
39 property: ['og:image', 'og:image:url', 'og:image:secure_url', 'og:audio', 'og:audio:secure_url', 'og:video', 'og:video:secure_url'],
40 name: ['twitter:image', 'msapplication-square150x150logo', 'msapplication-square310x310logo', 'msapplication-square70x70logo', 'msapplication-wide310x150logo', 'msapplication-TileImage', 'msapplication-config'],
41 itemprop: ['image', 'logo', 'screenshot', 'thumbnailUrl', 'contentUrl', 'downloadUrl']
42};
43const SCRIPT_TYPES = {
44 'application/javascript': 'js',
45 'text/javascript': 'js',
46 'application/json': false,
47 'application/ld+json': 'jsonld',
48 'text/html': false
49}; // Options to be passed to `addURLDependency` for certain tags + attributes
50
51const OPTIONS = {
52 a: {
53 href: {
54 entry: true
55 }
56 },
57 iframe: {
58 src: {
59 entry: true
60 }
61 }
62};
63
64class HTMLAsset extends Asset {
65 constructor(name, options) {
66 super(name, options);
67 this.type = 'html';
68 this.isAstDirty = false;
69 this.hmrPageReload = true;
70 }
71
72 parse(code) {
73 var _this = this;
74
75 return (0, _asyncToGenerator2.default)(function* () {
76 let res = yield posthtmlTransform.parse(code, _this);
77 res.walk = api.walk;
78 res.match = api.match;
79 return res;
80 })();
81 }
82
83 processSingleDependency(path, opts) {
84 let assetPath = this.addURLDependency(path, opts);
85
86 if (!isURL(assetPath)) {
87 assetPath = urlJoin(this.options.publicURL, assetPath);
88 }
89
90 return assetPath;
91 }
92
93 collectSrcSetDependencies(srcset, opts) {
94 const newSources = [];
95 var _iteratorNormalCompletion = true;
96 var _didIteratorError = false;
97 var _iteratorError = undefined;
98
99 try {
100 for (var _iterator = srcset.split(',')[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
101 const source = _step.value;
102 const pair = source.trim().split(' ');
103 if (pair.length === 0) continue;
104 pair[0] = this.processSingleDependency(pair[0], opts);
105 newSources.push(pair.join(' '));
106 }
107 } catch (err) {
108 _didIteratorError = true;
109 _iteratorError = err;
110 } finally {
111 try {
112 if (!_iteratorNormalCompletion && _iterator.return != null) {
113 _iterator.return();
114 }
115 } finally {
116 if (_didIteratorError) {
117 throw _iteratorError;
118 }
119 }
120 }
121
122 return newSources.join(',');
123 }
124
125 getAttrDepHandler(attr) {
126 if (attr === 'srcset') {
127 return this.collectSrcSetDependencies;
128 }
129
130 return this.processSingleDependency;
131 }
132
133 collectDependencies() {
134 let ast = this.ast; // Add bundled dependencies from plugins like posthtml-extend or posthtml-include, if any
135
136 if (ast.messages) {
137 ast.messages.forEach(message => {
138 if (message.type === 'dependency') {
139 this.addDependency(message.file, {
140 includedInParent: true
141 });
142 }
143 });
144 }
145
146 ast.walk(node => {
147 if (node.attrs) {
148 if (node.tag === 'meta') {
149 if (!Object.keys(node.attrs).some(attr => {
150 let values = META[attr];
151 return values && values.includes(node.attrs[attr]) && node.attrs.content !== '';
152 })) {
153 return node;
154 }
155 }
156
157 if (node.tag === 'link' && node.attrs.rel === 'manifest' && node.attrs.href) {
158 node.attrs.href = this.getAttrDepHandler('href').call(this, node.attrs.href, {
159 entry: true
160 });
161 this.isAstDirty = true;
162 return node;
163 }
164
165 for (let attr in node.attrs) {
166 const attrVal = node.attrs[attr];
167
168 if (!attrVal) {
169 continue;
170 } // Check for virtual paths
171
172
173 if (node.tag === 'a' && attrVal.lastIndexOf('.') < 1) {
174 continue;
175 }
176
177 let elements = ATTRS[attr];
178
179 if (elements && elements.includes(node.tag)) {
180 let depHandler = this.getAttrDepHandler(attr);
181 let options = OPTIONS[node.tag];
182 node.attrs[attr] = depHandler.call(this, attrVal, options && options[attr]);
183 this.isAstDirty = true;
184 }
185 }
186 }
187
188 return node;
189 });
190 }
191
192 pretransform() {
193 var _this2 = this;
194
195 return (0, _asyncToGenerator2.default)(function* () {
196 yield posthtmlTransform.transform(_this2);
197 })();
198 }
199
200 transform() {
201 var _this3 = this;
202
203 return (0, _asyncToGenerator2.default)(function* () {
204 if (_this3.options.minify) {
205 yield htmlnanoTransform(_this3);
206 }
207 })();
208 }
209
210 generate() {
211 var _this4 = this;
212
213 return (0, _asyncToGenerator2.default)(function* () {
214 // Extract inline <script> and <style> tags for processing.
215 let parts = [];
216
217 if (_this4.ast) {
218 _this4.ast.walk(node => {
219 if (node.tag === 'script' || node.tag === 'style') {
220 let value = node.content && node.content.join('').trim();
221
222 if (value) {
223 let type;
224
225 if (node.tag === 'style') {
226 if (node.attrs && node.attrs.type) {
227 type = node.attrs.type.split('/')[1];
228 } else {
229 type = 'css';
230 }
231 } else if (node.attrs && node.attrs.type) {
232 // Skip JSON
233 if (SCRIPT_TYPES[node.attrs.type] === false) {
234 return node;
235 }
236
237 if (SCRIPT_TYPES[node.attrs.type]) {
238 type = SCRIPT_TYPES[node.attrs.type];
239 } else {
240 type = node.attrs.type.split('/')[1];
241 }
242 } else {
243 type = 'js';
244 }
245
246 parts.push({
247 type,
248 value,
249 inlineHTML: true,
250 meta: {
251 type: 'tag',
252 node
253 }
254 });
255 }
256 } // Process inline style attributes.
257
258
259 if (node.attrs && node.attrs.style) {
260 parts.push({
261 type: 'css',
262 value: node.attrs.style,
263 meta: {
264 type: 'attr',
265 node
266 }
267 });
268 }
269
270 return node;
271 });
272 }
273
274 return parts;
275 })();
276 }
277
278 postProcess(generated) {
279 var _this5 = this;
280
281 return (0, _asyncToGenerator2.default)(function* () {
282 // Replace inline scripts and styles with processed results.
283 var _iteratorNormalCompletion2 = true;
284 var _didIteratorError2 = false;
285 var _iteratorError2 = undefined;
286
287 try {
288 for (var _iterator2 = generated[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
289 let rendition = _step2.value;
290 let _rendition$meta = rendition.meta,
291 type = _rendition$meta.type,
292 node = _rendition$meta.node;
293
294 if (type === 'attr' && rendition.type === 'css') {
295 node.attrs.style = rendition.value;
296 } else if (type === 'tag') {
297 if (rendition.isMain) {
298 node.content = rendition.value;
299 } // Delete "type" attribute, since CSS and JS are the defaults.
300 // Unless it's application/ld+json
301
302
303 if (node.attrs && (node.tag === 'style' || node.attrs.type && SCRIPT_TYPES[node.attrs.type] === 'js')) {
304 delete node.attrs.type;
305 }
306 }
307 }
308 } catch (err) {
309 _didIteratorError2 = true;
310 _iteratorError2 = err;
311 } finally {
312 try {
313 if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
314 _iterator2.return();
315 }
316 } finally {
317 if (_didIteratorError2) {
318 throw _iteratorError2;
319 }
320 }
321 }
322
323 return [{
324 type: 'html',
325 value: render(_this5.ast)
326 }];
327 })();
328 }
329
330}
331
332module.exports = HTMLAsset;
\No newline at end of file