UNPKG

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