diff options
Diffstat (limited to 'build/tasks/build.js')
-rw-r--r-- | build/tasks/build.js | 381 |
1 files changed, 174 insertions, 207 deletions
diff --git a/build/tasks/build.js b/build/tasks/build.js index 63dfc1ee8..188e1e452 100644 --- a/build/tasks/build.js +++ b/build/tasks/build.js @@ -1,132 +1,66 @@ /** - * Special concat/build task to handle various jQuery build requirements - * Concats AMD modules, removes their definitions, + * Special build task to handle various jQuery build requirements. + * Compiles JS modules into one bundle, sets the custom AMD name, * and includes/excludes specified modules */ "use strict"; module.exports = function( grunt ) { - var fs = require( "fs" ), - requirejs = require( "requirejs" ), - Insight = require( "insight" ), - pkg = require( "../../package.json" ), - srcFolder = __dirname + "/../../src/", - rdefineEnd = /\}\s*?\);[^}\w]*$/, - read = function( fileName ) { - return grunt.file.read( srcFolder + fileName ); - }, - - // Catch `// @CODE` and subsequent comment lines event if they don't start - // in the first column. - wrapper = read( "wrapper.js" ).split( /[\x20\t]*\/\/ @CODE\n(?:[\x20\t]*\/\/[^\n]+\n)*/ ), - - config = { - baseUrl: "src", - name: "jquery", - - // Allow strict mode - useStrict: true, - - // We have multiple minify steps - optimize: "none", - - // Include dependencies loaded with require - findNestedDependencies: true, - - // Avoid inserting define() placeholder - skipModuleInsertion: true, - - // Avoid breaking semicolons inserted by r.js - skipSemiColonInsertion: true, - wrap: { - start: wrapper[ 0 ].replace( /\/\*\s*eslint(?: |-).*\s*\*\/\n/, "" ), - end: wrapper[ 1 ] - }, - rawText: {}, - 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 ) { - var amdName; - - // Convert var modules - if ( /.\/var\//.test( path.replace( process.cwd(), "" ) ) ) { - contents = contents - .replace( - /define\(\s*(["'])[\w\W]*?\1[\w\W]*?return/, - "var " + - ( /var\/([\w-]+)/.exec( name )[ 1 ] ) + - " =" - ) - .replace( rdefineEnd, "" ); - - } else { - - contents = contents - .replace( /\s*return\s+[^\}]+(\}\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\([^{]*?{\s*(?:("|')use strict\1(?:;|))?/, "" ) - .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]+$/, "" ); - } - - // AMD Name - if ( ( amdName = grunt.option( "amd" ) ) != null && /^exports\/amd$/.test( name ) ) { - if ( amdName ) { - grunt.log.writeln( "Naming jQuery with AMD name: " + amdName ); - } else { - grunt.log.writeln( "AMD name now anonymous" ); - } - - // Remove the comma for anonymous defines - contents = contents - .replace( /(\s*)"jquery"(\,\s*)/, amdName ? "$1\"" + amdName + "\"$2" : "" ); - - } - return contents; - } + const fs = require( "fs" ); + const path = require( "path" ); + const rollup = require( "rollup" ); + const rollupHypothetical = require( "rollup-plugin-hypothetical" ); + const Insight = require( "insight" ); + const pkg = require( "../../package.json" ); + const srcFolder = path.resolve( `${ __dirname }/../../src` ); + const read = function( fileName ) { + return grunt.file.read( `${ srcFolder }/${ fileName }` ); + }; + + // Catch `// @CODE` and subsequent comment lines event if they don't start + // in the first column. + const wrapper = read( "wrapper.js" ) + .split( /[\x20\t]*\/\/ @CODE\n(?:[\x20\t]*\/\/[^\n]+\n)*/ ); + + const inputFileName = "jquery.js"; + const inputRollupOptions = { + input: `${ srcFolder }/${ inputFileName }` + }; + const outputRollupOptions = { + + // The ESM format is not actually used as we strip it during + // the build; it's just that it doesn't generate any extra + // wrappers so there's nothing for us to remove. + format: "esm", + + intro: wrapper[ 0 ] + .replace( /\n*$/, "" ), + outro: wrapper[ 1 ] + .replace( /^\n*/, "" ) + }; + const rollupHypotheticalOptions = { + allowFallthrough: true, + files: {} + }; grunt.registerMultiTask( "build", "Concatenate source, remove sub AMD definitions, " + "(include/exclude modules with +/- flags), embed date/version", - function() { - var flag, index, - done = this.async(), - flags = this.flags, - optIn = flags[ "*" ], - name = grunt.option( "filename" ), - minimum = this.data.minimum, - removeWith = this.data.removeWith, - excluded = [], - included = [], - version = grunt.config( "pkg.version" ), + async function() { + const done = this.async(); + + try { + let flag, index; + const flags = this.flags; + const optIn = flags[ "*" ]; + let name = grunt.option( "filename" ); + const minimum = this.data.minimum; + const removeWith = this.data.removeWith; + const excluded = []; + const included = []; + let version = grunt.config( "pkg.version" ); /** * Recursively calls the excluder to remove on all modules in the list @@ -134,15 +68,16 @@ module.exports = function( grunt ) { * @param {String} [prepend] Prepend this to the module name. * Indicates we're walking a directory */ - excludeList = function( list, prepend ) { + const excludeList = ( list, prepend ) => { if ( list ) { - prepend = prepend ? prepend + "/" : ""; + prepend = prepend ? `${ prepend }/` : ""; list.forEach( function( module ) { // Exclude var modules as well if ( module === "var" ) { excludeList( - fs.readdirSync( srcFolder + prepend + module ), prepend + module + fs.readdirSync( `${ srcFolder }/${ prepend }${ module }` ), + prepend + module ); return; } @@ -164,7 +99,7 @@ module.exports = function( grunt ) { } } ); } - }, + }; /** * Adds the specified module to the excluded or included list, depending on the flag @@ -172,11 +107,11 @@ module.exports = function( grunt ) { * the src directory starting with + or - to indicate * whether it should included or excluded */ - excluder = function( flag ) { - var additional, - m = /^(\+|\-|)([\w\/-]+)$/.exec( flag ), - exclude = m[ 1 ] === "-", - module = m[ 2 ]; + const excluder = flag => { + let additional; + const m = /^(\+|\-|)([\w\/-]+)$/.exec( flag ); + const exclude = m[ 1 ] === "-"; + const module = m[ 2 ]; if ( exclude ) { @@ -192,7 +127,10 @@ module.exports = function( grunt ) { // These are the removable dependencies // It's fine if the directory is not there try { - excludeList( fs.readdirSync( srcFolder + module ), module ); + excludeList( + fs.readdirSync( `${ srcFolder }/${ module }` ), + module + ); } catch ( e ) { grunt.verbose.writeln( e ); } @@ -204,7 +142,7 @@ module.exports = function( grunt ) { if ( additional ) { excludeList( additional.remove || additional ); if ( additional.include ) { - included = included.concat( additional.include ); + included.push( ...additional.include ); grunt.log.writeln( "+" + additional.include ); } } @@ -217,93 +155,122 @@ module.exports = function( grunt ) { } }; - // Filename can be passed to the command line using - // command line options - // e.g. grunt build --filename=jquery-custom.js - name = name ? ( "dist/" + name ) : this.data.dest; + // Filename can be passed to the command line using + // command line options + // e.g. grunt build --filename=jquery-custom.js + name = name ? `dist/${ name }` : this.data.dest; - // append commit id to version - if ( process.env.COMMIT ) { - version += " " + process.env.COMMIT; - } + // 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 ); - } + // 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 ); + } - // Replace exports/global with a noop noConflict - if ( ( index = excluded.indexOf( "exports/global" ) ) > -1 ) { - config.rawText[ "exports/global" ] = "define(['../core']," + - "function( jQuery ) {\njQuery.noConflict = function() {};\n});"; - excluded.splice( index, 1 ); - } + // Remove the jQuery export from the entry file, we'll use our own + // custom wrapper. + rollupHypotheticalOptions.files[ inputRollupOptions.input ] = read( inputFileName ) + .replace( /\n*export default jQuery;\n*/, "\n" ); + + // Replace exports/global with a noop noConflict + if ( ( index = excluded.indexOf( "exports/global" ) ) > -1 ) { + rollupHypotheticalOptions.files[ `${ srcFolder }/exports/global.js` ] = + "import jQuery from \"../core.js\";\n\n" + + "jQuery.noConflict = function() {};"; + excluded.splice( index, 1 ); + } - grunt.verbose.writeflags( excluded, "Excluded" ); - grunt.verbose.writeflags( included, "Included" ); + // Set a desired AMD name. + let amdName = grunt.option( "amd" ); + if ( amdName != null ) { + if ( amdName ) { + grunt.log.writeln( "Naming jQuery with AMD name: " + amdName ); + } else { + grunt.log.writeln( "AMD name now anonymous" ); + } - // append excluded modules to version - if ( excluded.length ) { - version += " -" + excluded.join( ",-" ); + // Remove the comma for anonymous defines + rollupHypotheticalOptions.files[ `${ srcFolder }/exports/amd.js` ] = + read( "exports/amd.js" ) + .replace( /(\s*)"jquery"(\,\s*)/, amdName ? "$1\"" + amdName + "\"$2" : "" ); + } - // 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 ); + grunt.verbose.writeflags( excluded, "Excluded" ); + grunt.verbose.writeflags( included, "Included" ); - // Have to use shallow or core will get excluded since it is a dependency - config.excludeShallow = excluded; - } - config.include = 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 ); + + // Replace excluded modules with empty sources. + for ( const module of excluded ) { + rollupHypotheticalOptions.files[ `${ srcFolder }/${ module }.js` ] = ""; + } + } + + // Turn off opt-in if necessary + if ( !optIn ) { - /** - * Handle Final output from the optimizer - * @param {String} compiled - */ - config.out = function( compiled ) { - compiled = compiled + // Remove the default inclusions, they will be overwritten with the explicitly + // included ones. + rollupHypotheticalOptions.files[ inputRollupOptions.input ] = ""; + + } + + // Import the explicitly included modules. + if ( included.length ) { + rollupHypotheticalOptions.files[ inputRollupOptions.input ] += included + .map( module => `import "./${module}.js";` ) + .join( "\n" ); + } + + const bundle = await rollup.rollup( { + ...inputRollupOptions, + plugins: [ rollupHypothetical( rollupHypotheticalOptions ) ] + } ); + + const { output: [ { code } ] } = await bundle.generate( outputRollupOptions ); + + const compiledContents = code // Embed Version .replace( /@VERSION/g, version ) // Embed Date // yyyy-mm-ddThh:mmZ - .replace( /@DATE/g, ( new Date() ).toISOString().replace( /:\d+\.\d+Z$/, "Z" ) ); - - // 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.jquery = "define([" + - ( included.length ? included.join( "," ) : "" ) + - "]);"; - } + .replace( + /@DATE/g, + ( new Date() ).toISOString() + .replace( /:\d+\.\d+Z$/, "Z" ) + ); - // Trace dependencies and concatenate files - requirejs.optimize( config, function( response ) { - grunt.verbose.writeln( response ); + grunt.file.write( name, compiledContents ); grunt.log.ok( "File '" + name + "' created." ); done(); - }, function( err ) { + } catch ( err ) { done( err ); - } ); + } } ); // Special "alias" task to make custom build creation less grawlix-y @@ -315,17 +282,17 @@ module.exports = function( grunt ) { // // grunt build:*:*:+ajax:-dimensions:-effects:-offset grunt.registerTask( "custom", function() { - var args = this.args, - modules = args.length ? args[ 0 ].replace( /,/g, ":" ) : "", - done = this.async(), - insight = new Insight( { - trackingCode: "UA-1076265-4", - pkg: pkg - } ); + const args = this.args; + const modules = args.length ? args[ 0 ].replace( /,/g, ":" ) : ""; + const done = this.async(); + const insight = new Insight( { + trackingCode: "UA-1076265-4", + pkg: pkg + } ); function exec( trackingAllowed ) { - var tracks = args.length ? args[ 0 ].split( "," ) : []; - var defaultPath = [ "build", "custom" ]; + let tracks = args.length ? args[ 0 ].split( "," ) : []; + const defaultPath = [ "build", "custom" ]; tracks = tracks.map( function( track ) { return track.replace( /\//g, "+" ); @@ -335,7 +302,7 @@ module.exports = function( grunt ) { // Track individuals tracks.forEach( function( module ) { - var path = defaultPath.concat( [ "individual" ], module ); + const path = defaultPath.concat( [ "individual" ], module ); insight.track.apply( insight, path ); } ); |