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