build/tasks/build.js (145 lines of code) (raw):

/** * Special concat/build task to handle various jQuery build requirements * Concats AMD modules, removes their definitions, and includes/excludes specified modules */ module.exports = function( grunt ) { "use strict"; var fs = require( "fs" ); var requirejs = require("requirejs"); var srcFolder = __dirname + "/../../xf/"; var rdefineEnd = /\}\);[^}\w]*$/; var config = { baseUrl: "xf/src", name: "xf.framework", out: "js/xf.js", // We have multiple minify steps optimize: "none", // Include dependencies loaded with require findNestedDependencies: true, // Avoid breaking semicolons inserted by r.js skipSemiColonInsertion: true, wrap: { start: '/*! X-Framework @DATE */\n;(function (window, $, BB) {', end: '}).call(this, window, $, Backbone); \n\n' }, rawText: {}, paths: { jquery: 'empty:', underscore: 'empty:', backbone: 'empty:' }, onBuildWrite: convert }; /** * Strip all definitions generated by requirejs * Convert "var" modules to var declarations * "var module" means the module only contains a return statement that should be converted to a var declaration * This is indicated by including the file in any "var" folder * @param {String} name * @param {String} path * @param {String} contents The contents to be written (including their AMD wrappers) */ function convert( name, path, contents ) { // Convert var modules. if ( /.\/var\//.test( path ) ) { contents = contents .replace( /define\([\w\W]*?return/, "var " + (/var\/([\w-]+)/.exec(name)[1]) + " =" ) .replace( rdefineEnd, "" ); } else { // Ignore exports, XF is exported when it is declared in xf.core.js. contents = contents .replace( /\s*return\s+[^\}]+(\}\);[^\w\}]*)$/, "$1" ) // Multiple exports .replace( /\s*exports\.\w+\s*=\s*\w+;/g, "" ); // Remove define wrappers, closure ends, and empty declarations. contents = contents .replace( /define\([^{]*?{/, "" ) .replace( rdefineEnd, "" ); // Remove anything wrapped with // /* ExcludeStart */ /* ExcludeEnd */ // or a single line directly after a // BuildExclude comment contents = contents .replace( /\/\*\s*ExcludeStart\s*\*\/[\w\W]*?\/\*\s*ExcludeEnd\s*\*\//ig, "" ) .replace( /\/\/\s*BuildExclude\n\r?[\w\W]*?\n\r?/ig, "" ); // Remove empty definitions contents = contents .replace( /define\(\[[^\]]+\]\)[\W\n]+$/, "" ); } return contents; } grunt.registerMultiTask( "build", "Concatenate source, remove sub AMD definitions, (include/exclude modules with +/- flags), embed date/version", function() { var flag; var index; var done = this.async(); var flags = this.flags; var optIn = flags[ "*" ]; var name = this.data.dest; var minimum = ['core']; var removeWith = []; var excluded = []; var included = []; var version = grunt.config("pkg.version"); /** * Recursively calls the excluder to remove on all modules in the list. * @param {Array} list * @param {String} [prepend] Prepend this to the module name. Indicates we're walking a directory. */ var excludeList = function (list, prepend) { if (list) { prepend = prepend ? prepend + "/" : ""; list.forEach(function (module) { // Exclude var modules as well if (module === "var") { excludeList(fs.readdirSync(srcFolder + prepend + module), prepend + module); return; } if (prepend) { // Skip if this is not a js file and we're walking files in a dir. if (!(module = /([\w-\/]+)\.js$/.exec(module))) { return; } // Prepend folder name if passed. // Remove .js extension. module = prepend + module[1]; } // Avoid infinite recursion. if (excluded.indexOf(module) === -1) { excluder("-" + module); } }); } }; /** * Adds the specified module to the excluded or included list, depending on the flag * @param {String} flag A module path relative to the src directory starting with + or - to indicate whether it should included or excluded */ var excluder = function (flag) { var m = /^(\+|\-|)([\w\/\.-]+)$/.exec(flag); var exclude = m[ 1 ] === "-"; var module = m[ 2 ]; if (exclude) { // Can't exclude certain modules if (minimum.indexOf(module) === -1) { // Add to excluded if (excluded.indexOf(module) === -1) { excluded.push(module); // Exclude all files in the folder of the same name // These are the removable dependencies // It's fine if the directory is not there try { excludeList(fs.readdirSync(srcFolder + module), module); } catch (e) { grunt.verbose.writeln(e); } } // Check removeWith list excludeList(removeWith[ module ]); } else { grunt.log.error("Module \"" + module + "\" is a mimimum requirement."); } } else { grunt.log.writeln(flag); included.push(module); } }; // append commit id to version if ( process.env.COMMIT ) { version += " " + process.env.COMMIT; } // figure out which files to exclude based on these rules in this order: // dependency explicit exclude // > explicit exclude // > explicit include // > dependency implicit exclude // > implicit exclude // examples: // * none (implicit exclude) // *:* all (implicit include) // *:*:-css all except css and dependents (explicit > implicit) // *:*:-css:+effects same (excludes effects because explicit include is trumped by explicit exclude of dependency) // *:+effects none except effects and its dependencies (explicit include trumps implicit exclude of dependency) delete flags[ "*" ]; for ( flag in flags ) { excluder( flag ); } // Exclude second inclusion of xf.* modules, required by xf.ui components. // TODO(jauhen): Move xf/ui folder to proper location to avoid this hack. ['-../src/xf.core', '-../src/xf.utils', '-../src/xf.device', '-../src/dom/dom'].forEach(function (module) { excluder(module); }); grunt.verbose.writeflags( excluded, "Excluded" ); grunt.verbose.writeflags( included, "Included" ); // Append excluded modules to version. if ( excluded.length ) { version += " -" + excluded.join( ",-" ); // set pkg.version to version with excludes, so minified file picks it up grunt.config.set( "pkg.version", version ); grunt.verbose.writeln( "Version changed to " + version ); // Have to use shallow or core will get excluded since it is a dependency config.excludeShallow = excluded; } config.include = included; /** * Handle Final output from the optimizer. * @param {String} compiled */ config.out = function( compiled ) { compiled = compiled // Embed Date .replace( /@DATE/g, grunt.template.today("dd-mm-yyyy") ); // Write concatenated source to file grunt.file.write( name, compiled ); }; // Turn off opt-in if necessary if ( !optIn ) { // Overwrite the default inclusions with the explicit ones provided config.rawText.xf = "define([" + (included.length ? included.join(",") : "") + "]);"; } // Trace dependencies and concatenate files requirejs.optimize( config, function( response ) { grunt.verbose.writeln( response ); grunt.log.ok( "File '" + name + "' created." ); done(); }, function( err ) { done( err ); }); }); // Special "alias" task to make custom build creation less grawlix-y // Translation example // // grunt custom:+ajax,-dimensions,-effects,-offset // // Becomes: // // grunt build:*:*:+ajax:-dimensions:-effects:-offset grunt.registerTask( "custom", function() { var args = [].slice.call( arguments ), modules = args.length ? args[ 0 ].replace( /,/g, ":" ) : ""; grunt.log.writeln( "Creating custom build...\n" ); grunt.task.run([ "build:*:*:" + modules, "uglify", "dist" ]); }); };