Gruntfile.js 12.4 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,
Mark Otto's avatar
Mark Otto committed
173
        advanced: false
XhmikosR's avatar
XhmikosR committed
174
      },
Mark Otto's avatar
Mark Otto committed
175
      core: {
176
177
178
179
180
181
182
183
184
        files: [
          {
            expand: true,
            cwd: 'dist/css',
            src: ['*.css', '!*.min.css'],
            dest: 'dist/css',
            ext: '.min.css'
          }
        ]
185
      },
XhmikosR's avatar
XhmikosR committed
186
      docs: {
Mark Otto's avatar
Mark Otto committed
187
        src: 'docs/assets/css/docs.min.css',
188
        dest: 'docs/assets/css/docs.min.css'
XhmikosR's avatar
XhmikosR committed
189
190
191
      }
    },

Mark Otto's avatar
Mark Otto committed
192
    copy: {
Mark Otto's avatar
Mark Otto committed
193
      docs: {
XhmikosR's avatar
XhmikosR committed
194
195
196
197
198
199
        expand: true,
        cwd: 'dist/',
        src: [
          '**/*'
        ],
        dest: 'docs/dist/'
Mark Otto's avatar
Mark Otto committed
200
201
202
      }
    },

203
204
205
206
207
    connect: {
      server: {
        options: {
          port: 3000,
          base: '.'
208
        }
209
210
211
      }
    },

212
    jekyll: {
213
      options: {
XhmikosR's avatar
XhmikosR committed
214
        bundleExec: true,
XhmikosR's avatar
XhmikosR committed
215
216
        config: '_config.yml',
        incremental: false
217
218
219
220
221
222
223
      },
      docs: {},
      github: {
        options: {
          raw: 'github: true'
        }
      }
224
225
    },

226
    htmllint: {
227
      options: {
228
        ignore: [
XhmikosR's avatar
XhmikosR committed
229
          '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”.',
230
          'Attribute “autocomplete” not allowed on element “button” at this point.',
231
          '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
232
233
          '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”.',
234
235
          '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
236
          'The “datetime” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
237
238
239
          '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
240
          'The “week” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.'
241
        ]
242
      },
243
      src: ['_gh_pages/**/*.html', 'js/tests/visual/*.html']
244
245
    },

246
247
    watch: {
      src: {
248
        files: '<%= concat.bootstrap.src %>',
fat's avatar
fat committed
249
        tasks: ['babel:dev']
250
      },
251
252
      sass: {
        files: 'scss/**/*.scss',
Mark Otto's avatar
Mark Otto committed
253
        tasks: ['dist-css', 'docs']
Mark Otto's avatar
Mark Otto committed
254
255
256
257
      },
      docs: {
        files: 'docs/assets/scss/**/*.scss',
        tasks: ['dist-css', 'docs']
258
      }
259
260
    },

261
262
263
264
    'saucelabs-qunit': {
      all: {
        options: {
          build: process.env.TRAVIS_JOB_ID,
Chris Rebert's avatar
Chris Rebert committed
265
          concurrency: 10,
266
          maxRetries: 3,
267
          maxPollRetries: 4,
268
          urls: ['http://127.0.0.1:3000/js/tests/index.html?hidepassed'],
269
          browsers: grunt.file.readYAML('grunt/sauce_browsers.yml')
270
271
        }
      }
Chris Rebert's avatar
Chris Rebert committed
272
273
274
    },

    exec: {
275
276
277
278
279
      postcss: {
        command: 'npm run postcss'
      },
      'postcss-docs': {
        command: 'npm run postcss-docs'
280
281
282
      },
      'upload-preview': {
        command: './grunt/upload-preview.sh'
283
      }
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
    },

    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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
    },

    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'
          }
        ]
      }
318
    }
XhmikosR's avatar
XhmikosR committed
319

320
  });
321
322


323
  // These plugins provide necessary tasks.
324
325
326
  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
327
  require('time-grunt')(grunt);
328

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

332
333
334
  var runSubset = function (subset) {
    return !process.env.TWBS_TEST || process.env.TWBS_TEST === subset;
  };
335
336
337
  var isUndefOrNonZero = function (val) {
    return val === undefined || val !== '0';
  };
338

339
  // Test task.
340
341
  var testSubtasks = [];
  // Skip core tests if running a different subset of the test suite
342
  if (runSubset('core') &&
Mark Otto's avatar
Mark Otto committed
343
    // Skip core tests if this is a Savage build
344
    process.env.TRAVIS_REPO_SLUG !== 'twbs-savage/bootstrap') {
345
    testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'test-scss', 'qunit', 'docs']);
346
347
  }
  // Skip HTML validation if running a different subset of the test suite
348
  if (runSubset('validate-html') &&
Chris Rebert's avatar
Chris Rebert committed
349
350
      isTravis &&
      // Skip HTML5 validator when [skip validator] is in the commit message
351
      isUndefOrNonZero(process.env.TWBS_DO_VALIDATOR)) {
352
353
    testSubtasks.push('validate-html');
  }
354
  // Only run Sauce Labs tests if there's a Sauce access key
Chris Rebert's avatar
Chris Rebert committed
355
  if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined' &&
356
      // Skip Sauce if running a different subset of the test suite
357
358
359
360
361
362
363
      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');
    }
364
365
  }
  grunt.registerTask('test', testSubtasks);
366

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

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

372
  // CSS distribution task.
373
374
375
376
  // Supported Compilers: sass (Ruby) and libsass.
  (function (sassCompilerName) {
    require('./grunt/bs-sass-compile/' + sassCompilerName + '.js')(grunt);
  })(process.env.TWBS_SASS || 'libsass');
377
378
  // grunt.registerTask('sass-compile', ['sass:core', 'sass:extras', 'sass:docs']);
  grunt.registerTask('sass-compile', ['sass:core', 'sass:docs']);
379

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

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

385
  // Default task.
Mark Otto's avatar
Mark Otto committed
386
  grunt.registerTask('default', ['clean:dist', 'test']);
387

388
  // Docs task.
389
  grunt.registerTask('docs-css', ['cssmin:docs', 'exec:postcss-docs']);
390
  grunt.registerTask('lint-docs-css', ['scsslint:docs']);
Mark Otto's avatar
Mark Otto committed
391
  grunt.registerTask('docs-js', ['uglify:docsJs']);
392
  grunt.registerTask('docs', ['lint-docs-css', 'docs-css', 'docs-js', 'clean:docs', 'copy:docs']);
393
  grunt.registerTask('docs-github', ['jekyll:github']);
394

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

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