Gruntfile.js 12.4 KB
Newer Older
Chris Rebert's avatar
Chris Rebert committed
1
2
/*!
 * Bootstrap's Gruntfile
3
 * https://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
        options: {
61
          sourceMap: true
62
        },
fat's avatar
fat committed
63
        files: {
fat's avatar
fat committed
64
65
66
67
68
69
70
          '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
71
          'js/dist/scrollspy.js' : 'js/src/scrollspy.js',
72
          'js/dist/tab.js'       : 'js/src/tab.js',
fat's avatar
fat committed
73
74
          'js/dist/tooltip.js'   : 'js/src/tooltip.js',
          'js/dist/popover.js'   : 'js/src/popover.js'
fat's avatar
fat committed
75
        }
76
      },
77
      dist: {
XhmikosR's avatar
XhmikosR committed
78
        options: {
79
          extends: '../../js/.babelrc'
XhmikosR's avatar
XhmikosR committed
80
        },
81
82
83
        files: {
          '<%= concat.bootstrap.dest %>' : '<%= concat.bootstrap.dest %>'
        }
84
85
      }
    },
86

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

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

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

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

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

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

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

206
207
208
209
210
    connect: {
      server: {
        options: {
          port: 3000,
          base: '.'
211
        }
212
213
214
      }
    },

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

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

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

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

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

    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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
    },

    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'
          }
        ]
      }
324
    }
XhmikosR's avatar
XhmikosR committed
325

326
  });
327
328


329
  // These plugins provide necessary tasks.
330
331
332
  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
333
  require('time-grunt')(grunt);
334

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

338
339
340
  var runSubset = function (subset) {
    return !process.env.TWBS_TEST || process.env.TWBS_TEST === subset;
  };
341
342
343
  var isUndefOrNonZero = function (val) {
    return val === undefined || val !== '0';
  };
344

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

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

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

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

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

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

391
  // Default task.
Mark Otto's avatar
Mark Otto committed
392
  grunt.registerTask('default', ['clean:dist', 'test']);
393

394
  // Docs task.
395
  grunt.registerTask('docs-css', ['cssmin:docs', 'exec:postcss-docs']);
396
  grunt.registerTask('lint-docs-css', ['scsslint:docs']);
Mark Otto's avatar
Mark Otto committed
397
  grunt.registerTask('docs-js', ['uglify:docsJs']);
398
  grunt.registerTask('docs', ['lint-docs-css', 'docs-css', 'docs-js', 'clean:docs', 'copy:docs']);
399
  grunt.registerTask('docs-github', ['jekyll:github']);
400

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

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