1module.exports = function(grunt) { 2 grunt.loadNpmTasks('grunt-contrib-uglify'); 3 grunt.loadNpmTasks('grunt-gjslint'); 4 grunt.loadNpmTasks('grunt-checkrepo'); 5 grunt.loadNpmTasks('grunt-karma'); 6 grunt.loadNpmTasks('grunt-saucelabs'); 7 grunt.loadNpmTasks('grunt-git-status'); 8 grunt.loadNpmTasks('grunt-template'); 9 10 var targetConfig = require('./target-config.js'); 11 12 var sourceMap = require('source-map'); 13 14 var config = { 15 uglify: {}, 16 template: {}, 17 wrap: {}, 18 sourceMapConcat: {}, 19 }; 20 21 function concat(sources, target, defines) { 22 config.uglify[target] = { 23 options: { 24 sourceMap: true, 25 sourceMapName: target + '.map', 26 wrap: false, 27 compress: { 28 global_defs: defines, 29 dead_code: false 30 }, 31 mangle: false 32 }, 33 nonull: true, 34 dest: target, 35 src: sources 36 }; 37 return 'uglify:' + target; 38 } 39 40 function compress(source, target, defines) { 41 var name = concat([source], target, defines); 42 var record = config.uglify[target]; 43 record.options.sourceMapIn = source + '.map'; 44 record.options.banner = grunt.file.read('templates/boilerplate'); 45 record.options.wrap = true; 46 record.options.compress.dead_code = true; 47 record.options.mangle = { eval: true }; 48 return name; 49 } 50 51 function genTarget(target) { 52 var config = targetConfig[target]; 53 var newGens = [ 54 generateFromTemplate('templates/web-animations.js', {target: target}, target + '.dev.js'), 55 generateFromTemplate('templates/web-animations.html', {src: config.src}, target + '.dev.html'), 56 generateFromTemplate('templates/runner.html', {target: target}, 'test/runner-' + target + '.html')]; 57 return newGens; 58 } 59 60 function generateFromTemplate(source, data, target) { 61 var targetSpec = {}; 62 targetSpec[target] = [source]; 63 config.template[target] = { 64 options: { 65 data: data 66 }, 67 files: targetSpec 68 } 69 return 'template:' + target; 70 } 71 72 function guard(source, target) { 73 config.wrap[target] = { 74 source: source, 75 preamble: '(function() {\n' + 76 ' if (document.documentElement.animate) {\n' + 77 ' var player = document.documentElement.animate([], 0);\n' + 78 ' var load = true;\n' + 79 ' if (player) {\n' + 80 ' load = false;\n' + 81 ' "play|currentTime|pause|reverse|playbackRate|cancel|finish|startTime|playState".split("|").forEach(function(t) {\n' + 82 ' if (player[t] === undefined) {\n' + 83 ' load = true;\n' + 84 ' }\n' + 85 ' });\n' + 86 ' }\n' + 87 ' if (!load) { return; }' + 88 ' }\n', 89 postamble: '})();' 90 }; 91 return 'wrap:' + target; 92 } 93 94 function concatWithMaps(sources, target) { 95 config.sourceMapConcat[target] = { 96 sources: sources 97 } 98 return 'sourceMapConcat:' + target; 99 }; 100 101 var concatDefines = { 102 WEB_ANIMATIONS_TESTING: false 103 }; 104 105 function buildWebAnimations1(target) { 106 var config = targetConfig[target]; 107 return genTarget(target).concat([ 108 concat(config.scopeSrc.concat(config.sharedSrc).concat(config.webAnimations1Src), 'inter-raw-' + target + '.js', concatDefines), 109 guard('inter-raw-' + target + '.js', 'inter-' + target + '.js'), 110 compress('inter-' + target + '.js', target + '.min.js', concatDefines) 111 ]); 112 } 113 114 function buildWebAnimationsNext(target) { 115 var config = targetConfig[target]; 116 return genTarget(target).concat([ 117 concat(config.scopeSrc.concat(config.sharedSrc), 'inter-' + target + '-preamble.js', concatDefines), 118 concat(config.webAnimations1Src, 'inter-component-' + target + 'web-animations-1.js', concatDefines), 119 guard('inter-component-' + target + 'web-animations-1.js', 'inter-guarded-' + target + '-web-animations-1.js'), 120 concat(config.webAnimationsNextSrc, 'inter-component-' + target + '.js', concatDefines), 121 concatWithMaps(['inter-' + target + '-preamble.js', 'inter-guarded-' + target + '-web-animations-1.js', 'inter-component-' + target + '.js'], 122 'inter-' + target + '.js'), 123 compress('inter-' + target + '.js', target + '.min.js', concatDefines) 124 ]); 125 } 126 127 grunt.registerTask('web-animations', buildWebAnimations1('web-animations')); 128 grunt.registerTask('web-animations-next', buildWebAnimationsNext('web-animations-next')); 129 grunt.registerTask('web-animations-next-lite', buildWebAnimationsNext('web-animations-next-lite')); 130 131 var testTargets = {'web-animations': {}, 'web-animations-next': {}}; 132 133 grunt.initConfig({ 134 uglify: config.uglify, 135 template: config.template, 136 wrap: config.wrap, 137 sourceMapConcat: config.sourceMapConcat, 138 checkrepo: { 139 all: { 140 clean: true, 141 }, 142 }, 143 'git-status': { 144 all: { 145 }, 146 }, 147 gjslint: { 148 options: { 149 flags: [ 150 '--nojsdoc', 151 '--strict', 152 '--disable 7,121,110', // 7: Wrong blank line count 153 // 121: Illegal comma at end of object literal 154 // 110: Line too long 155 ], 156 reporter: { 157 name: 'console' 158 } 159 }, 160 all: { 161 src: [ 162 'src/*.js', 163 'test/*.js', 164 'test/js/*.js', 165 ], 166 } 167 }, 168 test: testTargets, 169 sauce: testTargets, 170 }); 171 172 173 grunt.task.registerMultiTask('test', 'Run <target> tests under Karma', function() { 174 var done = this.async(); 175 var karmaConfig = require('karma/lib/config').parseConfig(require('path').resolve('test/karma-config.js'), {}); 176 var config = targetConfig[this.target]; 177 karmaConfig.files = ['test/runner.js'].concat(config.src, config.test); 178 var karmaServer = require('karma').server; 179 karmaServer.start(karmaConfig, function(exitCode) { 180 done(exitCode === 0); 181 }); 182 }); 183 184 grunt.task.registerMultiTask('sauce', 'Run <target> tests under Karma on Saucelabs', function() { 185 var done = this.async(); 186 var karmaConfig = require('karma/lib/config').parseConfig(require('path').resolve('test/karma-config-ci.js'), {}); 187 var config = targetConfig[this.target]; 188 karmaConfig.files = ['test/runner.js'].concat(config.src, config.test); 189 karmaConfig.sauceLabs.testName = 'web-animation-next ' + this.target + ' Unit tests'; 190 var karmaServer = require('karma').server; 191 karmaServer.start(karmaConfig, function(exitCode) { 192 done(exitCode === 0); 193 }); 194 }); 195 196 grunt.task.registerMultiTask('sourceMapConcat', 'concat source files and produce combined source map', 197 function() { 198 var sources = this.data.sources.map(grunt.file.read); 199 var sourceMaps = this.data.sources.map(function(f) { return grunt.file.read(f + '.map'); }); 200 var out = ""; 201 var outMapGenerator = new sourceMap.SourceMapGenerator({file: this.target}); 202 var lineDelta = 0; 203 for (var i = 0; i < sources.length; i++) { 204 out += sources[i]; 205 new sourceMap.SourceMapConsumer(sourceMaps[i]).eachMapping(function(mapping) { 206 outMapGenerator.addMapping({ 207 generated: {line: mapping.generatedLine + lineDelta, column: mapping.generatedColumn}, 208 original: {line: mapping.originalLine, column: mapping.originalColumn}, 209 source: mapping.source, name: mapping.name}); 210 }); 211 var sourceLines = sources[i].split('\n'); 212 lineDelta += sourceLines.length; 213 if (sources[i][sources[i].length - 1] !== '\n') { 214 out += '\n'; 215 } 216 } 217 grunt.file.write(this.target, out); 218 grunt.file.write(this.target + '.map', outMapGenerator.toString()); 219 }); 220 221 grunt.task.registerMultiTask('wrap', 'Wrap <target> source file and update source map', 222 function() { 223 var inFile = grunt.file.read(this.data.source); 224 var inMap = grunt.file.read(this.data.source + '.map'); 225 var inLines = inFile.split('\n'); 226 var i = 0; 227 228 // Discover copyright header 229 while (inLines[i].length < 2 || inLines[i].substring(0, 2) == '//') { 230 i++; 231 } 232 233 // Fix mapping footer 234 var postamble = this.data.postamble; 235 if (inLines[inLines.length - 1].substring(0, 21) == '//# sourceMappingURL=') { 236 postamble += '\n//# sourceMappingURL=' + this.target + '.map'; 237 } 238 239 if (i > 0) { 240 var banner = inLines.slice(0, i).join('\n') + '\n'; 241 } else { 242 var banner = ''; 243 } 244 245 var source = inLines.slice(i, inLines.length - 1).join('\n'); 246 247 grunt.file.write(this.target, banner + this.data.preamble + source + postamble); 248 var preLines = this.data.preamble.split('\n'); 249 var lineDelta = preLines.length; 250 if (this.data.preamble[this.data.preamble.length - 1] == '\n') { 251 var charDelta = 0; 252 } else { 253 var charDelta = preLines[lineDelta - 1].length; 254 lineDelta -= 1; 255 } 256 var inMapConsumer = new sourceMap.SourceMapConsumer(inMap); 257 var outMapGenerator = new sourceMap.SourceMapGenerator({file: this.target}); 258 inMapConsumer.eachMapping(function(mapping) { 259 if (mapping.generatedLine == i + 1) { 260 mapping.generatedColumn += charDelta; 261 } 262 mapping.generatedLine += lineDelta; 263 outMapGenerator.addMapping( 264 {generated: {line: mapping.generatedLine, column: mapping.generatedColumn}, 265 original: {line: mapping.originalLine, column: mapping.originalColumn}, 266 source: mapping.source, name: mapping.name}); 267 }); 268 grunt.file.write(this.target + '.map', outMapGenerator.toString()); 269 }); 270 271 grunt.task.registerTask('clean', 'Remove files generated by grunt', function() { 272 grunt.file.expand('web-animations*').concat(grunt.file.expand('test/runner-*.html')).concat(grunt.file.expand('inter-*')).forEach(function(file) { 273 grunt.file.delete(file); 274 grunt.log.writeln('File ' + file + ' removed'); 275 }); 276 }); 277 278 grunt.task.registerTask('default', ['web-animations', 'web-animations-next', 'web-animations-next-lite', 'gjslint']); 279}; 280