UNPKG

9.25 kBJavaScriptView Raw
1// the main filter function
2module.exports = function (s, data) {
3 var minimatch = require('minimatch');
4 var Promise = require('bluebird');
5 var jsdom = require('jsdom');
6 var streamRead = require('./stream');
7 var concator = require('./concator');
8
9 var Jsdom = jsdom.JSDOM;
10 var VirtualConsole = jsdom.VirtualConsole;
11
12 var route = this.route;
13 var logger = this.log || console;
14 var root = this.config.root;
15 var config = this.config.filter_optimize;
16 var css = config.css;
17 var js = config.js;
18 var removeComments = config.remove_comments;
19
20 var list = route.list();
21
22 // only filter html files.
23 var htmls = list.filter(function (path) {
24 return minimatch(path, '**/*.html', { nocase: true });
25 });
26
27 // check whether `path` is in `excludes`
28 var inExcludes = function (path, excludes) {
29 for (var i = 0; i < excludes.length; i++) {
30 if (minimatch(path, excludes[i], { nocase: true })) {
31 return true;
32 }
33 }
34 return false;
35 };
36
37 css.excludes = css.excludes || [];
38 css.inlines = css.inlines || ['css/main.css'];
39 js.excludes = js.excludes || [];
40
41 // grab the defined inline css files
42 var inlineCssText = '';
43 var first = Promise.resolve();
44
45 if (css.enable) {
46 var inlines = list.filter(function (path) {
47 if (inExcludes(path, css.excludes)) {
48 return false;
49 }
50 return css.inlines.indexOf(path) >= 0;
51 });
52
53 if (inlines.length > 0) {
54 first = concator.bundleFiles(inlines, list, route, '', removeComments)
55 .then(function (content) {
56 inlineCssText = content;
57 }).catch(function (err) {
58 logger.log('Errors when get the inline css: ', err);
59 });
60 }
61 }
62
63 // other parameters
64 var bundleJsList = [];
65 var bundleCssList = [];
66
67 // start to optimize the html defination
68 var vc = new VirtualConsole();
69 // to avoid some unnecessary css parsing information
70 return first.then(function () {
71 return Promise.map(htmls, function (path) {
72 var stream = route.get(path);
73 return streamRead(stream)
74 .then(function (str) {
75 // load jsdom from the html string
76 var dom = new Jsdom(str, { virtualConsole: vc });
77 var doc = dom.window && dom.window.document;
78 if (doc == null) {
79 return;
80 }
81
82 var links = Array.from(doc.querySelectorAll('link'));
83 if (links.length <= 0) {
84 // something other static resources, skip
85 return;
86 }
87
88 // console.log('processing: ' + path);
89
90 var regQuery = /\?v=[\d.]*$/;
91
92 var cssCode = '';
93 var cssList = [];
94 var hasInlines = false;
95 var hasDelivery = false;
96 if (config.remove_query_string) {
97 links.filter(function (el) { return regQuery.test(el.href); })
98 .forEach(function (el) {
99 el.href = el.href.replace(regQuery, '');
100 });
101 }
102 if (css.enable) {
103 links
104 .filter(function (el) { return el.rel === 'stylesheet'; })
105 .forEach(function (el) {
106 var href = el.href;
107 if (inExcludes(href, css.excludes)) {
108 return;
109 }
110 var isCssBundle = false;
111 if (css.inlines.filter(function (p) {
112 return href.indexOf(p) >= 0;
113 }).length > 0) {
114 hasInlines = true;
115 } else {
116 if (href[0] === '/' && href[1] !== '/') {
117 if (bundleCssList.indexOf(href) < 0) {
118 bundleCssList.push(href);
119 isCssBundle = true;
120 }
121 }
122 if (!isCssBundle && bundleCssList.indexOf(href) < 0) {
123 cssCode += 'loadCss(\'' + href + '\');';
124 cssList.push(href);
125 }
126 hasDelivery = true;
127 }
128 el.remove();
129 });
130 }
131
132 if (js.bundle || config.remove_query_string) {
133 var scripts = Array.from(doc.querySelectorAll('script'));
134 var scriptText = null;
135 scripts.forEach(function (el) {
136 var src = el.src;
137 if (config.remove_query_string && regQuery.test(src)) {
138 el.src = src.replace(regQuery, '');
139 }
140 if (js.bundle) {
141 var isJsBundle = false;
142 // skip the `hexo.configurations` block,
143 // to avoid the NexT is not defined error.
144 if (el.id !== 'hexo.configurations' && scriptText != null
145 // is text script block
146 && !src
147 // and has the content
148 && el.textContent && el.textContent.length > 0) {
149 // record it
150 scriptText = concator.combine(scriptText, el.textContent,
151 ';', removeComments);
152 el.remove();
153 } else if (src && src[0] === '/' && src[1] !== '/') {
154 if (bundleJsList.indexOf(src) < 0) {
155 bundleJsList.push(src);
156 isJsBundle = true;
157 }
158 }
159 if (isJsBundle || bundleJsList.indexOf(src) >= 0) {
160 if (scriptText == null) {
161 scriptText = '';
162 }
163 el.remove();
164 }
165 }
166 });
167
168 if (bundleJsList.length > 0) {
169 var bundleJs = doc.createElement('script');
170 bundleJs.type = 'text/javascript';
171 bundleJs.src = root + 'bundle.js';
172 doc.body.appendChild(bundleJs);
173 }
174
175 if (scriptText != null && scriptText.length > 0) {
176 var textScript = doc.createElement('script');
177 textScript.type = 'text/javascript';
178 textScript.textContent = scriptText;
179 doc.body.appendChild(textScript);
180 }
181 }
182
183 var changed = bundleJsList.length > 0;
184 // if there is any css need to delivery
185 if (hasDelivery) {
186 var cssElement = doc.createElement('script');
187 if (bundleCssList.length > 0) {
188 cssCode = 'loadCss(\'' + root + 'style.css\');' + cssCode;
189 }
190 // eslint-disable-next-line
191 cssCode = "function loadCss(l){var d=document,h=d.head,s=d.createElement('link');s.rel='stylesheet';s.href=l;!function e(f){if (d.body)return f();setTimeout(function(){e(f)})}(function(){h.appendChild(s);});}"
192 + cssCode;
193 cssElement.textContent = cssCode;
194 doc.head.appendChild(cssElement);
195 // add the noscript block to make sure the css will be loaded
196 if (cssList != null && cssList.length > 0) {
197 var ns = doc.createElement('noscript');
198 var c;
199 if (bundleCssList.length > 0) {
200 c = doc.createElement('link');
201 c.rel = 'stylesheet';
202 c.href = root + 'style.css';
203 ns.appendChild(c);
204 }
205 for (var i = 0; i < cssList.length; i++) {
206 c = doc.createElement('link');
207 c.rel = 'stylesheet';
208 c.href = cssList[i];
209 ns.appendChild(c);
210 }
211 doc.head.appendChild(ns);
212 }
213 changed = true;
214 }
215
216 var noscripts = doc.getElementsByTagName('noscript');
217 var noscript;
218 if (noscripts.length > 0 && noscripts[0].parentNode === doc.head) {
219 noscript = noscripts[0];
220 }
221
222 // if there is some inline-styles need to be inserted
223 if (hasInlines && inlineCssText.length > 0) {
224 var main = doc.createElement('style');
225 main.textContent = inlineCssText;
226 if (noscript != null) {
227 // avoiding to overmit the noscript css
228 doc.head.insertBefore(main, noscript);
229 } else {
230 doc.head.appendChild(main);
231 }
232 changed = true;
233 }
234
235 if (changed) {
236 // get the replaced string
237 str = dom.serialize();
238 route.set(path, str);
239 }
240 });
241 });
242
243
244 /**
245 * make javascript bundle file
246 */
247 }).then(function () {
248 var p;
249 if (bundleCssList.length > 0) {
250 p = concator
251 .bundleFiles(bundleCssList, list, route, '', removeComments)
252 .then(function (content) {
253 return new Promise(function (resolve) {
254 route.set(root + 'style.css', content);
255 resolve();
256 });
257 });
258 } else {
259 p = Promise.resolve();
260 }
261
262 if (bundleJsList.length > 0) {
263 p = p.then(function () {
264 return concator
265 .bundleFiles(bundleJsList, list, route, ';', removeComments)
266 .then(function (content) {
267 return new Promise(function (resolve) {
268 route.set(root + 'bundle.js', content);
269 resolve();
270 });
271 });
272 });
273 }
274
275 return p;
276 });
277};