Gruntfile.js 12.9 KB
Newer Older
Chris Rebert's avatar
Chris Rebert committed
1
2
3
/*!
 * Bootstrap's Gruntfile
 * http://getbootstrap.com
4
 * Copyright 2013-2016 The Bootstrap Authors
5
 * Copyright 2013-2016 Twitter, Inc.
Chris Rebert's avatar
Chris Rebert committed
6
7
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 */
8

Chris Rebert's avatar
Chris Rebert committed
9
module.exports = function (grunt) {
XhmikosR's avatar
XhmikosR committed
10
  'use strict';
11

12
13
14
  // Force use of Unix newlines
  grunt.util.linefeed = '\n';

Zlatan Vasović's avatar
Zlatan Vasović committed
15
  RegExp.quote = function (string) {
Chris Rebert's avatar
Chris Rebert committed
16
17
    return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
  };
18

Chris Rebert's avatar
Chris Rebert committed
19
  var fs = require('fs');
20
  var path = require('path');
Chris Rebert's avatar
Chris Rebert committed
21
  var isTravis = require('is-travis');
22
  var mq4HoverShim = require('mq4-hover-shim');
23
  var autoprefixerSettings = require('./grunt/autoprefixer-settings.js');
24
  var autoprefixer = require('autoprefixer')(autoprefixerSettings);
Mark Otto's avatar
Mark Otto committed
25

26
27
28
29
30
31
32
  var configBridge = grunt.file.readJSON('./grunt/configBridge.json', { encoding: 'utf8' });

  Object.keys(configBridge.paths).forEach(function (key) {
    configBridge.paths[key].forEach(function (val, i, arr) {
      arr[i] = path.join('./docs/assets', val);
    });
  });
33

34
35
36
37
38
  // Project configuration.
  grunt.initConfig({

    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
39
    banner: '/*!\n' +
XhmikosR's avatar
XhmikosR committed
40
41
            ' * Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
            ' * Copyright 2011-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
42
            ' * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n' +
XhmikosR's avatar
XhmikosR committed
43
            ' */\n',
44
45
46
47
48
    jqueryCheck: 'if (typeof jQuery === \'undefined\') {\n' +
                 '  throw new Error(\'Bootstrap\\\'s JavaScript requires jQuery\')\n' +
                 '}\n',
    jqueryVersionCheck: '+function ($) {\n' +
                        '  var version = $.fn.jquery.split(\' \')[0].split(\'.\')\n' +
Chris Rebert's avatar
Chris Rebert committed
49
50
                        '  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] >= 3)) {\n' +
                        '    throw new Error(\'Bootstrap\\\'s JavaScript requires at least jQuery v1.9.1 but less than v3.0.0\')\n' +
51
52
                        '  }\n' +
                        '}(jQuery);\n\n',
53
54
55

    // Task configuration.
    clean: {
56
57
      dist: 'dist',
      docs: 'docs/dist'
58
59
    },

60
    // JS build configuration
fat's avatar
fat committed
61
    babel: {
fat's avatar
fat committed
62
      dev: {
63
64
65
        options: {
          sourceMap: true,
          modules: 'ignore'
66
        },
fat's avatar
fat committed
67
        files: {
fat's avatar
fat committed
68
69
70
71
72
73
74
          'js/dist/util.js'      : 'js/src/util.js',
          'js/dist/alert.js'     : 'js/src/alert.js',
          'js/dist/button.js'    : 'js/src/button.js',
          'js/dist/carousel.js'  : 'js/src/carousel.js',
          'js/dist/collapse.js'  : 'js/src/collapse.js',
          'js/dist/dropdown.js'  : 'js/src/dropdown.js',
          'js/dist/modal.js'     : 'js/src/modal.js',
fat's avatar
tab es6    
fat committed
75
          'js/dist/scrollspy.js' : 'js/src/scrollspy.js',
76
          'js/dist/tab.js'       : 'js/src/tab.js',
fat's avatar
fat committed
77
78
          'js/dist/tooltip.js'   : 'js/src/tooltip.js',
          'js/dist/popover.js'   : 'js/src/popover.js'
fat's avatar
fat committed
79
        }
80
      },
81
      dist: {
XhmikosR's avatar
XhmikosR committed
82
        options: {
83
          modules: 'ignore'
XhmikosR's avatar
XhmikosR committed
84
        },
85
86
87
        files: {
          '<%= concat.bootstrap.dest %>' : '<%= concat.bootstrap.dest %>'
        }
88
89
      }
    },
90

91
    stamp: {
92
      options: {
93
94
        banner: '<%= banner %>\n<%= jqueryCheck %>\n<%= jqueryVersionCheck %>\n+function ($) {\n',
        footer: '\n}(jQuery);'
95
96
      },
      bootstrap: {
97
98
99
        files: {
          src: '<%= concat.bootstrap.dest %>'
        }
100
101
      }
    },
102

103
    concat: {
104
      options: {
XhmikosR's avatar
XhmikosR committed
105
106
107
108
        // Custom function to remove all export and import statements
        process: function (src) {
          return src.replace(/^(export|import).*/gm, '');
        },
109
        stripBanners: false
110
      },
111
      bootstrap: {
fat's avatar
fat committed
112
        src: [
113
114
115
116
117
118
119
120
121
122
123
          'js/src/util.js',
          'js/src/alert.js',
          'js/src/button.js',
          'js/src/carousel.js',
          'js/src/collapse.js',
          'js/src/dropdown.js',
          'js/src/modal.js',
          'js/src/scrollspy.js',
          'js/src/tab.js',
          'js/src/tooltip.js',
          'js/src/popover.js'
fat's avatar
fat committed
124
        ],
125
        dest: 'dist/js/<%= pkg.name %>.js'
fat's avatar
fat committed
126
127
128
129
130
      }
    },

    uglify: {
      options: {
fat's avatar
fat committed
131
132
        compress: {
          warnings: false
fat's avatar
fat committed
133
        },
fat's avatar
fat committed
134
        mangle: true,
XhmikosR's avatar
XhmikosR committed
135
        preserveComments: /^!|@preserve|@license|@cc_on/i
XhmikosR's avatar
XhmikosR committed
136
      },
fat's avatar
fat committed
137
138
      core: {
        src: '<%= concat.bootstrap.dest %>',
139
        dest: 'dist/js/<%= pkg.name %>.min.js'
fat's avatar
fat committed
140
      },
141
      docsJs: {
142
        src: configBridge.paths.docsJs,
143
        dest: 'docs/assets/js/docs.min.js'
144
145
146
      }
    },

Mark Otto's avatar
Mark Otto committed
147
148
149
150
151
152
153
    qunit: {
      options: {
        inject: 'js/tests/unit/phantom.js'
      },
      files: 'js/tests/index.html'
    },

154
    // CSS build configuration
Chris Rebert's avatar
Chris Rebert committed
155
156
    scsslint: {
      options: {
157
        bundleExec: true,
nextgenthemes's avatar
nextgenthemes committed
158
        config: 'scss/.scss-lint.yml',
Mark Otto's avatar
Mark Otto committed
159
160
        reporterOutput: null
      },
Mark Otto's avatar
Mark Otto committed
161
162
163
164
      core: {
        src: ['scss/*.scss', '!scss/_normalize.scss']
      },
      docs: {
vsn4ik's avatar
vsn4ik committed
165
        src: ['docs/assets/scss/*.scss', '!docs/assets/scss/docs.scss']
Mark Otto's avatar
Mark Otto committed
166
      }
Chris Rebert's avatar
Chris Rebert committed
167
168
    },

Chris Rebert's avatar
Chris Rebert committed
169
    postcss: {
Bas Bosman's avatar
Bas Bosman committed
170
171
      core: {
        options: {
172
173
174
          map: true,
          processors: [
            mq4HoverShim.postprocessorFor({ hoverSelectorPrefix: '.bs-true-hover ' }),
175
            require('postcss-flexbugs-fixes')(),
176
177
            autoprefixer
          ]
Bas Bosman's avatar
Bas Bosman committed
178
        },
179
        src: 'dist/css/*.css'
Bas Bosman's avatar
Bas Bosman committed
180
181
      },
      docs: {
182
183
184
185
186
        options: {
          processors: [
            autoprefixer
          ]
        },
Mark Otto's avatar
Mark Otto committed
187
        src: 'docs/assets/css/docs.min.css'
Bas Bosman's avatar
Bas Bosman committed
188
189
      },
      examples: {
190
191
192
193
194
        options: {
          processors: [
            autoprefixer
          ]
        },
Bas Bosman's avatar
Bas Bosman committed
195
196
197
198
199
200
201
        expand: true,
        cwd: 'docs/examples/',
        src: ['**/*.css'],
        dest: 'docs/examples/'
      }
    },

XhmikosR's avatar
XhmikosR committed
202
    cssmin: {
XhmikosR's avatar
XhmikosR committed
203
      options: {
204
205
        // TODO: disable `zeroUnits` optimization once clean-css 3.2 is released
        //    and then simplify the fix for https://github.com/twbs/bootstrap/issues/14837 accordingly
206
        compatibility: 'ie9',
207
        keepSpecialComments: '*',
208
        sourceMap: true,
Mark Otto's avatar
Mark Otto committed
209
        advanced: false
XhmikosR's avatar
XhmikosR committed
210
      },
Mark Otto's avatar
Mark Otto committed
211
      core: {
212
213
214
215
216
217
218
219
220
        files: [
          {
            expand: true,
            cwd: 'dist/css',
            src: ['*.css', '!*.min.css'],
            dest: 'dist/css',
            ext: '.min.css'
          }
        ]
221
      },
XhmikosR's avatar
XhmikosR committed
222
      docs: {
Mark Otto's avatar
Mark Otto committed
223
        src: 'docs/assets/css/docs.min.css',
224
        dest: 'docs/assets/css/docs.min.css'
XhmikosR's avatar
XhmikosR committed
225
226
227
      }
    },

Mark Otto's avatar
Mark Otto committed
228
    copy: {
Mark Otto's avatar
Mark Otto committed
229
      docs: {
XhmikosR's avatar
XhmikosR committed
230
231
232
233
234
235
        expand: true,
        cwd: 'dist/',
        src: [
          '**/*'
        ],
        dest: 'docs/dist/'
Mark Otto's avatar
Mark Otto committed
236
237
238
      }
    },

239
240
241
242
243
    connect: {
      server: {
        options: {
          port: 3000,
          base: '.'
244
        }
245
246
247
      }
    },

248
    jekyll: {
249
      options: {
XhmikosR's avatar
XhmikosR committed
250
        bundleExec: true,
XhmikosR's avatar
XhmikosR committed
251
252
        config: '_config.yml',
        incremental: false
253
254
255
256
257
258
259
      },
      docs: {},
      github: {
        options: {
          raw: 'github: true'
        }
      }
260
261
    },

262
    htmllint: {
263
      options: {
264
265
        ignore: [
          'Element “img” is missing required attribute “src”.',
266
          'Attribute “autocomplete” is only allowed when the input type is “color”, “date”, “datetime”, “datetime-local”, “email”, “month”, “number”, “password”, “range”, “search”, “tel”, “text”, “time”, “url”, or “week”.',
267
268
          'Attribute “autocomplete” not allowed on element “button” at this point.',
          'Element “div” not allowed as child of element “progress” in this context. (Suppressing further errors from this subtree.)',
269
          'Consider using the “h1” element as a top-level heading only (all “h1” elements are treated as top-level headings by many screen readers and other tools).',
270
          'The “datetime” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
271
272
273
274
275
          'The “color” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
          'The “date” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
          'The “datetime-local” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
          'The “month” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
          'The “time” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
XhmikosR's avatar
XhmikosR committed
276
          'The “week” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.'
277
        ]
278
      },
279
      src: ['_gh_pages/**/*.html', 'js/tests/visual/*.html']
280
281
    },

282
283
    watch: {
      src: {
284
        files: '<%= concat.bootstrap.src %>',
fat's avatar
fat committed
285
        tasks: ['babel:dev']
286
      },
287
288
      sass: {
        files: 'scss/**/*.scss',
Mark Otto's avatar
Mark Otto committed
289
        tasks: ['dist-css', 'docs']
Mark Otto's avatar
Mark Otto committed
290
291
292
293
      },
      docs: {
        files: 'docs/assets/scss/**/*.scss',
        tasks: ['dist-css', 'docs']
294
      }
295
296
    },

297
298
299
300
    'saucelabs-qunit': {
      all: {
        options: {
          build: process.env.TRAVIS_JOB_ID,
Chris Rebert's avatar
Chris Rebert committed
301
          concurrency: 10,
302
          maxRetries: 3,
303
          maxPollRetries: 4,
304
          urls: ['http://127.0.0.1:3000/js/tests/index.html?hidepassed'],
305
          browsers: grunt.file.readYAML('grunt/sauce_browsers.yml')
306
307
        }
      }
Chris Rebert's avatar
Chris Rebert committed
308
309
310
    },

    exec: {
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
    },

    buildcontrol: {
      options: {
        dir: '_gh_pages',
        commit: true,
        push: true,
        message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
      },
      pages: {
        options: {
          remote: 'git@github.com:twbs/derpstrap.git',
          branch: 'gh-pages'
        }
      }
XhmikosR's avatar
XhmikosR committed
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
    },

    compress: {
      main: {
        options: {
          archive: 'bootstrap-<%= pkg.version %>-dist.zip',
          mode: 'zip',
          level: 9,
          pretty: true
        },
        files: [
          {
            expand: true,
            cwd: 'dist/',
            src: ['**'],
            dest: 'bootstrap-<%= pkg.version %>-dist'
          }
        ]
      }
345
    }
XhmikosR's avatar
XhmikosR committed
346

347
  });
348
349


350
  // These plugins provide necessary tasks.
351
352
353
  require('load-grunt-tasks')(grunt, { scope: 'devDependencies',
    // Exclude Sass compilers. We choose the one to load later on.
    pattern: ['grunt-*', '!grunt-sass', '!grunt-contrib-sass'] });
XhmikosR's avatar
XhmikosR committed
354
  require('time-grunt')(grunt);
355

356
  // Docs HTML validation task
XhmikosR's avatar
XhmikosR committed
357
  grunt.registerTask('validate-html', ['jekyll:docs', 'htmllint']);
358

359
360
361
  var runSubset = function (subset) {
    return !process.env.TWBS_TEST || process.env.TWBS_TEST === subset;
  };
362
363
364
  var isUndefOrNonZero = function (val) {
    return val === undefined || val !== '0';
  };
365

366
  // Test task.
367
368
  var testSubtasks = [];
  // Skip core tests if running a different subset of the test suite
369
  if (runSubset('core') &&
Mark Otto's avatar
Mark Otto committed
370
    // Skip core tests if this is a Savage build
371
    process.env.TRAVIS_REPO_SLUG !== 'twbs-savage/bootstrap') {
372
    testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'test-scss', 'qunit', 'docs']);
373
374
  }
  // Skip HTML validation if running a different subset of the test suite
375
  if (runSubset('validate-html') &&
Chris Rebert's avatar
Chris Rebert committed
376
377
      isTravis &&
      // Skip HTML5 validator when [skip validator] is in the commit message
378
      isUndefOrNonZero(process.env.TWBS_DO_VALIDATOR)) {
379
380
    testSubtasks.push('validate-html');
  }
381
  // Only run Sauce Labs tests if there's a Sauce access key
Chris Rebert's avatar
Chris Rebert committed
382
  if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined' &&
383
      // Skip Sauce if running a different subset of the test suite
384
385
386
      runSubset('sauce-js-unit') &&
      // Skip Sauce on Travis when [skip sauce] is in the commit message
      isUndefOrNonZero(process.env.TWBS_DO_SAUCE)) {
387
    testSubtasks.push('babel:dev');
388
389
    testSubtasks.push('connect');
    testSubtasks.push('saucelabs-qunit');
390
391
  }
  grunt.registerTask('test', testSubtasks);
392

393
  // JS distribution task.
394
  grunt.registerTask('dist-js', ['babel:dev', 'concat', 'babel:dist', 'stamp', 'uglify:core']);
395

Mark Otto's avatar
Mark Otto committed
396
  grunt.registerTask('test-scss', ['scsslint:core']);
Chris Rebert's avatar
Chris Rebert committed
397

398
  // CSS distribution task.
399
400
401
402
  // Supported Compilers: sass (Ruby) and libsass.
  (function (sassCompilerName) {
    require('./grunt/bs-sass-compile/' + sassCompilerName + '.js')(grunt);
  })(process.env.TWBS_SASS || 'libsass');
403
404
  // grunt.registerTask('sass-compile', ['sass:core', 'sass:extras', 'sass:docs']);
  grunt.registerTask('sass-compile', ['sass:core', 'sass:docs']);
405

406
  grunt.registerTask('dist-css', ['sass-compile', 'postcss:core', 'cssmin:core', 'cssmin:docs']);
Mark Otto's avatar
Mark Otto committed
407

408
  // Full distribution task.
Mark Otto's avatar
Mark Otto committed
409
  grunt.registerTask('dist', ['clean:dist', 'dist-css', 'dist-js']);
410

411
  // Default task.
Mark Otto's avatar
Mark Otto committed
412
  grunt.registerTask('default', ['clean:dist', 'test']);
413

414
  // Docs task.
415
  grunt.registerTask('docs-css', ['postcss:docs', 'postcss:examples', 'cssmin:docs']);
416
  grunt.registerTask('lint-docs-css', ['scsslint:docs']);
Mark Otto's avatar
Mark Otto committed
417
  grunt.registerTask('docs-js', ['uglify:docsJs']);
418
  grunt.registerTask('docs', ['lint-docs-css', 'docs-css', 'docs-js', 'clean:docs', 'copy:docs']);
419
  grunt.registerTask('docs-github', ['jekyll:github']);
420

421
  grunt.registerTask('prep-release', ['dist', 'docs', 'docs-github', 'compress']);
422

Mark Otto's avatar
Mark Otto committed
423
424
  // Publish to GitHub
  grunt.registerTask('publish', ['buildcontrol:pages']);
425
};