UNPKG

16 kBJavaScriptView Raw
1"use strict";
2/**
3 * 替换文件中的url
4 *
5 */
6
7//system
8const path = require('path');
9const cheerio = require('cheerio');
10
11//jdf-lib
12const jdfUtils = require('jdf-utils');
13const $ = jdfUtils.base;
14const f = jdfUtils.file;
15const jdf = require('./jdf.js');
16const shelljs = require('shelljs');
17const logger = require('jdf-log');
18const VFS = require('./VFS/VirtualFileSystem');
19const _ = require("lodash");
20const esr = require('escape-string-regexp');
21
22const jsAst = require('./jsAst');
23
24//exports
25const urlReplace = module.exports = {};
26
27urlReplace.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 //给html文件中的script标签引用资源,添加cdn
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 //给html文件中的link标签引用资源,添加cdn
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 // 存在rel 并且 不为样式表
85 if (rel && rel.toLowerCase() !== 'stylesheet') {
86 return;
87 }
88 // 存在type 并且type不为 css
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 * combo html 文件中的 css 或 js 文件引用
149 * @param {string} content html 内容
150 * @return {string} combo 后的 html 内容
151 */
152urlReplace.comboUrlPath = function (content) {
153 let $$ = cheerio.load(content, {
154 decodeEntities: false
155 });
156
157 /**
158 * @param {Array} [["aaa","bbb"],["aaa","b1bb"],["aaa","b2bb"]]
159 * @returns {Array} ["aaa"]
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 * @增加前缀banner
309 * @return {String} /* projectPath - Date:2014-03-13 13:06:12:120 * /
310 */
311urlReplace.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* css中图片路径替换
320* @time 2014-2-21 10:17:13
321* @param cdn 前缀
322* @param prefix css目录前缀
323* @example
324 cssImagesUrlReplace('.test{background-image:url("i/test.jpg");}','http://cdn.com/','?time=123') ===>
325 .test{background-image:url("http://cdn.com/i/test.jpg?time=123");}
326*/
327urlReplace.cssImagesUrlReplace = function (content, cb) {
328 var cssImagesUrlReg = new RegExp("url\\(.*?\\)", "igm");
329 var cssImagesUrl = content.match(cssImagesUrlReg);
330
331 //使用Set数据结构,直接去重
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
356urlReplace.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 //当没有传入filename时,我会认为你是要处理当前文件,所以loadType需要置为false
367 if (!filename) {
368 filename = path.basename(source);
369 loadType = false;
370 }
371
372 if(!$.is.httpLink(filename)){
373 /**
374 * 文件路径是以“jdf/ felibs/ seajs/ virtuals/”开头的,直接返回
375 * 当使用require或者seajs.use加载文件,并且文件路径是以字母开头的,直接添加cdn返回
376 */
377 if(/^jdf\//.test(filename) || /^felibs\//.test(filename) || /^seajs\//.test(filename) || /^virtuals\//.test(filename)){
378 // jdf,felibs开头的也在buildOutputWidget中使用了
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 //以斜杠的开头的文件直接添加cdn和项目根目录
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@method 将 webp 相关css 追加到指定css中
401@option {String} source 输入文件路径
402**/
403urlReplace.appendWebpCSSFIX = function (source) {
404 var AST_result = [];
405 var sourceCode = urlReplace.css(source, false);
406 //remove comment
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}