-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmt_util.js
419 lines (383 loc) · 15.8 KB
/
mt_util.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
'use strict';
var util = require('util');
/**
* Running Grunt instance
* @typedef {object} Grunt
*/
/**
* Prints complete object instead of [Object object]
* @param {type} o
* @returns {undefined}
*/
function printObject(o) {
console.log(util.inspect(o, false, null, true));
}
/**
* Loads configuration file located under passed path. If the file is missing
* then it will use the path to default configuration file to regenerate a
* configuration file by copying it. If the default configuration is missing it
* will fail grunt task with a warning.
*
* Passing `--default_config` argument in the command line will force to use
* the default configuration.
*
* Configuration file is not tracked by git in contrary to default configuration
* file.
* @param {Grunt} grunt - Running Grunt instance
* @param {string} configPath - Path to the configuration file
* @param {string} defaultConfigPath - Path to the default configuration file
* @returns {object} Configuration object to be merged with grunt config.
*/
function loadConfigFile(grunt, configPath, defaultConfigPath) {
var config;
if (grunt.option('default_config')) {
if (!grunt.file.exists(defaultConfigPath)) {
grunt.fail.warn('Missing default configuration file: ' + defaultConfigPath);
}
config = require(defaultConfigPath);
grunt.verbose.writeln('Running configuration found in ' + defaultConfigPath.green);
} else {
if (!grunt.file.exists(configPath)) {
grunt.verbose.writeln(('Missing ' + configPath + ' file - regenerating using ' + defaultConfigPath + ' file instead').yellow);
grunt.file.copy(defaultConfigPath, configPath);
}
config = require(configPath);
grunt.verbose.writeln('Running configuration found in ' + configPath.green);
}
return config;
}
/**
* Searches plugin directories for plugin directory to return path to it.
* @param {Grunt} grunt - Running Grunt instance
* @param {string} pluginName - Enabled plugin name
* @param {string[]} pluginsDirs - List of plugins directories paths in which it
* should look for given plugin's name
* @returns {(string|undefined)} Path to the plugin's contents or undefined if not found.
*/
function findPluginPath(grunt, pluginName, pluginsDirs) {
var foundPluginPath = pluginsDirs
.map(function (pluginDir) {
return pluginDir + '/' + pluginName;
})
.filter(function (path) {
return grunt.file.exists(path);
});
return foundPluginPath[0];
}
/**
* Loads tasks defined under taskPath.
* @param {Grunt} grunt - Running Grunt instance
* @param {string} taskPath - path to folder where the tasks are defined
* @returns {undefined}
*/
function loadTasks(grunt, taskPath) {
if (grunt.file.exists(taskPath)) {
//Grunt will log what tasks has been loaded with --verbose mode
grunt.task.loadTasks(taskPath);
}
}
/**
* Adds plugins files' paths which should be copied on copy task during the
* build process.
* @param {Grunt} grunt - Running Grunt instance
* @param {string} pluginPath - Path to the plugin's contents
* @param {string[]} folderPaths - List of glob patterns of files that must be
* copied. Used as a 'src' parameter of Grunt files array format.
* @returns {undefined}
*/
function addPluginFoldersPathsToCopy(grunt, pluginPath, folderPaths) {
var filesArray = grunt.config('filesToCopy');
filesArray.push({
cwd: pluginPath,
expand: true,
src: folderPaths,
dest: '<%= buildDir %>/vc2'
});
grunt.config('filesToCopy', filesArray);
grunt.verbose.writeln('Added ' + pluginPath + ' files to copy task: ' + folderPaths);
}
/**
* Ads css file paths to the main configuration for concatenation and
* minification during the build process. Those files will be combined
* in alphabetic order per each plugin.
* @param {Grunt} grunt - Running Grunt instance
* @param {string} pluginPath - Path to the plugin's contents
* @returns {undefined}
*/
function addCssPaths(grunt, pluginPath) {
var cssFiles = grunt.config('appCss');
cssFiles.push(pluginPath + '/static/css/**/*.css');
grunt.config('appCss', cssFiles);
}
/**
* Returns name for the plugin's AMD module if there is existance of .js file
* inside the plugin's `static` directory, with the same name as the plugin.
* @param {Grunt} grunt - Running Grunt instance
* @param {string} pluginPath - Path to the plugin directory
* @param {string} pluginName - Plugin name
* @returns {string|null} Name of the plugin's AMD module (which would be the
* same as the plugin's name)
*/
function getPluginModule(grunt, pluginPath, pluginName) {
var module = pluginPath + '/static/' + pluginName;
return grunt.file.exists(module + '.js') ? pluginName : null;
}
/**
* Given plugin's AMD module name registers it in browser config and the
* requirejs optimizer. Browser needs to know what plugins are available and
* under what path (module name) they can be retrieved. Requirejs needs to know
* explicitly what bundles it needs to create as the plugins are loaded dynamically;
* @param {Grunt} grunt - Running Grunt instance
* @param {object} config - Plugin configuration
* @param {type} pluginModule - Name of the plugin's AMD module
* @param {string[]} pluginsDirs - Array of directories paths where to look for
* plugins
* @returns {undefined}
*/
function addPluginModule(grunt, config, pluginModule, pluginsDirs) {
if (pluginModule) {
if (!config.appConfig || !config.appConfig.plugin) {
grunt.fail.warn('Missing plugin definition in configuration file for plugin ' + pluginModule);
} else {
var plugin = config.appConfig.plugin,
plugins = grunt.config('appConfig.plugins'),
requirejsModules = grunt.config.getRaw('requirejs.options.modules');
plugin.modulePath = pluginModule;
plugins.push(plugin);
grunt.config('appConfig.plugins', plugins);
grunt.verbose.writeln('Added plugin ' + pluginModule + ' description to the browser appConfig');
plugin.pluginDeps = plugin.pluginDeps || [];
requirejsModules.push({
name: pluginModule,
exclude: plugin.pluginDeps
.map(function (pluginName) {
var pluginPath, depPluginModule;
pluginPath = findPluginPath(grunt, pluginName, pluginsDirs);
if (!pluginPath) {
grunt.fail.warn('Plugin module ' + pluginModule + ' has a ' +
' dependecy on plugin ' + pluginName + ' but it ' +
' could not be found in plugins directories: [' +
pluginsDirs.join(', ') + '].');
return;
}
depPluginModule = getPluginModule(grunt, pluginPath, pluginName);
if (!depPluginModule) {
grunt.fail.warn('Plugin module ' + pluginModule + ' has a ' +
' dependecy on plugin ' + pluginName + ' but it ' +
' does not have an module file called: ' + pluginName +
'.js.');
return;
}
return depPluginModule;
})
.concat(['bower_components', 'vc2_core']),
excludeShallow: ['text!core/config/app-config.json']
});
grunt.config('requirejs.options.modules', requirejsModules);
grunt.verbose.writeln('Added plugin module ' + pluginModule + ' to be optimized into a bundle.');
}
} else {
if (config.appConfig && config.appConfig.plugin) {
grunt.fail.warn('Missing module file for plugin ' + pluginModule);
}
}
}
/**
* Uniquely concatenates arrays.
* @param {array} arr1
* @param {array} arr2
* @returns {array}
*/
function concatUniquely(arr1, arr2) {
return arr1.concat(arr2).filter(function (val, i, array) {
return array.indexOf(val) === i;
});
}
/**
* Merges plugin configuration file with the grunt configuration. AppConfig
* property is merged with this rules before calling `grunt.config.merge`:
* - configDefaults are added/overriden (otherwise the complete object
* `configDefaults` would get replaced)
* - vendorNgModules arrays are uniquely concatenated (otherwise it would be
* overriden, and the same angular module dependency would be added)
*
* @param {Grunt} grunt - Running Grunt instance
* @param {object} pluginConfig - Plugin configuration file
*/
function mergePluginConfig(grunt, pluginConfig) {
var pluginAppConfig = pluginConfig.appConfig,
config = grunt.config();
if (pluginAppConfig) {
//Merge properties of configDefaults objects
Object.assign(config.appConfig.configDefaults, pluginAppConfig.configDefaults);
//Uniquely concatenate vendorNgModules arrays
config.appConfig.vendorNgModules = concatUniquely(config.appConfig.vendorNgModules,
pluginAppConfig.vendorNgModules || []);
delete pluginConfig.appConfig;
grunt.config('appConfig', config.appConfig);
}
Object.keys(pluginConfig).forEach(function (key) {
switch (key) {
case 'filesToCopy':
case 'vendorCss':
case 'appCss':
case 'serverTasks':
case 'commonDeps':
config[key] = config[key].concat(pluginConfig[key]);
break;
default:
Object.assign(config[key], pluginConfig[key]);
}
grunt.config(key, config[key]);
});
}
/**
* Adds path to language files to the build_lang_files task configuration.
* @param {type} grunt
* @param {type} path
* @returns {undefined}
*/
function addLangFiles(grunt, path) {
var files = grunt.config('build_lang_files.all.files');
grunt.verbose.writeln('Adding path to lang files:' + path);
files[0].src.push(path);
grunt.config('build_lang_files.all.files', files);
}
/**
* Load plugins components located in plugins driectories:
* - Grunt tasks in /tasks folder
* - Configuration - configuration object to be merged (override) main configuration
* - Assets - Files inside the static, views and test folders (except for .css),
* to be copied over.
* - CSS - Plugin's css files.
* @param {Grunt} grunt - Running Grunt instance
* @param {string[]} pluginsNames - Array of plugins names to be loaded
* @param {string[]} pluginsDirs - Array of directories paths where to look for
* plugins
* @param {string} configPath - Relative path to configuration file
* @param {string} defaultConfigPath - Relative path to the default configuration file
* @param {string} tasksPath - Relative path to the grunt tasks
* @param {string} langPath - Relative path to the language (i18n) files
* @returns {undefined}
*/
function loadPlugins(grunt, pluginsNames, pluginsDirs, configPath, defaultConfigPath, tasksPath, langPath) {
pluginsNames.forEach(function (pluginName) {
grunt.verbose.writeln();
grunt.verbose.ok('Loading plugin ' + pluginName);
var path = findPluginPath(grunt, pluginName, pluginsDirs),
config;
if (path) {
loadTasks(grunt, path + tasksPath);
config = loadConfigFile(grunt, path + configPath, path + defaultConfigPath);
if (config) {
addPluginModule(grunt, config, getPluginModule(grunt, path, pluginName), pluginsDirs);
mergePluginConfig(grunt, config);
grunt.verbose.writeln('Merged ' + pluginName + ' configuration with main configuration');
}
addPluginFoldersPathsToCopy(grunt, path, [
//.css and lang files are handled by cssmin (or concat) and
//build_lang_files tasks respectively
'static/**/*', '!static/css/**/*', '!static/lang/**/*',
'views/**/*', 'test/**/*'
]);
addCssPaths(grunt, path);
addLangFiles(grunt, path + langPath);
} else {
grunt.log.error('Could not find plugin ' + pluginName + ' in any'
+ ' of these locations: [' + pluginsDirs + ']');
}
});
}
/**
* Returns paths for plugins components.
* @param {string[]} pluginsDirs - Array of directories paths where to look for
* plugins
* @param {string} pathType - Plugin's component path
* @returns {string[]} List of paths to the plugin's components
*/
function getPluginsPathFor(pluginsDirs, pathType, pluginsList, grunt) {
if (pluginsList && pluginsList.length) {
var result = [];
pluginsDirs.forEach(function (pluginDir) {
pluginsList.forEach(function (pluginName) {
result.push(pluginDir + '/' + pluginName);
});
});
result = result.filter(function (path) {
return grunt.file.exists(path);
}).map(function (path) {
return path + pathType;
});
return result;
}
return pluginsDirs.map(function (path) {
return path + pathType;
});
}
/**
* Creates a function to be called after the requirejs optimizer finished creating
* new build.
* @param {Grunt} grunt - Running Grunt instance
* @returns {function} Function which parses build summary to detect duplicates.
*/
function checkForDuplicatesInBundles(grunt) {
/**
* Performs check on created r.js bundles if they consist of duplicates,
* which is presence of the same module in two separate bundles. This might
* cause unexpected behaviour when the same module is defined twice of more
* times.
*
* In case of a duplicate ('config' is object exported by config.js):
* - Check if this module could be inlcuded in config.commonDeps list (like jQuery)
* - Check config.appConfig.requireOnStart list - This list is made specially
* for angular libraries that needs to be loaded prior to creating this
* project main angular module, so they can be included as angular module
* dependencies.
* - Check config.appConfig.plugins.pluginDeps
*
* @param {function} callback - Function to call when finished, to signal
* requirejs optimizer that it can continue.
* @param {string} buildSummary - Optimizer result summary
* @returns {undefined}
*/
return function (callback, buildSummary) {
var bundleName,
build = {},
duplicates;
buildSummary
.split(/\r\n|\r|\n/g)
.forEach(function (line) {
//New line indicating new bundle file
if (!line.length) {
bundleName = null;
} else if (!bundleName) {
bundleName = line;
} else if (line !== '----------------') {
build[line] = build[line] || [];
build[line].push(bundleName);
}
});
duplicates = Object.keys(build).filter(function (module) {
return build[module].length > 1;
});
if (duplicates.length) {
grunt.log.subhead('Duplicates found in requirejs build:');
duplicates
.forEach(function (module) {
grunt.log.warn('Module ' + module + ' is duplicated in this bundles: ', build[module]);
});
callback(new Error('r.js built bundles with duplicated modules. Please check dependencies and pluginDeps in config.js.'));
} else {
callback();
}
};
}
module.exports = {
loadConfigFile: loadConfigFile,
loadTasks: loadTasks,
checkForDuplicatesInBundles: checkForDuplicatesInBundles,
getPluginsPathFor: getPluginsPathFor,
loadPlugins: loadPlugins,
printObject: printObject,
addLangFiles: addLangFiles
};