UNPKG

11.2 kBJavaScriptView Raw
1
2"use strict";
3
4const path = require('path');
5const os = require('os');
6const fs = require('fs');
7const logger = require('jdf-log');
8const cheerio = require('cheerio');
9
10//lib自身组件
11const jdfUtils = require('jdf-utils');
12const $ = jdfUtils.base;
13const f = jdfUtils.file;
14const jdf = require('./jdf');
15const VFS = require('./VFS/VirtualFileSystem');
16const widgetParser = require('./buildWidget');
17const buildHTML = require('./buildHTML');
18const jsAst = require('./jsAst');
19const urlReplace = require('./urlReplace');
20
21//exports
22let bow = module.exports = {};
23
24/**
25 * 编译{%widgetOutputName="mywidgetname" type="css,js"%}
26 * 把html中的所有引用widget的css,js合并到一个文件中
27 * 实际工作就是把已经编译好的css,js拼装起来,组装成一个新的mywidgetname的vfile
28 * 2017-01-17:新增在config.json中配置全局(跨页面级别)widgetOutputName
29 */
30bow.init = function () {
31 let globalOutputNameIsInvoked = false;
32 return VFS.go()
33 .then(() => {
34 return bow.initGlobalOutputName();
35 })
36 .then(() => {
37 return VFS.travel((vfile, done) => {
38 // 只操作html文件
39 if (!$.is.html(vfile.originPath)) {
40 return;
41 }
42
43 logger.verbose(`parse widgetOutputName:${vfile.originPath}`);
44
45 // 读取解析{%widgetOutputName="mywidgetname" type=""%}
46 // 前面经过buildHTML处理,这里使用originContent获取引用的widget
47 // {
48 // name: mywidgetname,
49 // concatTag: {js: true, css:false} //根据type确定拼装文件
50 // }
51 // 只允许一个页面有一个widgetOutputName, 否则提出警告,并忽略后续的outputName标签
52 let outputInfo = widgetParser.parseOutputName(vfile.targetContent);
53
54 // 如果html页面中没有{%widgetOutputName %} 返回
55 if (!outputInfo.name) {
56 return;
57 }
58
59 // 如果不是引用全局的widgetOutputName,那么就生成页面级别的widgetOutputName
60 if (outputInfo.name !== jdf.config.widgetOutputName) {
61 // 获取页面widget引用的js和css
62 let contents = this.concatOutputContent(outputInfo, vfile);
63
64 // 添加资源到VFS, 在jdf o或者b的时候可以引用到
65 this.addToVFS(outputInfo, contents);
66 } else {
67 globalOutputNameIsInvoked = true;
68 }
69
70 // 根据outputInfo将页面中引用的link或script标签删除
71 // this.delSourceFromWidget(outputInfo);
72 this.delWidgetTagInHTML(outputInfo, vfile);
73
74 // 仅删除当前{%widgetOutputName %} 标签
75 let jsPath = path.join(VFS.originDir, jdf.config.jsDir, outputInfo.name + '.js');
76 let cssPath = path.join(VFS.originDir, jdf.config.cssDir, outputInfo.name + '.css');
77 vfile.targetContent = vfile.targetContent.replace(outputInfo.text, '');
78 if (outputInfo.concatTag.js) {
79 let jsVfile = VFS.queryFile(jsPath);
80 buildHTML.insertJS(vfile, jsVfile, jdf.config.build.jsPlace);
81 }
82 if (outputInfo.concatTag.css) {
83 let cssVfile = VFS.queryFile(cssPath);
84 buildHTML.insertCSS(vfile, cssVfile);
85 }
86 });
87 })
88 .then(() => {
89 // 如果没有调用全局outputname, 那么就删除它,不输出
90 if (!globalOutputNameIsInvoked) {
91 let jsPath = path.join(jdf.outputDir, jdf.config.jsDir, jdf.config.widgetOutputName + '.js');
92 let cssPath = path.join(jdf.outputDir, jdf.config.cssDir, jdf.config.widgetOutputName + '.css');
93 VFS.deleteFile(jsPath, 'target');
94 VFS.deleteFile(cssPath, 'target');
95 }
96
97 })
98 .catch((err) => {
99 if (err && err.message) {
100 logger.error(err.message);
101 }
102 });
103}
104
105bow.initGlobalOutputName = function () {
106 // 编译全局的widgetOutputName
107 let mode = jdf.config.widgetOutputMode;
108 let widgetList = [],
109 targetList = [];
110 let widgetPath = path.join(jdf.currentDir, jdf.config.widgetDir);
111
112 try {
113 widgetList = fs.readdirSync(widgetPath);
114 } catch (e) {
115 logger.warn('can\'t find widget dir in your jdf project, skip combine your widget js/css file.')
116 return Promise.reject();
117 }
118
119 // 获取需要输出的widget集合
120 if (mode === 1) {
121 targetList = widgetList;
122 } else if (mode === 2) {
123 targetList = jdf.config.widgetWhiteList;
124 } else if (mode === 3) {
125 let blacklist = jdf.config.widgetBlackList;
126 targetList = widgetList.filter(function (item) {
127 return !blacklist.some(function (item1) {
128 return item1 === item;
129 });
130 });
131 }
132
133 // 拼装所有widget 生成js css文件,并写入VFS
134 this.genWidgetOutputNameJS(targetList);
135 this.genWidgetOutputNameCSS(targetList);
136}
137
138bow.concatOutputContent = function (info, htmlVfile) {
139 let $$ = cheerio.load(htmlVfile.targetContent, {
140 decodeEntities: false
141 });
142
143 let jsSet = new Set();
144 let cssSet = new Set();
145
146 if (info.concatTag.js) {
147 $$('script[source=widget]').each(function (idx, el) {
148 let src = $$(el).attr('src');
149 src = src.split('/');
150 if (src) {
151 jsSet.add(src[src.length - 1].replace('.js', ''));
152 }
153 })
154
155 }
156 if (info.concatTag.css) {
157 $$('link[source=widget]').each(function (idx, el) {
158 let href = $$(el).attr('href');
159 href = href.split('/');
160 if (href) {
161 cssSet.add(href[href.length - 1].replace('.css', ''))
162 }
163 })
164 }
165
166 // 下面采用解析widget标签的形式,上面是直接读取页面中source=widget的script,link标签
167 // let widgetsInfo = widgetParser.parseWidget(htmlVfile.originContent);
168 // widgetsInfo.forEach(widget => {
169 // if (widget.buildTag.js) {
170 // jsSet.add(widget.name);
171 // }
172 // if (widget.buildTag.css) {
173 // cssSet.add(widget.name);
174 // }
175 // });
176
177 let jsContent = '';
178 for (let name of jsSet.keys()) {
179 let tPath = path.join(this.getWidgetTargetDirByName(name), name + '.js');
180 let widgetVfile = VFS.queryFile(tPath, 'target');
181 if (widgetVfile) {
182 let transferTargetContent = this.jsPathRelative(widgetVfile, path.resolve(process.cwd(), jdf.config.jsDir));
183 jsContent += transferTargetContent + ';' + os.EOL;
184 }
185 }
186
187 let cssContent = '';
188 for (let name of cssSet.keys()) {
189 let tPath = path.join(this.getWidgetTargetDirByName(name), name + '.css');
190 let widgetVfile = VFS.queryFile(tPath, 'target');
191 if (widgetVfile) {
192 let transferTargetContent = this.cssPathRelative(widgetVfile, path.resolve(process.cwd(), jdf.config.cssDir));
193 cssContent += transferTargetContent + os.EOL;
194 }
195 }
196
197 return {
198 css: cssContent,
199 js: jsContent
200 }
201}
202
203bow.getWidgetTargetDirByName = function (name) {
204 let widgetDir = path.join(VFS.targetDir, jdf.config.widgetDir, name);
205 return widgetDir;
206}
207
208bow.addToVFS = function (info, contents) {
209 let jsPath = path.join(VFS.originDir, jdf.config.jsDir, info.name + '.js');
210 let cssPath = path.join(VFS.originDir, jdf.config.cssDir, info.name + '.css');
211 if (info.concatTag.js) {
212 VFS.updateFile(jsPath, contents.js);
213 }
214 if (info.concatTag.css) {
215 VFS.updateFile(cssPath, contents.css);
216 }
217}
218
219bow.delWidgetTagInHTML = function (info, vfile) {
220 let $$ = cheerio.load(vfile.targetContent, {
221 decodeEntities: false
222 });
223 if (info.concatTag.js) {
224 $$('script[source=widget]') && $$('script[source=widget]').remove();
225 }
226 if (info.concatTag.css) {
227 $$('link[source=widget]') && $$('link[source=widget]').remove();
228 }
229 vfile.targetContent = $$.html();
230}
231
232bow.genWidgetOutputNameJS = function (widgetList) {
233 let jsPathList = widgetList.map(widgetname => {
234 return path.join(jdf.outputDir,
235 jdf.config.widgetDir,
236 widgetname,
237 widgetname + '.js');
238 });
239 let jsContent = '';
240 for (let i = 0; i < jsPathList.length; i++) {
241 let jsVfile = VFS.queryFile(jsPathList[i], 'target');
242 if (jsVfile) {
243 let transferTargetContent = this.jsPathRelative(jsVfile, path.resolve(process.cwd(), jdf.config.jsDir));
244 jsContent += transferTargetContent + ';' + os.EOL;
245 }
246 }
247 let outputJsPath = path.join(jdf.currentDir, jdf.config.jsDir, jdf.config.widgetOutputName + '.js');
248 VFS.updateFile(outputJsPath, jsContent);
249
250}
251
252bow.genWidgetOutputNameCSS = function (widgetList) {
253 let cssPathList = widgetList.map(function (widgetname) {
254 return path.join(jdf.outputDir,
255 jdf.config.widgetDir,
256 widgetname,
257 widgetname + '.css');
258 });
259 let cssContent = '';
260 for (let i = 0; i < cssPathList.length; i++) {
261 let cssVfile = VFS.queryFile(cssPathList[i], 'target');
262 if (cssVfile) {
263 let transferTargetContent = this.cssPathRelative(cssVfile, path.resolve(process.cwd(), jdf.config.cssDir));
264 cssContent += transferTargetContent + os.EOL;
265 }
266 }
267 let outputCssPath = path.join(jdf.currentDir, jdf.config.cssDir, jdf.config.widgetOutputName + '.css');
268 VFS.updateFile(outputCssPath, cssContent);
269}
270
271bow.jsPathRelative = function (vfile, targetDir) {
272 var content = vfile.targetContent;
273 return jsAst.main(content, {filepath: vfile.originPath}, function(nodeObj) {
274 var str = nodeObj.str,
275 fullpathstr;
276
277 /**
278 * 以下情况不处理
279 * 1、http路径;2、jdf,felibs,virtuals开头;3、绝对路径
280 */
281 if(!str ||
282 $.is.httpLink(str) ||
283 /^jdf\//.test(str) ||
284 /^virtuals\//.test(str) ||
285 /^seajs\//.test(str) ||
286 /^felibs\//.test(str) ||
287 path.isAbsolute(str)){
288 return nodeObj;
289 }
290
291 fullpathstr = path.resolve(path.dirname(vfile.originPath), nodeObj.str);
292
293 str = path.relative(targetDir, fullpathstr);
294
295 str = f.pathFormat(str);
296
297 nodeObj.str = str;
298
299 return nodeObj;
300 });
301}
302
303bow.cssPathRelative = function (vfile, targetDir) {
304 var content = vfile.targetContent;
305 return urlReplace.cssImagesUrlReplace(content, function (url) {
306 var str = url,
307 fullpathstr;
308 /**
309 * 以下情况不处理
310 * 1、http路径;2、jdf,felibs开头;3、绝对路径
311 */
312 if($.is.httpLink(str) ||
313 /^jdf\//.test(str) ||
314 /^felibs\//.test(str) ||
315 path.isAbsolute(str)){
316 return url;
317 }
318
319 fullpathstr = path.resolve(path.dirname(vfile.originPath), str);
320
321 str = path.relative(targetDir, fullpathstr);
322 str = f.pathFormat(str);
323 return str;
324 });
325}