Gruntfile.js 10.2 KB
Newer Older
1
/* jshint node: true */
2

Chris Rebert's avatar
Chris Rebert committed
3
module.exports = function (grunt) {
XhmikosR's avatar
XhmikosR committed
4
  'use strict';
5

6
7
8
  // Force use of Unix newlines
  grunt.util.linefeed = '\n';

9
  RegExp.quote = require('regexp-quote')
10
  var btoa = require('btoa')
11
12
13
14
15
  // Project configuration.
  grunt.initConfig({

    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
16
    banner: '/*!\n' +
Zlatan Vasović's avatar
Zlatan Vasović committed
17
              ' * Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
18
19
              ' * Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
              ' * Licensed under <%= _.pluck(pkg.licenses, "url").join(", ") %>\n' +
20
              ' */\n\n',
21
    jqueryCheck: 'if (typeof jQuery === "undefined") { throw new Error("Bootstrap requires jQuery") }\n\n',
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

    // Task configuration.
    clean: {
      dist: ['dist']
    },

    jshint: {
      options: {
        jshintrc: 'js/.jshintrc'
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      src: {
        src: ['js/*.js']
      },
      test: {
        src: ['js/tests/unit/*.js']
40
41
42
      },
      assets: {
        src: ['docs-assets/js/application.js', 'docs-assets/js/customizer.js']
43
44
      }
    },
45

Chris Rebert's avatar
Chris Rebert committed
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
    jscs: {
      options: {
        config: 'js/.jscs.json',
      },
      gruntfile: {
        src: ['Gruntfile.js']
      },
      src: {
        src: ['js/*.js']
      },
      test: {
        src: ['js/tests/unit/*.js']
      }
    },

XhmikosR's avatar
XhmikosR committed
61
62
63
64
65
66
67
    csslint: {
      options: {
        csslintrc: '.csslintrc'
      },
      src: ['dist/css/bootstrap.css', 'dist/css/bootstrap-theme.css']
    },

68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
    concat: {
      options: {
        banner: '<%= banner %><%= jqueryCheck %>',
        stripBanners: false
      },
      bootstrap: {
        src: [
          'js/transition.js',
          'js/alert.js',
          'js/button.js',
          'js/carousel.js',
          'js/collapse.js',
          'js/dropdown.js',
          'js/modal.js',
          'js/tooltip.js',
          'js/popover.js',
          'js/scrollspy.js',
          'js/tab.js',
          'js/affix.js'
        ],
        dest: 'dist/js/<%= pkg.name %>.js'
      }
    },
91

92
93
    uglify: {
      options: {
94
95
        banner: '<%= banner %>',
        report: 'min'
96
97
98
99
      },
      bootstrap: {
        src: ['<%= concat.bootstrap.dest %>'],
        dest: 'dist/js/<%= pkg.name %>.min.js'
XhmikosR's avatar
XhmikosR committed
100
101
102
103
104
105
106
107
108
109
      },
      customize: {
        src: [
          'docs-assets/js/less.js',
          'docs-assets/js/jszip.js',
          'docs-assets/js/uglify.js',
          'docs-assets/js/filesaver.js',
          'docs-assets/js/customizer.js'
        ],
        dest: 'docs-assets/js/customize.js'
110
111
112
      }
    },

113
114
115
116
117
118
    less: {
      compile: {
        files: {
          'dist/css/<%= pkg.name %>.css': 'less/bootstrap.less',
          'dist/css/<%= pkg.name %>-theme.css': 'less/theme.less'
        }
119
      },
120
121
122
123
    },

    usebanner: {
      dist: {
124
        options: {
125
126
          position: 'top',
          banner: '<%= banner %>'
127
        },
128
        files: {
129
130
131
132
          src: [
            'dist/css/<%= pkg.name %>.css',
            'dist/css/<%= pkg.name %>-theme.css',
          ]
133
134
135
136
        }
      }
    },

137
138
    cssmin: {
      compress: {
Chris Rebert's avatar
Chris Rebert committed
139
        options: {
140
141
142
          keepSpecialComments: 1,
          report: 'min',
          selectorsMergeMode: 'ie8'
Chris Rebert's avatar
Chris Rebert committed
143
144
        },
        files: {
145
146
          'dist/css/<%= pkg.name %>.min.css': 'dist/css/<%= pkg.name %>.css',
          'dist/css/<%= pkg.name %>-theme.min.css': 'dist/css/<%= pkg.name %>-theme.css'
Chris Rebert's avatar
Chris Rebert committed
147
        }
148
149
150
      }
    },

151
152
    csscomb: {
      sort: {
153
        options: {
154
          sortOrder: '.csscomb.json'
155
        },
156
        files: {
157
158
          'dist/css/<%= pkg.name %>.css': ['dist/css/<%= pkg.name %>.css'],
          'dist/css/<%= pkg.name %>-theme.css': ['dist/css/<%= pkg.name %>-theme.css'],
159
        }
160
161
162
      }
    },

Mark Otto's avatar
Mark Otto committed
163
164
165
    copy: {
      fonts: {
        expand: true,
XhmikosR's avatar
XhmikosR committed
166
        src: ['fonts/*'],
Mark Otto's avatar
Mark Otto committed
167
168
169
170
        dest: 'dist/'
      }
    },

171
172
173
174
175
176
    qunit: {
      options: {
        inject: 'js/tests/unit/phantom.js'
      },
      files: ['js/tests/*.html']
    },
177

178
179
180
181
182
    connect: {
      server: {
        options: {
          port: 3000,
          base: '.'
183
        }
184
185
186
      }
    },

187
188
189
190
191
192
    jekyll: {
      docs: {}
    },

    validation: {
      options: {
193
194
        reset: true,
        relaxerror: [
XhmikosR's avatar
XhmikosR committed
195
196
          'Bad value X-UA-Compatible for attribute http-equiv on element meta.',
          'Element img is missing required attribute src.'
197
        ]
198
199
      },
      files: {
XhmikosR's avatar
XhmikosR committed
200
        src: ['_gh_pages/**/*.html']
201
202
203
      }
    },

204
205
206
207
208
209
210
211
212
    watch: {
      src: {
        files: '<%= jshint.src.src %>',
        tasks: ['jshint:src', 'qunit']
      },
      test: {
        files: '<%= jshint.test.src %>',
        tasks: ['jshint:test', 'qunit']
      },
213
      less: {
214
        files: 'less/*.less',
215
        tasks: ['less']
216
      }
217
218
219
220
221
222
223
224
225
226
227
    },

    sed: {
      versionNumber: {
        pattern: (function () {
          var old = grunt.option('oldver')
          return old ? RegExp.quote(old) : old
        })(),
        replacement: grunt.option('newver'),
        recursive: true
      }
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
    },

    'saucelabs-qunit': {
      all: {
        options: {
          build: process.env.TRAVIS_JOB_ID,
          concurrency: 3,
          urls: ['http://127.0.0.1:3000/js/tests/index.html'],
          browsers: [
            // See https://saucelabs.com/docs/platforms/webdriver
            {
              browserName: 'safari',
              version: '6',
              platform: 'OS X 10.8'
            },
            {
              browserName: 'chrome',
              version: '28',
              platform: 'OS X 10.6'
            },
            /* FIXME: currently fails 1 tooltip test
            {
              browserName: 'firefox',
              version: '25',
              platform: 'OS X 10.6'
            },*/
            // Mac Opera not currently supported by Sauce Labs
            /* FIXME: currently fails 1 tooltip test
            {
              browserName: 'internet explorer',
              version: '11',
              platform: 'Windows 8.1'
            },*/
            /*
            {
              browserName: 'internet explorer',
              version: '10',
              platform: 'Windows 8'
            },
            {
              browserName: 'internet explorer',
              version: '9',
              platform: 'Windows 7'
            },
            {
              browserName: 'internet explorer',
              version: '8',
              platform: 'Windows 7'
            },
            {// unofficial
              browserName: 'internet explorer',
              version: '7',
              platform: 'Windows XP'
            },
            */
            {
              browserName: 'chrome',
              version: '31',
              platform: 'Windows 8.1'
            },
            {
              browserName: 'firefox',
              version: '25',
              platform: 'Windows 8.1'
            },
            // Win Opera 15+ not currently supported by Sauce Labs
            {
              browserName: 'iphone',
              version: '6.1',
              platform: 'OS X 10.8'
            },
            // iOS Chrome not currently supported by Sauce Labs
            // Linux (unofficial)
            {
              browserName: 'chrome',
              version: '30',
              platform: 'Linux'
            },
            {
              browserName: 'firefox',
              version: '25',
              platform: 'Linux'
            }
            // Android Chrome not currently supported by Sauce Labs
            /* Android Browser (super-unofficial)
            {
              browserName: 'android',
              version: '4.0',
              platform: 'Linux'
            }
            */
          ],
        }
      }
322
323
    }
  });
324
325


326
  // These plugins provide necessary tasks.
327
  grunt.loadNpmTasks('grunt-banner');
328
329
330
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-connect');
Mark Otto's avatar
Mark Otto committed
331
  grunt.loadNpmTasks('grunt-contrib-copy');
XhmikosR's avatar
XhmikosR committed
332
  grunt.loadNpmTasks('grunt-contrib-csslint');
XhmikosR's avatar
XhmikosR committed
333
  grunt.loadNpmTasks('grunt-contrib-cssmin');
334
  grunt.loadNpmTasks('grunt-contrib-jshint');
335
  grunt.loadNpmTasks('grunt-contrib-less');
336
337
338
  grunt.loadNpmTasks('grunt-contrib-qunit');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');
339
  grunt.loadNpmTasks('grunt-csscomb');
340
341
  grunt.loadNpmTasks('grunt-html-validation');
  grunt.loadNpmTasks('grunt-jekyll');
Chris Rebert's avatar
Chris Rebert committed
342
  grunt.loadNpmTasks('grunt-jscs-checker');
343
  grunt.loadNpmTasks('grunt-saucelabs');
344
  grunt.loadNpmTasks('grunt-sed');
345

346
  // Docs HTML validation task
347
  grunt.registerTask('validate-html', ['jekyll', 'validation']);
348

349
  // Test task.
Chris Rebert's avatar
Chris Rebert committed
350
  var testSubtasks = ['dist-css', 'jshint', 'jscs', 'qunit', 'validate-html'];
351
352
353
354
  // Only run Sauce Labs tests if there's a Sauce access key
  if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined') {
    testSubtasks.push('connect');
    testSubtasks.push('saucelabs-qunit');
355
356
  }
  grunt.registerTask('test', testSubtasks);
357

358
359
  // JS distribution task.
  grunt.registerTask('dist-js', ['concat', 'uglify']);
360

361
  // CSS distribution task.
362
  grunt.registerTask('dist-css', ['less', 'cssmin', 'csscomb', 'usebanner']);
363

Mark Otto's avatar
Mark Otto committed
364
365
366
  // Fonts distribution task.
  grunt.registerTask('dist-fonts', ['copy']);

367
  // Full distribution task.
Mark Otto's avatar
Mark Otto committed
368
  grunt.registerTask('dist', ['clean', 'dist-css', 'dist-fonts', 'dist-js']);
369

370
  // Default task.
371
  grunt.registerTask('default', ['test', 'dist', 'build-customizer']);
372

373
374
375
376
377
  // Version numbering task.
  // grunt change-version-number --oldver=A.B.C --newver=X.Y.Z
  // This can be overzealous, so its changes should always be manually reviewed!
  grunt.registerTask('change-version-number', ['sed']);

378
379
380
381
382
383
384
385
  // task for building customizer
  grunt.registerTask('build-customizer', 'Add scripts/less files to customizer.', function () {
    var fs = require('fs')

    function getFiles(type) {
      var files = {}
      fs.readdirSync(type)
        .filter(function (path) {
fat's avatar
fat committed
386
          return type == 'fonts' ? true : new RegExp('\\.' + type + '$').test(path)
387
388
        })
        .forEach(function (path) {
389
390
          var fullPath = type + '/' + path
          return files[path] = (type == 'fonts' ? btoa(fs.readFileSync(fullPath)) : fs.readFileSync(fullPath, 'utf8'))
391
392
393
394
        })
      return 'var __' + type + ' = ' + JSON.stringify(files) + '\n'
    }

fat's avatar
fat committed
395
    var files = getFiles('js') + getFiles('less') + getFiles('fonts')
396
    fs.writeFileSync('docs-assets/js/raw-files.js', files)
397
  });
398
};