1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | const path = require('path');
|
9 | const cheerio = require('cheerio');
|
10 |
|
11 |
|
12 | const jdfUtils = require('jdf-utils');
|
13 | const $ = jdfUtils.base;
|
14 | const f = jdfUtils.file;
|
15 | const jdf = require('./jdf.js');
|
16 | const shelljs = require('shelljs');
|
17 | const logger = require('jdf-log');
|
18 | const VFS = require('./VFS/VirtualFileSystem');
|
19 | const _ = require("lodash");
|
20 | const esr = require('escape-string-regexp');
|
21 |
|
22 | const jsAst = require('./jsAst');
|
23 |
|
24 |
|
25 | const urlReplace = module.exports = {};
|
26 |
|
27 | urlReplace.init = function (options) {
|
28 | var outputType = options.outputType;
|
29 |
|
30 | if (outputType == 'plain') {
|
31 | return Promise.resolve();
|
32 | }
|
33 | logger.profile('replace url');
|
34 |
|
35 | return VFS.go()
|
36 | .then(() => {
|
37 | return VFS.travel((vfile, done) => {
|
38 | var config = jdf.config;
|
39 | var cdn = config.cdn;
|
40 | var source = vfile.targetPath;
|
41 | var content = vfile.targetContent;
|
42 |
|
43 | var linkReplace = config.output.linkReplace,
|
44 | cssImagesUrlReplace = config.output.cssImagesUrlReplace,
|
45 | scriptReplace = config.output.jsUrlReplace,
|
46 | jsUrlReplace = config.output.jsUrlReplace;
|
47 |
|
48 | if ($.is.html(source)) {
|
49 | logger.verbose(`replace url: ${source}`);
|
50 |
|
51 | let $$ = cheerio.load(content, {
|
52 | decodeEntities: false
|
53 | });
|
54 |
|
55 |
|
56 | if (scriptReplace) {
|
57 | $$('script').each(function (index, script) {
|
58 | var src = $$(script).attr('src');
|
59 | var type = $$(script).attr('type');
|
60 | var inlineScript = $$(script).html();
|
61 |
|
62 | if(!type || type == 'text/javascript'){
|
63 | if(inlineScript){
|
64 | $$(script).text(jsAstMain(inlineScript, vfile.originPath));
|
65 | }
|
66 | }
|
67 |
|
68 | if (!$.is.httpLink(src) && src) {
|
69 | let cdnSrc = urlReplace.addSourceCdn(source, src);
|
70 |
|
71 | if (cdnSrc) {
|
72 | $$(script).attr('src', cdnSrc);
|
73 | }
|
74 | }
|
75 | });
|
76 | }
|
77 |
|
78 |
|
79 | if (linkReplace) {
|
80 | $$('link').each(function (index, link) {
|
81 | var src = $$(link).attr('href');
|
82 | var rel = $$(link).attr('rel');
|
83 | var type = $$(link).attr('type');
|
84 |
|
85 | if (rel && rel.toLowerCase() !== 'stylesheet') {
|
86 | return;
|
87 | }
|
88 |
|
89 | if (type && type.toLowerCase() !== 'text/css') {
|
90 | return;
|
91 | }
|
92 |
|
93 | if (!$.is.httpLink(src)) {
|
94 | let cdnSrc = urlReplace.addSourceCdn(source, src);
|
95 |
|
96 | if (cdnSrc) {
|
97 | $$(link).attr('href', cdnSrc);
|
98 | }
|
99 | }
|
100 | });
|
101 | }
|
102 |
|
103 | content = $$.html();
|
104 | if(outputType != 'plain' && outputType != 'debug'){
|
105 | logger.verbose(`combo url: ${source}`);
|
106 | content = urlReplace.comboUrlPath(content);
|
107 | }
|
108 | }
|
109 |
|
110 | if ($.is.js(source) && jsUrlReplace) {
|
111 | logger.verbose(`replace url: ${source}`);
|
112 | content = jsAstMain(content, vfile.originPath);
|
113 | }
|
114 |
|
115 | if ($.is.css(source) && cssImagesUrlReplace) {
|
116 | logger.verbose(`replace url: ${source}`);
|
117 | content = urlReplace.cssImagesUrlReplace(content, function (url) {
|
118 | return urlReplace.addSourceCdn(source, url);
|
119 | });
|
120 | }
|
121 |
|
122 | vfile.targetContent = content;
|
123 |
|
124 | function jsAstMain(content, filepath){
|
125 | return jsAst.main(content, {filepath: filepath}, function(nodeObj){
|
126 | var name = nodeObj.name;
|
127 | var src = nodeObj.str;
|
128 | var loadType = nodeObj.loadType;
|
129 |
|
130 | name = urlReplace.addSourceCdn(source, name, loadType);
|
131 | src = urlReplace.addSourceCdn(source, src, loadType);
|
132 |
|
133 | nodeObj.name = name;
|
134 | nodeObj.str = src;
|
135 |
|
136 | return nodeObj;
|
137 | });
|
138 | }
|
139 | });
|
140 | }).then(() => {
|
141 | logger.profile('replace url');
|
142 | }, err => {
|
143 | console.log(err);
|
144 | });
|
145 | }
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 | urlReplace.comboUrlPath = function (content) {
|
153 | let $$ = cheerio.load(content, {
|
154 | decodeEntities: false
|
155 | });
|
156 |
|
157 | |
158 |
|
159 |
|
160 |
|
161 | function getShortest(arr) {
|
162 | if (arr.length == 0) {
|
163 | return false;
|
164 | }
|
165 | let idx = 0;
|
166 | let isSame = true;
|
167 | while (isSame) {
|
168 | let item4Idx;
|
169 | for (let i = 0; i < arr.length; i++) {
|
170 | let item = arr[i];
|
171 | if (item.length == 0 || item[idx] == undefined) {
|
172 | isSame = false;
|
173 | break;
|
174 | }
|
175 | if (!item4Idx) item4Idx = item[idx];
|
176 | if (item4Idx != item[idx]) {
|
177 | isSame = false;
|
178 | break;
|
179 | }
|
180 | }
|
181 | idx++;
|
182 | }
|
183 | return arr[0].slice(0, idx - 1)
|
184 | };
|
185 |
|
186 | let cdn = jdf.config.cdn;
|
187 |
|
188 | function comboByHtmlTag(tagArr, attr) {
|
189 | let resultArr = [];
|
190 | tagArr.forEach((tag, index) => {
|
191 | let url = $$(tag).attr(attr);
|
192 | url = url.replace(cdn, "");
|
193 | url = _.trim(url, '/');
|
194 | resultArr.push(url.split("/"));
|
195 | });
|
196 |
|
197 |
|
198 | let commonPart;
|
199 | commonPart = getShortest(resultArr).join("/");
|
200 | commonPart = _.trim(commonPart, "/")
|
201 |
|
202 | let part = commonPart ? commonPart + "/??" : "??";
|
203 | let cdnCombo = [cdn, '/', part];
|
204 |
|
205 | resultArr.forEach((val, idx) => {
|
206 | let p = val.join('/').replace(commonPart, "");
|
207 | cdnCombo.push(p)
|
208 | cdnCombo.push(',')
|
209 | });
|
210 | if (cdnCombo[cdnCombo.length - 1] == ',') {
|
211 | cdnCombo.pop();
|
212 | }
|
213 | return cdnCombo.join('')
|
214 | }
|
215 |
|
216 | function appendLink(src, extProp) {
|
217 | let tpl = `<link ${extProp || ""} type="text/css" rel="stylesheet" href="${src}" >\n`;
|
218 | $$("head").append(tpl);
|
219 | }
|
220 |
|
221 | function appendScript(src, extProp) {
|
222 | let tpl = `<script ${extProp || ""} type="text/javascript" src="${src}"></script>\n`;
|
223 | let $lastScript = $$('body script:not([src])').last();
|
224 | if ($lastScript.length) {
|
225 | $lastScript.before(tpl);
|
226 | } else {
|
227 | $$("body").append(tpl);
|
228 | }
|
229 |
|
230 | }
|
231 |
|
232 | let comboStr;
|
233 |
|
234 | if (jdf.config.output.cssCombo) {
|
235 | let $widgetCssTags = $$('link[source="widget"]');
|
236 | if ($widgetCssTags.length >= jdf.config.output.comboItemCount) {
|
237 | comboStr = comboByHtmlTag($widgetCssTags.toArray(), "href");
|
238 | appendLink(comboStr, 'source="widget"');
|
239 | $widgetCssTags.remove();
|
240 | }
|
241 |
|
242 | let cssSet = {};
|
243 | let $CssTags = $$('link[source != "widget"][href^="' + cdn + '"]');
|
244 | if ($CssTags.length) {
|
245 | $CssTags.each((index, tag) => {
|
246 |
|
247 | let p = path.dirname($$(tag).attr("href"));
|
248 |
|
249 | if (!cssSet[p]) {
|
250 | cssSet[p] = {
|
251 | child: [tag]
|
252 | }
|
253 | } else {
|
254 | cssSet[p].child.push(tag);
|
255 | }
|
256 |
|
257 | });
|
258 | for (let i in cssSet) {
|
259 | let folder = cssSet[i];
|
260 | if (folder.child.length >= jdf.config.output.comboItemCount) {
|
261 | comboStr = comboByHtmlTag(folder.child, "href");
|
262 | appendLink(comboStr);
|
263 | $$(folder.child).remove();
|
264 | }
|
265 | }
|
266 | }
|
267 |
|
268 | }
|
269 |
|
270 | if (jdf.config.output.jsCombo) {
|
271 | let $widgetScriptTags = $$('script[source="widget"]');
|
272 | if ($widgetScriptTags.length >= jdf.config.output.comboItemCount) {
|
273 | comboStr = comboByHtmlTag($widgetScriptTags.toArray(), "src");
|
274 | appendScript(comboStr, 'source="widget"');
|
275 | $widgetScriptTags.remove();
|
276 | }
|
277 | let scriptSet = {};
|
278 | let $scriptTags = $$('script[source != "widget"][src^="' + cdn + '"]');
|
279 | if ($scriptTags.length) {
|
280 | $scriptTags.each((index, tag) => {
|
281 | let p = path.dirname($$(tag).attr("src"));
|
282 | if (!scriptSet[p]) {
|
283 | scriptSet[p] = {
|
284 | child: [tag]
|
285 | }
|
286 | } else {
|
287 | scriptSet[p].child.push(tag);
|
288 | }
|
289 | });
|
290 |
|
291 | for (let i in scriptSet) {
|
292 | let folder = scriptSet[i];
|
293 | if (folder.child.length >= jdf.config.output.comboItemCount) {
|
294 | comboStr = comboByHtmlTag(folder.child, "src");
|
295 | appendScript(comboStr);
|
296 | $$(folder.child).remove();
|
297 | }
|
298 | }
|
299 | }
|
300 | }
|
301 | let html = $$.html();
|
302 | html = html.replace(/\r/ig, "");
|
303 | return html.replace(/\n{2,}/ig, "\n");
|
304 |
|
305 | }
|
306 |
|
307 |
|
308 |
|
309 |
|
310 |
|
311 | urlReplace.setPrefixBanner = function (source) {
|
312 | var projectPath = jdf.getProjectPath() ? jdf.getProjectPath().replace('/', '-') + ' ' : '';
|
313 | var basename = path.basename(source);
|
314 |
|
315 | return '/* ' + projectPath + basename + ' Date:' + $.getDay('-') + ' ' + $.getTime(':', false) + ' */\r\n';
|
316 | }
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 | urlReplace.cssImagesUrlReplace = function (content, cb) {
|
328 | var cssImagesUrlReg = new RegExp("url\\(.*?\\)", "igm");
|
329 | var cssImagesUrl = content.match(cssImagesUrlReg);
|
330 |
|
331 |
|
332 | var tempSet = new Set(cssImagesUrl);
|
333 |
|
334 | var outputdir = path.normalize(f.currentDir() + '/' + jdf.config.outputDirName);
|
335 |
|
336 | if (tempSet.size) {
|
337 |
|
338 | for (var i of tempSet.values()) {
|
339 | var b = i;
|
340 | b = b.replace('url(', '');
|
341 | b = b.replace(')', '');
|
342 | b = b.replace(/\s/g, '');
|
343 | b = b.replace(/\"/g, '');
|
344 | b = b.replace(/\'/g, '');
|
345 |
|
346 | if ($.is.imageFile(b) && !$.is.httpLink(b) && b.indexOf('?__base64') == -1) {
|
347 | var sReg = new RegExp(`url\\([\\"\\']{0,1}${esr(b)}[\\"\\']{0,1}\\)`, 'gim');
|
348 | content = content.replace(sReg, 'url(' + cb(b) + ')');
|
349 | }
|
350 | };
|
351 | };
|
352 |
|
353 | return content;
|
354 | }
|
355 |
|
356 | urlReplace.addSourceCdn = function (source, filename, loadType) {
|
357 | var cdn = jdf.config.cdn;
|
358 | var sourcedir = path.normalize(path.dirname(source));
|
359 | var projectPath = jdf.config.projectPath;
|
360 | let outputdir = path.normalize($.pathJoin(f.currentDir(), jdf.config.outputDirName, projectPath));
|
361 |
|
362 | if (!source) {
|
363 | return;
|
364 | }
|
365 |
|
366 |
|
367 | if (!filename) {
|
368 | filename = path.basename(source);
|
369 | loadType = false;
|
370 | }
|
371 |
|
372 | if(!$.is.httpLink(filename)){
|
373 | |
374 |
|
375 |
|
376 |
|
377 | if(/^jdf\//.test(filename) || /^felibs\//.test(filename) || /^seajs\//.test(filename) || /^virtuals\//.test(filename)){
|
378 |
|
379 | return '//misc.360buyimg.com/' + filename;
|
380 | }
|
381 | else if ((loadType == 'require' || loadType == 'use') && /^\w/.test(filename)) {
|
382 | return cdn + $.pathJoin('/', filename);
|
383 | }
|
384 | else if (/^\/\w/.test(filename)) {
|
385 |
|
386 | filename = cdn + $.pathJoin('/', jdf.getProjectPath(), filename);
|
387 | }
|
388 | else {
|
389 | var d = sourcedir.replace(outputdir, '');
|
390 | var e = path.normalize(path.join(d, filename));
|
391 |
|
392 | filename = cdn + $.pathJoin('/', projectPath, e);
|
393 | }
|
394 | }
|
395 |
|
396 | return filename.replace(/\\/g, '/');
|
397 | }
|
398 |
|
399 |
|
400 |
|
401 |
|
402 |
|
403 | urlReplace.appendWebpCSSFIX = function (source) {
|
404 | var AST_result = [];
|
405 | var sourceCode = urlReplace.css(source, false);
|
406 |
|
407 | sourceCode = sourceCode.replace(/\/\*.*?\*\//ig, function (match) {
|
408 | return ""
|
409 | });
|
410 | var rules = sourceCode.match(/.*?\{.*?\}/ig);
|
411 | if (!rules) {
|
412 | return;
|
413 | }
|
414 | for (var i = 0; i < rules.length; i++) {
|
415 | var rule = rules[i];
|
416 | if (rule.match(/\{/g).length != rule.match(/\}/g).length) {
|
417 | continue;
|
418 | }
|
419 | var cssBodyStr = rule.match(/\{.*?\}/ig)[0];
|
420 | var cssHead = rule.replace(cssBodyStr, "");
|
421 | cssHead = cssHead.split(';');
|
422 | cssHead = cssHead[cssHead.length - 1];
|
423 | cssBodyStr = cssBodyStr.replace('{', '').replace('}', '');
|
424 | var cssBodyProperties = cssBodyStr.split(';');
|
425 | var astCssRule = {};
|
426 | astCssRule.selector = cssHead;
|
427 | astCssRule.values = [];
|
428 | for (var j in cssBodyProperties) {
|
429 | var cssObj = cssBodyProperties[j].split(":");
|
430 | var cssPropertyName = cssObj[0];
|
431 | var cssPropertyValue = cssBodyProperties[j].replace(cssPropertyName + ":", "");
|
432 | if (cssPropertyValue.match(/.*?url.*?\.(png|jpg)/ig)) {
|
433 | var _c = {
|
434 | k: cssPropertyName,
|
435 | v: cssPropertyValue.replace(/.*?url.*?\.(png|jpg)/ig, function (match) {
|
436 | return match + ".webp";
|
437 | })
|
438 | };
|
439 | astCssRule.values.push(_c);
|
440 | }
|
441 | }
|
442 | if (astCssRule.values.length) {
|
443 | AST_result.push(astCssRule);
|
444 | }
|
445 | }
|
446 |
|
447 | var resultCss = ["/* webp css prefix */"];
|
448 | for (var i in AST_result) {
|
449 | var webpCssRule = AST_result[i];
|
450 | var rootClass = jdf.config.output.webpRootClass ? '.' + jdf.config.output.webpRootClass + " " : ".root-webp ";
|
451 |
|
452 | var cssValues = [];
|
453 | for (var j in webpCssRule.values) {
|
454 | var cssV = webpCssRule.values[j];
|
455 | cssValues.push(cssV.k + ":" + cssV.v);
|
456 | }
|
457 | var css = rootClass + webpCssRule.selector + "{" + cssValues.join(';') + "}";
|
458 | resultCss.push(css);
|
459 | }
|
460 |
|
461 | var raw = f.read(source);
|
462 | f.write(source, raw + "\n" + resultCss.join("\n"));
|
463 |
|
464 |
|
465 | }
|