UNPKG

9.71 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 * 编译HTML
25 * 在编译HTML之前,最好先编译完CSS、JS、vm/tpl/smarty模板,确保得到最正确的结果
26 * @param {VFS} VFS 内存虚拟文件系统
27 * @return
28 */
29buildHTML.init = function () {
30 buildHTML.VFS = VFS;
31 logger.profile('parse widget');
32 return VFS.go()
33 .then(() => {
34 return VFS.travel((vfile, done) => {
35 // 只操作html文件
36 if (!$.is.html(vfile.originPath)) {
37 return;
38 }
39
40 logger.verbose(`reset vfile.targetContent to be vfile.originContent: ${vfile.originPath}`);
41 // 由于要重新编译html,在这里重置targetContent
42 vfile.targetContent = vfile.originContent;
43
44 logger.verbose(`parse widget tag in html, generate widgetInfo object collection.`)
45 let widgets = widgetParser.parseWidget(vfile.originContent);
46 widgets.forEach(widgetInfo => {
47 widgetInfo.pagename = path.basename(vfile.originPath);
48 });
49
50 logger.verbose(`foreach widgetInfo`);
51 // 编译单个widget到html中。下面两个map是为了css,js引用不重复插入html中
52 let jsMap = new Map();
53 let cssMap = new Map();
54 widgets.forEach(widgetInfo => {
55 logger.verbose(`parse widget: ${widgetInfo.name}`);
56 let widgetVfiles = VFS.queryDir(widgetInfo.dirname);
57 widgetVfiles.forEach(widgetVfile => {
58
59 // 不处理widget中与widget规定不符的文件
60 // 匹配\\widget\\widgetName\\widgetName.ext
61 let oPath = widgetVfile.originPath;
62 let oBasename = path.basename(oPath);
63 let dirname = path.dirname(oPath);
64 let widgetNameReg = new RegExp(escapeStringRegexp(widgetInfo.name) + '\.\\w+$');
65 if (path.relative(dirname, widgetInfo.dirname)
66 || !widgetNameReg.test(oBasename)) {
67 // 1、相对路径不为空
68 // 2、文件名不和widget名一致
69 return;
70 }
71
72 let wPath = widgetVfile.targetPath;
73 let existMap = {jsMap: jsMap, cssMap: cssMap};
74 // widgetInfo.buildTag反映type中指定的编译类型
75 if (widgetInfo.buildTag.vm && $.is.vm(wPath)) {
76 logger.verbose(`insert vm into HTML: ${widgetVfile.originPath}`);
77 buildHTML.insertVM(widgetInfo, vfile, widgetVfile, existMap);
78 }
79 else if (widgetInfo.buildTag.tpl && $.is.tpl(wPath)) {
80 // 旧代码vm和tpl等价
81 logger.verbose(`insert vm into HTML: ${widgetVfile.originPath}`);
82 buildHTML.insertVM(widgetInfo, vfile, widgetVfile, existMap);
83 }
84 else if (widgetInfo.buildTag.smarty && $.is.smarty(wPath)){
85 logger.verbose(`insert smarty into HTML: ${widgetVfile.originPath}`);
86 buildHTML.insertSmarty(widgetInfo, vfile, widgetVfile);
87 }
88 else if (widgetInfo.buildTag.css && $.is.css(wPath)){
89 if (cssMap.has(widgetInfo.name)) {
90 logger.verbose(`have inserted widget ${widgetInfo.name}'s css <link> in html`);
91 } else {
92 logger.verbose(`insert <link> tag into HTML: ${widgetVfile.originPath}`);
93 buildHTML.insertCSS(vfile, widgetVfile);
94 cssMap.set(widgetInfo.name, true);
95 }
96 }
97 else if (widgetInfo.buildTag.js && $.is.js(wPath)){
98 if (jsMap.has(widgetInfo.name)) {
99 logger.verbose(`have inserted widget ${widgetInfo.name}'s js <script> in html`);
100 } else {
101 logger.verbose(`insert <script> tag into HTML: ${widgetVfile.originPath}`);
102 buildHTML.insertJS(vfile, widgetVfile, jdf.config.build.jsPlace);
103 jsMap.set(widgetInfo.name, true);
104 }
105 }
106 });
107 });
108 // 处理完vm,js,css后都要清除widget标签,因为有些只引入js或css,从而不会被vm替换
109 widgets.forEach(widgetInfo => {
110 vfile.targetContent = vfile.targetContent.replace(widgetInfo.text, os.EOL);
111 })
112 });
113 }).then(() => {
114 logger.profile('parse widget');
115 }).catch(err => {
116 logger.error(err);
117 });
118
119}
120
121/**
122 * 将widget的vm编译后插入到html的原{%widget %}标签位置
123 * @param {object} widgetInfo 描述{%widget %}信息的对象
124 * @param {VFile} htmlVfile html文件在VFS中的抽象对象
125 * @param {VFile} vmVfile vm文件在VFS中的抽象对象
126 * @return
127 */
128buildHTML.insertVM = function (widgetInfo, htmlVfile, vmVfile, existMap) {
129 logger.verbose(widgetInfo.name, '->insertVM');
130 widgetInfo.type = 'vm';
131
132 let replaceStr = '';
133
134 let tpl = vmVfile.targetContent;
135
136 // hook
137 tpl = pluginCore.excuteBeforeTplRender(tpl, widgetInfo);
138
139 let vmdata = widgetParser.getWidgetData(widgetInfo);
140
141 if (!tpl) {
142 replaceStr = '';
143 } else {
144 try {
145 let result = Vm.render(tpl, {
146 dataObj: vmdata,
147 dirname: widgetInfo.dirname,
148 existMap: existMap,
149 htmlDir: path.dirname(htmlVfile.originPath)
150 });
151 replaceStr = os.EOL + result;
152 logger.verbose(`have vm content and render success`);
153 } catch (err) {
154 logger.error('velocityjs compile failed.');
155 logger.error(err);
156 }
157 }
158
159
160 if (replaceStr === '') {
161 logger.verbose(`vm parsed, and result to empty string`);
162 }
163
164 // hook
165 replaceStr = pluginCore.excuteBeforeTplInsert(replaceStr, widgetInfo);
166
167 // 打标签
168 // <!-- widget widgetName begin -->
169 // <!-- widget widgetName end -->
170 replaceStr = `${os.EOL}<!-- widget ${widgetInfo.name} begin -->`
171 + `${replaceStr}`
172 + `${os.EOL}<!-- widget ${widgetInfo.name} end -->`;
173 htmlVfile.targetContent = htmlVfile.targetContent.replace(widgetInfo.text, replaceStr);
174}
175
176buildHTML.insertSmarty = function (widgetInfo, htmlVfile, smVfile) {
177 widgetInfo.type = 'smarty';
178
179 let smdata = widgetParser.getWidgetData(widgetInfo);
180
181 let tpl = smVfile.targetContent;
182
183 // hook
184 tpl = pluginCore.excuteBeforeTplRender(tpl, widgetInfo);
185
186 let smartyCompiled = new jSmart(tpl);
187
188 let replaceStr = '';
189 if(smartyCompiled){
190 replaceStr = smartyCompiled.fetch(smdata);
191 }
192
193 // hook
194 replaceStr = pluginCore.excuteBeforeTplInsert(replaceStr, widgetInfo);
195
196
197 // 打标签
198 // <!-- widget widgetName begin -->
199 // <!-- widget widgetName end -->
200 replaceStr = `${os.EOL}<!-- widget ${widgetInfo.name} begin -->`
201 + `${replaceStr}`
202 + `${os.EOL}<!-- widget ${widgetInfo.name} end -->`;
203 htmlVfile.targetContent = htmlVfile.targetContent.replace(widgetInfo.text, replaceStr);
204}
205
206/**
207 * 将widget的js文件引用插入到html文件中
208 * @param {VFile} htmlVfile html文件在VFS中的抽象对象
209 * @param {VFile} jsVfile js文件在VFS中的抽象对象
210 * @param {string} place js插入html的位置
211 * @return
212 */
213buildHTML.insertJS = function (htmlVfile, jsVfile, place) {
214 let insertFn;
215 if (place !== 'insertBody') {
216 insertFn = $.placeholder.insertHead;
217 } else {
218 insertFn = $.placeholder.insertBody;
219 }
220
221 let htmlFileDir = path.dirname(htmlVfile.originPath);
222
223 let scriptPath = path.join(path.dirname(jsVfile.originPath), path.basename(jsVfile.targetPath));
224 scriptPath = path.relative(htmlFileDir, scriptPath);
225 scriptPath = f.pathFormat(scriptPath);
226
227 if (!htmlVfile.targetContent) {
228 htmlVfile.targetContent = htmlVfile.originContent;
229 }
230 htmlVfile.targetContent = insertFn(htmlVfile.targetContent, $.placeholder.jsLink(scriptPath));
231}
232
233/**
234 * 将widget的css(scss,less)文件引用插入到html中
235 * @param {VFile} htmlVfile html文件在VFS中的抽象对象
236 * @param {VFile} cssVfile css文件在VFS中的抽象对象
237 * @return
238 */
239buildHTML.insertCSS = function (htmlVfile, cssVfile) {
240 let insertFn = $.placeholder.insertHead;
241
242 let htmlFileDir = path.dirname(htmlVfile.originPath);
243
244 let linkPath = path.join(path.dirname(cssVfile.originPath), path.basename(cssVfile.targetPath));
245 linkPath = path.relative(htmlFileDir, linkPath);
246 linkPath = f.pathFormat(linkPath);
247
248 if (!htmlVfile.targetContent) {
249 htmlVfile.targetContent = htmlVfile.originContent;
250 }
251 htmlVfile.targetContent = insertFn(htmlVfile.targetContent, $.placeholder.cssLink(linkPath));
252}