tao-test/app/taoQtiItem/views/build/grunt/portableelement.js

253 lines
10 KiB
JavaScript

/**
* Register the portable element compilation task for a given TAO extension
*
* @example compile all portable element in the extension qtiItemPci
* grunt portableelement --extension=qtiItemPci
*
* @example compile only the likertScaleInteraction in the extension qtiItemPci
* grunt portableelement --extension=qtiItemPci --identifier=likertScaleInteraction
*
* @example compile only the likertScaleInteraction in the extension qtiItemPci using short param
* grunt portableelement -e=qtiItemPci -i=likertScaleInteraction
*/
module.exports = function (grunt) {
'use strict';
var root = grunt.option('root');
var requirejs = grunt.option('requirejsModule');
var ext = require(root + '/tao/views/build/tasks/helpers/extensions')(grunt, root);
var amdConfig = require(root + '/tao/views/build/config/requirejs.build.json');
var portableModels = [
{
type : 'PCI',
file : 'pciCreator.json',
searchPattern : '/views/js/pciCreator/**/pciCreator.json'
},
{
type : 'IMSPCI',
file : 'imsPciCreator.json',
searchPattern : '/views/js/pciCreator/**/imsPciCreator.json'
},
{
type : 'PIC',
file : 'picCreator.json',
searchPattern : '/views/js/picCreator/**/picCreator.json'
}
];
grunt.config.merge({
portableelement: {
options: {
optimize: 'uglify2',
uglify2: {
mangle: false,
output: {
'max_line_len': 666
}
},
preserveLicenseComments: false,
optimizeAllPluginResources: true,
findNestedDependencies: true,
skipDirOptimize: true,
optimizeCss: 'none',
buildCss: false,
inlineText: true,
skipPragmas: true,
generateSourceMaps: true,
removeCombined: true,
baseUrl: amdConfig.baseUrl,
shim : amdConfig.shim,
excludeShallow: ['mathJax'],
exclude: ['qtiCustomInteractionContext', 'qtiInfoControlContext']
.concat(ext.getExtensionSources('taoQtiItem', ['views/js/qtiItem/**/*.js', 'views/js/qtiCreator/**/*.js'], true))
.concat(ext.getExtensionSources('taoItems', ['views/js/**/*.js'], true)),
paths: Object.assign({
'taoItems': root + '/taoItems/views/js',
'taoItemsCss': root + '/taoItems/views/css',
'taoQtiItem': root + '/taoQtiItem/views/js',
'taoQtiItemCss': root + '/taoQtiItem/views/css',
'qtiCustomInteractionContext': root + '/taoQtiItem/views/js/runtime/qtiCustomInteractionContext',
'qtiInfoControlContext': root + '/taoQtiItem/views/js/runtime/qtiInfoControlContext'
}, amdConfig.paths, require('./paths.json'))
}
}
});
/**
* Get the name of entry point for the portable element
* @param {Object} pciRuntimeData - the runtime object from the portable element manifest
* @param {String} prefix - the prefix identifier of the portable element
* @returns {String}
*/
function getHookFileName(pciRuntimeData, prefix) {
if (Array.isArray(pciRuntimeData.src) && pciRuntimeData.src.length > 0) {
//by convention the first module is the hook file
return pciRuntimeData.src[0]
.replace(/\.js$/i, '')
.replace(/^\.\//, prefix + '/');
}
}
/**
* Get the name of the min file for the portable element
* @param {Object} pciRuntimeData - the runtime object from the portable element manifest
* @returns {String}
*/
function getMinHookFile(pciRuntimeData) {
var minHookFile;
if (pciRuntimeData.hook) {
minHookFile = pciRuntimeData.hook;
} else if (Array.isArray(pciRuntimeData.libraries) && pciRuntimeData.libraries.length > 0) {
//by convention the first module is the min file
minHookFile = pciRuntimeData.libraries[0];
}
if(minHookFile){
return minHookFile.replace(/^\.\//, '');
}
}
/**
* Print report when all promises are resolved/rejected
* @param {Array|Function} report
*/
function printReport(report) {
if(Array.isArray(report)){
report.forEach(function (r) {
printReport(r);
});
}else if(typeof report === 'function'){
report.call();
}
}
/**
* Get the portable element model from its manifest file
* @param file
* @returns {*}
*/
function getPortableModelFromFile(file){
var model;
portableModels.forEach(function(portableModel){
if(file.match(new RegExp('\/' + portableModel.file + '$'))){
model = {};
model.type = portableModel.type;
model.manifest = grunt.file.readJSON(file);
model.basePath = file.replace('/' + portableModel.file, '');
model.id = model.manifest.typeIdentifier;
model.map = [
{
name : 'RUNTIME',
src : getHookFileName(model.manifest.runtime, model.id),
min : getMinHookFile(model.manifest.runtime)
},
{
name : 'CREATOR',
src : getHookFileName(model.manifest.creator, model.id),
min : getMinHookFile(model.manifest.creator)
}
];
return false;
}
});
return model;
}
grunt.registerTask('portableelement', 'Compile Portable Elements', function () {
var done = this.async();//async mode because requirejs optimization is an async process
var extension = grunt.option('extension') || grunt.option('e');
var selectedId = grunt.option('identifier') || grunt.option('i');
var manifests, compileTasks;
var self = this;
if(!extension){
grunt.log.error('Missing the extension in param, e.g. "grunt portableelement -e=qtiItemPci"');
return done();
}
grunt.log.writeln('Started optimizing portable elements in extension "' + extension + '"');
if(selectedId){
grunt.log.writeln('Only searching portable element "' + selectedId + '"');
}
manifests = portableModels.reduce(function(acc, model) {
return grunt.file.expand(root + '/' + extension + model.searchPattern).concat(acc);
}, []);
compileTasks = manifests.map(function(file){
var config;
var model = getPortableModelFromFile(file);
var subcompilationPromises = [];
if(!model){
//not the targeted one
return Promise.resolve([grunt.log.error.bind(null, 'invalid portable manifest file ' + file)]);
}
if(selectedId && selectedId !== model.id){
//not the targeted one
return Promise.resolve([]);
}
subcompilationPromises.push([grunt.log.subhead.bind(null, model.type + ' "' + model.id + '" found in manifest "' + file + '" ...')]);
model.map.forEach(function(compilMap){
subcompilationPromises.push(new Promise(function(resolve, reject){
var report = [];
if (!compilMap.src) {
//when no source file has been found, skip the compilation
report.push(grunt.log.ok.bind(null, 'No source file defined for ' + model.type +' "' + model.id + '" - '+compilMap.name));
return resolve(report);
}
//extends the default configuration with portable element specific build config
config = self.options({
name: compilMap.src,
out: model.basePath + '/' + compilMap.min,
//this wrapping is required to allow self loading portable element module.
wrap: {
start: '',
end: "define(['" + compilMap.src + "'],function(" + model.type + '){return ' + model.type + '});'
}
//(note: the option "insertRequire" does not work because it is resolved asynchronously)
});
// Add the path to the given extension for portableLib resolution
config.paths[extension] = root + '/' + extension + '/views/js';
config.paths[model.id] = model.basePath;
requirejs.optimize(config, function (buildResponse) {
report.push(grunt.log.ok.bind(null, 'Compiled ' + model.type + ' "' + model.id + '" - ' + compilMap.name));
report.push(grunt.log.writeln.bind(null, buildResponse));
resolve(report);
}, function (err) {
report.push(grunt.log.error.bind(null, model.type + ' "' + model.id + '" ' + compilMap.name + ' cannot be compiled'));
report.push(grunt.log.error.bind(null, err));
reject(report);
});
}));
});
return Promise.all(subcompilationPromises);
});
if (compileTasks.length) {
Promise.all(compileTasks).then(function (report) {
printReport(report);
done();
}).catch(function (report) {
printReport(report);
done();
});
} else {
grunt.log.writeln('no portable element to be compiled in extension "'+extension+'"');
done();
}
});
};