UNPKG

8.56 kBJavaScriptView Raw
1var { reduce, forEach, isEmpty } = require("lodash");
2var { dirname, join: pathJoin } = require("path");
3var { warn } = require("console");
4var log = require('fancy-log');
5var colors = require('ansi-colors');
6
7var fetch = require("node-fetch");
8var UglifyJS = require("uglify-js");
9var parseString = require('xml2js').parseString;
10var crypto = require("crypto");
11var { UI5Cache } = require("./cache");
12
13var { eachDeep } = require('deepdash')(require('lodash'));
14
15var persistCache = UI5Cache.Load();
16
17var FIVE_MINUTES = 5 * 60 * 1000;
18
19var BASE64 = "base64";
20
21/**
22 * md5 hash
23 */
24var md5 = s => {
25 var md5 = crypto.createHash("md5");
26 return md5.update(s).digest("hex");
27};
28
29var readBinary = async url => {
30
31 var GlobalResourceCache = persistCache.get("GlobalResourceCache") || {};
32 var hash = md5(url);
33 var base64Content = GlobalResourceCache[hash];
34
35 if (!base64Content) {
36 var response = await fetch(url, { timeout: FIVE_MINUTES });
37 var buf = await response.buffer();
38 GlobalResourceCache[hash] = buf.toString(BASE64);
39 persistCache.set("GlobalResourceCache", GlobalResourceCache);
40 return buf;
41 } else {
42 return Buffer.from(base64Content, BASE64);
43 }
44
45};
46
47var readURLFromCache = async url => {
48 var GlobalResourceCache = persistCache.get("GlobalResourceCache") || {};
49 var hash = md5(url);
50 var urlContent = GlobalResourceCache[hash];
51 if (!urlContent) {
52 var response = await fetch(url, { timeout: FIVE_MINUTES });
53 if (response.status == 404) {
54 log.error('gulp-ui5-eager-preload', colors.red(`Can not found: ${url}`));
55 }
56 urlContent = await response.text();
57 GlobalResourceCache[hash] = urlContent;
58 persistCache.set("GlobalResourceCache", GlobalResourceCache);
59 }
60 return urlContent;
61};
62
63var fetchSource = async(mName, resourceRoot = "") => {
64 var url = `${resourceRoot}${mName}.js`;
65 try {
66 return await readURLFromCache(url);
67 } catch (error) {
68 warn(`fetch ${mName} failed ${error}`);
69 throw error;
70 }
71};
72
73var fetchAllResource = async(resourceList = [], resourceRoot = "") => {
74 var rt = {};
75
76 await Promise.all(
77 resourceList.map(async r => {
78 var url = `${resourceRoot}${r}`;
79 try {
80 rt[r] = await readURLFromCache(url);
81 return rt[r];
82 } catch (error) {
83 warn(`fetch ${r} failed ${error}`);
84 throw error;
85 }
86 })
87 );
88
89 return rt;
90};
91
92/**
93 * find modules in sap.ui.define pattern
94 */
95var findAllUi5StandardModules = (source, sourceName) => {
96 var base = dirname(sourceName);
97
98 var deps = [];
99
100 var reqMultiReg = /sap\.ui\.require\((\[".*?\"].*?)/g;
101
102 var group;
103
104 while ((group = reqMultiReg.exec(source)) != undefined) {
105 try {
106 deps = deps.concat(JSON.parse(group[1].replace(/'/g, '"')));
107 } catch (error) {
108 log.error(`can not parse sap.ui.require([...]) with ${group[1]} in ${sourceName}`);
109 }
110 }
111
112 var reqSyncReg = /sap\.ui\.requireSync\("(.*?)"\)/g;
113
114 while ((group = reqSyncReg.exec(source)) != undefined) {
115 try {
116 const v = group[1];
117 // some require sync is formatted by string
118 if (v.indexOf("+") < 0) {
119 deps = deps.concat(group[1]);
120 }
121 } catch (error) {
122 log.error(`can not parse sap.ui.requireSync([...]) with ${group[1]} in ${sourceName}`);
123 }
124 }
125
126 var reqSingleReg = /sap\.ui\.require\("(.*?)"\)/g;
127
128 while ((group = reqSingleReg.exec(source)) != undefined) {
129 try {
130 deps = deps.concat(group[1]);
131 } catch (error) {
132 log.error(`can not parse sap.ui.require("...") with ${group[1]} in ${sourceName}`);
133 }
134 }
135
136 var defGroups = /sap\.ui\.define\(.*?(\[.*?\])/g.exec(source);
137
138 if (defGroups && defGroups.length > 0) {
139 var sArray = defGroups[1].replace(/'/g, '"');
140 deps = deps.concat(JSON.parse(sArray));
141 }
142
143 return deps.map(d => {
144 if (d.startsWith("./") || d.startsWith("../")) {
145 d = pathJoin(base, d);
146 // replace \ to / after join
147 d = d.replace(/\\/g, "/");
148 }
149 return d;
150 });
151};
152
153var findAllUi5ViewModules = async(source, sourceName) => {
154 try {
155 return await new Promise((resolve, reject) => {
156 var ds = new Set();
157 parseString(source, { xmlns: true }, function(err, result) {
158 if (err) {
159 reject(err);
160 } else {
161 eachDeep(result, (value) => {
162 if (value && value.$ns) {
163 var mName = `${value.$ns.uri}.${value.$ns.local}`.replace(/\./g, "/");
164 ds.add(mName);
165 }
166 });
167 resolve(Array.from(ds));
168 }
169 });
170 });
171 } catch (error) {
172 warn(`parse ${sourceName} modules failed: ${error}`);
173 return [];
174 }
175};
176
177var findAllImportModules = (source, sourceName = "") => {
178 var base = dirname(sourceName);
179 var rt = [];
180 var matchedTexts = source.match(/import.*?["|'](.*?)["|']/g);
181 if (matchedTexts) {
182 rt = matchedTexts.map(t => {
183 var importName = /import.*?["|'](.*?)["|']/g.exec(t)[1];
184 if (importName.startsWith("./")) {
185 importName = pathJoin(base, importName).replace(/\\/g, "/");
186 }
187 return importName;
188 });
189 }
190 return rt;
191};
192
193// change recursively to iteration
194var resolveUI5Module = async(sModuleNames = [], resourceRoot) => {
195 var globalModuleCache = persistCache.get("GlobalModuleCache") || {};
196 // this time used modules
197 var modules = {};
198 // without cache
199 var moduleDeps = {};
200
201 // set entry
202 moduleDeps["entry"] = sModuleNames;
203
204 for (; ;) {
205 var needToBeLoad = new Set();
206
207 forEach(moduleDeps, dep => {
208 forEach(dep, d => {
209 if (modules[d] == undefined) {
210 needToBeLoad.add(d);
211 }
212 });
213 });
214
215 if (isEmpty(needToBeLoad)) {
216 // no more dependencies need to be analyzed
217 // break from this loop
218 break;
219 } else {
220 await Promise.all(
221 Array.from(needToBeLoad).map(async mName => {
222 try {
223 var source = "";
224 try {
225 source = await fetchSource(mName, resourceRoot);
226 } catch (error) {
227 // retry once
228 source = await fetchSource(mName, resourceRoot);
229 }
230 // use cache here
231 modules[mName] = source;
232 if (!moduleDeps[mName]) {
233 moduleDeps[mName] = findAllUi5StandardModules(source, mName);
234 }
235 } catch (error) {
236 modules[mName] = "";
237 moduleDeps[mName] = [];
238 }
239 })
240 );
241 }
242 }
243
244 persistCache.set("GlobalModuleCache", Object.assign(globalModuleCache, modules));
245
246 return modules;
247};
248
249/**
250 * UI5 Library List
251 */
252var UI5Libraries = [
253 "sap/ui/core",
254 "sap/ui/layout",
255 "sap/ui/unified",
256 "sap/ui/table",
257 "sap/ui/commons",
258 "sap/ui/viz",
259 "sap/ui/suite",
260 "sap/ui/richtexteditor",
261 "sap/ui/comp",
262 "sap/m",
263 "sap/f",
264 "sap/gantt",
265 "sap/ushell",
266 "sap/tnt",
267 "sap/uxap"
268];
269
270/**
271 * find out all ui5 libraries
272 * @param {string[]} modules name
273 *
274 * @returns {string[]} lib names
275 */
276var findAllLibraries = (modules = []) => {
277 var rt = new Set();
278 forEach(modules, m => {
279 forEach(UI5Libraries, l => {
280 if (m.startsWith(l)) {
281 rt.add(l);
282 }
283 });
284 });
285 return Array.from(rt);
286};
287
288var isUI5StandardModule = sModuleName => {
289 var rt = false;
290 UI5Libraries.forEach(packageName => {
291 if (sModuleName && sModuleName.startsWith(packageName)) {
292 rt = true;
293 }
294 });
295 return rt;
296};
297
298/**
299 * temporary in memory uglify cache
300 */
301var TmpUglifyNameCache = {};
302
303/**
304 * To generate preload file content
305 * @param {*} cache object
306 * @param {*} resources list
307 */
308var generatePreloadFile = (cache = {}, resources = {}) => {
309
310 var modules = reduce(
311 cache,
312 (pre, moduleSource, moduleName) => {
313 // ignore core modules, will be load on bootstrap
314 if (!moduleName.startsWith("sap/ui/core")) {
315 var sourceHash = md5(moduleSource);
316 var compressed = TmpUglifyNameCache[sourceHash];
317 if (!compressed) {
318 compressed = UglifyJS.minify(moduleSource).code;
319 }
320 pre[`${moduleName}.js`] = compressed;
321 TmpUglifyNameCache[sourceHash] = compressed;
322 }
323 return pre;
324 }, {}
325 );
326
327 forEach(resources, (content, resourceName) => {
328 modules[resourceName] = content;
329 });
330
331 return `sap.ui.require.preload(${JSON.stringify(modules)})`;
332};
333
334module.exports = {
335 fetchAllResource,
336 generatePreloadFile,
337 fetchSource,
338 findAllUi5ViewModules,
339 isUI5StandardModule,
340 findAllImportModules,
341 findAllUi5StandardModules,
342 resolveUI5Module,
343 findAllLibraries,
344 readURLFromCache,
345 readBinary,
346 persistCache
347};
\No newline at end of file