UNPKG

9.66 kBJavaScriptView Raw
1"use strict";
2
3const path = require('path');
4const os = require('os');
5const fs = require('fs');
6const jsmart = require('jsmart');
7const logger = require('jdf-log');
8const escapeStringRegexp = require('escape-string-regexp');
9const stripJsonComments = require('strip-json-comments');
10
11//lib自身组件
12const jdfUtils = require('jdf-utils');
13const $ = jdfUtils.base;
14const f = jdfUtils.file;
15const jdf = require('./jdf');
16const Vm = require("./vm");
17const VFS = require('./VFS/VirtualFileSystem');
18const widgetParser = require('./buildWidget');
19const pluginCore = require('./pluginCore')
20
21let buildHTML = module.exports = {};
22
23/**
24 * 深度搜索widget,实现widget嵌套
25 * 算法概要:
26 * 1、收集HTML中的widget,作为widgetTree的root节点,一个页面有多个widgetTree
27 * 2、深度搜索widgetname.vm中的widget依赖,将依赖附加到widgetTree上,同时确定模板为vm、tpl、smarty其中一种
28 * 在搜索依赖时检测循环引用,一旦出现循环引用直接退出编译
29 * 3、自底向上遍历widgetTree,生成{jsMap, cssMap, vmContent}
30 * 4、有条件将渲染好的template插入container
31 * 5、合并所有widgetTree的{jsMap, cssMap},并插入HTML
32 * 6、完毕
33 */
34buildHTML.init = function () {
35 buildHTML.VFS = VFS;
36 logger.profile('parse widget');
37 return VFS.go()
38 .then(() => {
39 return VFS.travel((vfile, done) => {
40 // 只操作html文件
41 if (!$.is.html(vfile.originPath)) {
42 return;
43 }
44
45 logger.verbose(`reset vfile.targetContent to be vfile.originContent: ${vfile.originPath}`);
46 // 由于要重新编译html,在这里重置targetContent
47 vfile.targetContent = vfile.originContent;
48
49 logger.verbose(`step1: parse widgetTree roots`)
50 let widgetRoots = widgetParser.parseWidget(vfile.originContent);
51
52 // 去重,引用同一个widget多次,js,css只引用一次
53 let jsMap = new Map();
54 let cssMap = new Map();
55
56 let rootTrees = [];
57 widgetRoots.forEach(rootWidgetInfo => {
58 logger.verbose(`step2: generate widget tree,page:${path.basename(vfile.originPath)},widget:${rootWidgetInfo.name}`);
59 let WidgetTree = widgetParser.generateWidgetTree(rootWidgetInfo);
60 rootTrees.push(WidgetTree);
61
62 logger.verbose(`step 3: 自底向上遍历widget tree, 生成vmcontent, jsMap, cssMap`);
63 let container = vfile.targetContent;
64 container = buildHTML.renderTemplateToContainer(WidgetTree, container, jsMap, cssMap);
65
66 logger.verbose(`step 4: 插入vm到html中`);
67 vfile.targetContent = container;
68 });
69
70 logger.verbose(`step 5: 清除widget标签,因为有些只引入js或css,从而不会被vm替换`);
71 rootTrees.forEach(WidgetTree => {
72 vfile.targetContent = this.cleanWidgetLabel(WidgetTree, vfile.targetContent);
73 })
74
75 logger.verbose(`step 6: 插入js、css到html中`);
76 for (let key of jsMap.keys()) {
77 let jsPath = path.resolve(VFS.targetDir, jdf.config.widgetDir, key, key + '.js');
78 let jsVfile = VFS.queryFile(jsPath, 'target');
79 if (jsVfile) {
80 this.insertJS(vfile, jsVfile, jdf.config.build.jsPlace);
81 }
82 }
83 for (let key of cssMap.keys()) {
84 let cssPath = path.resolve(VFS.targetDir, jdf.config.widgetDir, key, key + '.css');
85 let cssVfile = VFS.queryFile(cssPath, 'target');
86 if (cssVfile) {
87 this.insertCSS(vfile, cssVfile);
88 }
89 }
90 });
91 }).then(() => {
92 logger.profile('parse widget');
93 }).catch(err => {
94 logger.error(err);
95 });
96
97}
98
99/**
100 * 生成root vmcontent算法,自底向上DFS
101 */
102buildHTML.renderTemplateToContainer = function (node, container, jsMap, cssMap) {
103 let widgetInfo = node.widgetInfo;
104
105 let buildTag = widgetInfo.buildTag;
106
107 if (buildTag.js) {
108 jsMap.set(widgetInfo.name, true);
109 }
110 if (buildTag.css) {
111 cssMap.set(widgetInfo.name, true);
112 }
113
114 // 没有模板编译
115 if (!(widgetInfo.buildTag.vm || widgetInfo.buildTag.tpl || widgetInfo.buildTag.smarty)) {
116 return container;
117 }
118
119 // 精确控制template类型
120 let vmVfile = widgetParser.findvmVfile(widgetInfo);
121 if (!vmVfile) {
122 return container;
123 }
124
125 let renderedResult = '';
126 if (widgetInfo.buildTag.vm || widgetInfo.buildTag.tpl) {
127 renderedResult = this.renderVM(widgetInfo, jsMap, cssMap);;
128 } else if (widgetInfo.buildTag.smarty) {
129 renderedResult = this.renderSmarty(widgetInfo, jsMap, cssMap);;
130 }
131
132 // 存在子widget
133 if (node.children.length > 0) {
134 // 获取当前widget编译后的模板,作为新的container
135 let childContainer = renderedResult;
136
137 // 然后递归
138 node.children.forEach(child => {
139 childContainer = this.renderTemplateToContainer(child, childContainer, jsMap, cssMap);
140
141 });
142 renderedResult = childContainer;
143 }
144
145 if (jdf.config.widgetLabel && renderedResult) {
146 // 打标签
147 // <!-- widget widgetName begin -->
148 // <!-- widget widgetName end -->
149 renderedResult = `${os.EOL}<!-- widget ${widgetInfo.name} begin -->`
150 + `${renderedResult}`
151 + `${os.EOL}<!-- widget ${widgetInfo.name} end -->`;
152 }
153
154 container = container.replace(widgetInfo.text, renderedResult);
155
156 return container;
157}
158
159/**
160 * 根据widgetInfo编译vm,同时收集#parse引进的js,css
161 * 注:#parse功能和widget嵌套类似
162 */
163buildHTML.renderVM = function (widgetInfo, jsMap, cssMap) {
164 widgetInfo.type = 'vm';
165
166 let replaceStr = '';
167
168 // 获取widget的模板
169 let vmVfile = widgetParser.findvmVfile(widgetInfo);
170 if (!vmVfile) {
171 return replaceStr;
172 }
173
174 let tpl = vmVfile.targetContent;
175
176 // hook
177 tpl = pluginCore.excuteBeforeTplRender(tpl, widgetInfo);
178
179 // 编译模板
180 let vmdata = widgetParser.getWidgetData(widgetInfo);
181 if (!tpl) {
182 replaceStr = '';
183 } else {
184 try {
185 let result = Vm.render(tpl, {
186 dataObj: vmdata,
187 dirname: widgetInfo.dirname,
188 existMap: {jsMap: jsMap, cssMap: cssMap}
189 });
190 replaceStr = os.EOL + result;
191 logger.verbose(`have vm content and render success`);
192 } catch (err) {
193 logger.error('velocityjs compile failed.');
194 logger.error(err);
195 }
196 }
197 if (replaceStr === '') {
198 logger.verbose(`vm parsed, and result to empty string`);
199 }
200
201 // hook
202 replaceStr = pluginCore.excuteBeforeTplInsert(replaceStr, widgetInfo);
203
204 return replaceStr;
205}
206
207/**
208 * 根据widgetInfo编译smarty
209 */
210buildHTML.renderSmarty = function (widgetInfo) {
211 widgetInfo.type = 'smarty';
212
213 // 获取widget的模板
214 let vmVfile = widgetParser.findvmVfile(widgetInfo);
215 if (!vmVfile) {
216 return replaceStr;
217 }
218
219 let tpl = vmVfile.targetContent;
220
221 // hook
222 tpl = pluginCore.excuteBeforeTplRender(tpl, widgetInfo);
223
224 let smdata = widgetParser.getWidgetData(widgetInfo);
225
226 let smartyCompiled = new jSmart(tpl);
227
228 let replaceStr = '';
229 if(smartyCompiled){
230 replaceStr = smartyCompiled.fetch(smdata);
231 }
232
233 // hook
234 replaceStr = pluginCore.excuteBeforeTplInsert(replaceStr, widgetInfo);
235
236 return replaceStr;
237}
238
239/**
240 * 将widget的js文件引用插入到html文件中
241 * @param {VFile} htmlVfile html文件在VFS中的抽象对象
242 * @param {VFile} jsVfile js文件在VFS中的抽象对象
243 * @param {string} place js插入html的位置
244 * @return
245 */
246buildHTML.insertJS = function (htmlVfile, jsVfile, place) {
247 let insertFn;
248 if (place !== 'insertBody') {
249 insertFn = $.placeholder.insertHead;
250 } else {
251 insertFn = $.placeholder.insertBody;
252 }
253
254 let htmlFileDir = path.dirname(htmlVfile.originPath);
255
256 let scriptPath = path.join(path.dirname(jsVfile.originPath), path.basename(jsVfile.targetPath));
257 scriptPath = path.relative(htmlFileDir, scriptPath);
258 scriptPath = f.pathFormat(scriptPath);
259
260 if (!htmlVfile.targetContent) {
261 htmlVfile.targetContent = htmlVfile.originContent;
262 }
263 htmlVfile.targetContent = insertFn(htmlVfile.targetContent, $.placeholder.jsLink(scriptPath));
264}
265
266/**
267 * 将widget的css(scss,less)文件引用插入到html中
268 * @param {VFile} htmlVfile html文件在VFS中的抽象对象
269 * @param {VFile} cssVfile css文件在VFS中的抽象对象
270 * @return
271 */
272buildHTML.insertCSS = function (htmlVfile, cssVfile) {
273 let insertFn = $.placeholder.insertHead;
274
275 let htmlFileDir = path.dirname(htmlVfile.originPath);
276
277 let linkPath = path.join(path.dirname(cssVfile.originPath), path.basename(cssVfile.targetPath));
278 linkPath = path.relative(htmlFileDir, linkPath);
279 linkPath = f.pathFormat(linkPath);
280
281 if (!htmlVfile.targetContent) {
282 htmlVfile.targetContent = htmlVfile.originContent;
283 }
284 htmlVfile.targetContent = insertFn(htmlVfile.targetContent, $.placeholder.cssLink(linkPath));
285}
286
287buildHTML.cleanWidgetLabel = function (node, content) {
288 content = content.replace(new RegExp(escapeStringRegexp(node.widgetInfo.text), 'g'), os.EOL);
289 node.children.forEach(child => {
290 content = this.cleanWidgetLabel(child, content);
291 });
292 return content;
293}
\No newline at end of file