Gruntfile.js 12.6 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');
Mark Otto's avatar
Mark Otto committed
22

23
24
25
26
  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) {
27
      arr[i] = path.join('./docs', val);
28
29
    });
  });
30

31
32
33
34
35
  // Project configuration.
  grunt.initConfig({

    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
36
    banner: '/*!\n' +
XhmikosR's avatar
XhmikosR committed
37
38
            ' * Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
            ' * Copyright 2011-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
39
            ' * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n' +
XhmikosR's avatar
XhmikosR committed
40
            ' */\n',
41
42
43
44
45
    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' +
46
47
                        '  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] >= 4)) {\n' +
                        '    throw new Error(\'Bootstrap\\\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0\')\n' +
48
49
                        '  }\n' +
                        '}(jQuery);\n\n',
50
51
52

    // Task configuration.
    clean: {
53
54
      dist: 'dist',
      docs: 'docs/dist'
55
56
    },

57
    // JS build configuration
fat's avatar
fat committed
58
    babel: {
fat's avatar
fat committed
59
      dev: {
60
61
62
        options: {
          sourceMap: true,
          modules: 'ignore'
63
        },
fat's avatar
fat committed
64
        files: {
fat's avatar
fat committed
65
66
67
68
69
70
71
          '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
72
          'js/dist/scrollspy.js' : 'js/src/scrollspy.js',
73
          'js/dist/tab.js'       : 'js/src/tab.js',
fat's avatar
fat committed
74
75
          'js/dist/tooltip.js'   : 'js/src/tooltip.js',
          'js/dist/popover.js'   : 'js/src/popover.js'
fat's avatar
fat committed
76
        }
77
      },
78
      dist: {
XhmikosR's avatar
XhmikosR committed
79
        options: {
80
          modules: 'ignore'
XhmikosR's avatar
XhmikosR committed
81
        },
82
83
84
        files: {
          '<%= concat.bootstrap.dest %>' : '<%= concat.bootstrap.dest %>'
        }
85
86
      }
    },
87

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

100
    concat: {
101
      options: {
XhmikosR's avatar
XhmikosR committed
102
103
104
105
        // Custom function to remove all export and import statements
        process: function (src) {
          return src.replace(/^(export|import).*/gm, '');
        },
106
        stripBanners: false
107
      },
108
      bootstrap: {
fat's avatar
fat committed
109
        src: [
110
111
112
113
114
115
116
117
118
119
120
          '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
121
        ],
122
        dest: 'dist/js/<%= pkg.name %>.js'
fat's avatar
fat committed
123
124
125
126
127
      }
    },

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

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

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

XhmikosR's avatar
XhmikosR committed
166
    cssmin: {
XhmikosR's avatar
XhmikosR committed
167
      options: {
168
169
        // 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
170
        compatibility: 'ie9',
171
        keepSpecialComments: '*',
172
        sourceMap: true,
173
        sourceMapInlineSources: true,
Mark Otto's avatar
Mark Otto committed
174
        advanced: false
XhmikosR's avatar
XhmikosR committed
175
      },
Mark Otto's avatar
Mark Otto committed
176
      core: {
177
178
179
180
181
182
183
184
185
        files: [
          {
            expand: true,
            cwd: 'dist/css',
            src: ['*.css', '!*.min.css'],
            dest: 'dist/css',
            ext: '.min.css'
          }
        ]
186
      },
XhmikosR's avatar
XhmikosR committed
187
      docs: {
188
189
190
191
192
193
194
195
196
        files: [
          {
            expand: true,
            cwd: 'docs/assets/css',
            src: ['*.css', '!*.min.css'],
            dest: 'docs/assets/css',
            ext: '.min.css'
          }
        ]
XhmikosR's avatar
XhmikosR committed
197
198
199
      }
    },

Mark Otto's avatar
Mark Otto committed
200
    copy: {
Mark Otto's avatar
Mark Otto committed
201
      docs: {
XhmikosR's avatar
XhmikosR committed
202
203
204
205
206
207
        expand: true,
        cwd: 'dist/',
        src: [
          '**/*'
        ],
        dest: 'docs/dist/'
Mark Otto's avatar
Mark Otto committed
208
209
210
      }
    },

211
212
213
214
215
    connect: {
      server: {
        options: {
          port: 3000,
          base: '.'
216
        }
217
218
219
      }
    },

220
    jekyll: {
221
      options: {
XhmikosR's avatar
XhmikosR committed
222
        bundleExec: true,
XhmikosR's avatar
XhmikosR committed
223
224
        config: '_config.yml',
        incremental: false
225
226
227
228
229
230
231
      },
      docs: {},
      github: {
        options: {
          raw: 'github: true'
        }
      }
232
233
    },

234
    htmllint: {
235
      options: {
236
        ignore: [
XhmikosR's avatar
XhmikosR committed
237
          'Attribute “autocomplete” is only allowed when the input type is “color”, “date”, “datetime”, “datetime-local”, “email”, “hidden”, “month”, “number”, “password”, “range”, “search”, “tel”, “text”, “time”, “url”, or “week”.',
238
          'Attribute “autocomplete” not allowed on element “button” at this point.',
239
          '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).',
XhmikosR's avatar
XhmikosR committed
240
241
          'Element “div” not allowed as child of element “progress” in this context. (Suppressing further errors from this subtree.)',
          'Element “img” is missing required attribute “src”.',
242
243
          '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.',
XhmikosR's avatar
XhmikosR committed
244
          'The “datetime” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
245
246
247
          '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
248
          'The “week” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.'
249
        ]
250
      },
251
      src: ['_gh_pages/**/*.html', 'js/tests/visual/*.html']
252
253
    },

254
255
    watch: {
      src: {
256
        files: '<%= concat.bootstrap.src %>',
fat's avatar
fat committed
257
        tasks: ['babel:dev']
258
      },
259
260
      sass: {
        files: 'scss/**/*.scss',
Mark Otto's avatar
Mark Otto committed
261
        tasks: ['dist-css', 'docs']
Mark Otto's avatar
Mark Otto committed
262
263
264
265
      },
      docs: {
        files: 'docs/assets/scss/**/*.scss',
        tasks: ['dist-css', 'docs']
266
      }
267
268
    },

269
270
271
272
    'saucelabs-qunit': {
      all: {
        options: {
          build: process.env.TRAVIS_JOB_ID,
Chris Rebert's avatar
Chris Rebert committed
273
          concurrency: 10,
274
          maxRetries: 3,
275
          maxPollRetries: 4,
276
          urls: ['http://127.0.0.1:3000/js/tests/index.html?hidepassed'],
277
          browsers: grunt.file.readYAML('grunt/sauce_browsers.yml')
278
279
        }
      }
Chris Rebert's avatar
Chris Rebert committed
280
281
282
    },

    exec: {
283
284
285
286
287
      postcss: {
        command: 'npm run postcss'
      },
      'postcss-docs': {
        command: 'npm run postcss-docs'
288
      },
289
290
291
      htmlhint: {
        command: 'npm run htmlhint'
      },
292
293
      'upload-preview': {
        command: './grunt/upload-preview.sh'
294
      }
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
    },

    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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
    },

    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'
          }
        ]
      }
329
    }
XhmikosR's avatar
XhmikosR committed
330

331
  });
332
333


334
  // These plugins provide necessary tasks.
335
336
337
  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
338
  require('time-grunt')(grunt);
339

340
  // Docs HTML validation task
341
  grunt.registerTask('validate-html', ['jekyll:docs', 'htmllint', 'exec:htmlhint']);
342

343
344
345
  var runSubset = function (subset) {
    return !process.env.TWBS_TEST || process.env.TWBS_TEST === subset;
  };
346
347
348
  var isUndefOrNonZero = function (val) {
    return val === undefined || val !== '0';
  };
349

350
  // Test task.
351
352
  var testSubtasks = [];
  // Skip core tests if running a different subset of the test suite
353
  if (runSubset('core') &&
Mark Otto's avatar
Mark Otto committed
354
    // Skip core tests if this is a Savage build
355
    process.env.TRAVIS_REPO_SLUG !== 'twbs-savage/bootstrap') {
356
    testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'test-scss', 'qunit', 'docs']);
357
358
  }
  // Skip HTML validation if running a different subset of the test suite
359
  if (runSubset('validate-html') &&
Chris Rebert's avatar
Chris Rebert committed
360
361
      isTravis &&
      // Skip HTML5 validator when [skip validator] is in the commit message
362
      isUndefOrNonZero(process.env.TWBS_DO_VALIDATOR)) {
363
364
    testSubtasks.push('validate-html');
  }
365
  // Only run Sauce Labs tests if there's a Sauce access key
Chris Rebert's avatar
Chris Rebert committed
366
  if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined' &&
367
      // Skip Sauce if running a different subset of the test suite
368
369
370
371
372
373
374
      runSubset('sauce-js-unit')) {
    testSubtasks = testSubtasks.concat(['dist', 'docs-css', 'docs-js', 'clean:docs', 'copy:docs', 'exec:upload-preview']);
    // Skip Sauce on Travis when [skip sauce] is in the commit message
    if (isUndefOrNonZero(process.env.TWBS_DO_SAUCE)) {
      testSubtasks.push('connect');
      testSubtasks.push('saucelabs-qunit');
    }
375
376
  }
  grunt.registerTask('test', testSubtasks);
377

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

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

383
  // CSS distribution task.
384
385
386
387
  // Supported Compilers: sass (Ruby) and libsass.
  (function (sassCompilerName) {
    require('./grunt/bs-sass-compile/' + sassCompilerName + '.js')(grunt);
  })(process.env.TWBS_SASS || 'libsass');
388
  // grunt.registerTask('sass-compile', ['sass:core', 'sass:extras', 'sass:docs']);
389
  grunt.registerTask('sass-compile', ['sass:core', 'sass:extras', 'sass:docs']);
390

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

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

396
  // Default task.
Mark Otto's avatar
Mark Otto committed
397
  grunt.registerTask('default', ['clean:dist', 'test']);
398

399
  // Docs task.
400
  grunt.registerTask('docs-css', ['cssmin:docs', 'exec:postcss-docs']);
401
  grunt.registerTask('lint-docs-css', ['scsslint:docs']);
Mark Otto's avatar
Mark Otto committed
402
  grunt.registerTask('docs-js', ['uglify:docsJs']);
403
  grunt.registerTask('docs', ['lint-docs-css', 'docs-css', 'docs-js', 'clean:docs', 'copy:docs']);
404
  grunt.registerTask('docs-github', ['jekyll:github']);
405

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

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