Gruntfile.js 9.61 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
    less: {
      compile: {
115
116
117
        options: {
          strictMath: true
        },
118
119
120
121
        files: {
          'dist/css/<%= pkg.name %>.css': 'less/bootstrap.less',
          'dist/css/<%= pkg.name %>-theme.css': 'less/theme.less'
        }
122
      },
123
124
125
126
127
128
129
130
131
132
      minify: {
        options: {
          cleancss: true,
          report: 'min'
        },
        files: {
          'dist/css/<%= pkg.name %>.min.css': 'dist/css/<%= pkg.name %>.css',
          'dist/css/<%= pkg.name %>-theme.min.css': 'dist/css/<%= pkg.name %>-theme.css'
        }
      }
133
134
135
136
    },

    usebanner: {
      dist: {
137
        options: {
138
139
          position: 'top',
          banner: '<%= banner %>'
140
        },
141
        files: {
142
143
          src: [
            'dist/css/<%= pkg.name %>.css',
144
            'dist/css/<%= pkg.name %>.min.css',
145
            'dist/css/<%= pkg.name %>-theme.css',
146
            'dist/css/<%= pkg.name %>-theme.min.css',
147
          ]
148
149
150
151
        }
      }
    },

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

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

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

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

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

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

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

    sed: {
      versionNumber: {
        pattern: (function () {
          var old = grunt.option('oldver')
          return old ? RegExp.quote(old) : old
        })(),
        replacement: grunt.option('newver'),
        recursive: true
      }
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
322
    },

    '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'
            }
            */
          ],
        }
      }
323
324
    }
  });
325
326


327
  // These plugins provide necessary tasks.
328
    require('load-grunt-tasks')(grunt, {scope: 'devDependencies'});
329

330
  // Docs HTML validation task
331
  grunt.registerTask('validate-html', ['jekyll', 'validation']);
332

333
  // Test task.
Chris Rebert's avatar
Chris Rebert committed
334
  var testSubtasks = ['dist-css', 'jshint', 'jscs', 'qunit', 'validate-html'];
335
336
337
338
  // 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');
339
340
  }
  grunt.registerTask('test', testSubtasks);
341

342
343
  // JS distribution task.
  grunt.registerTask('dist-js', ['concat', 'uglify']);
344

345
  // CSS distribution task.
346
  grunt.registerTask('dist-css', ['less', 'csscomb', 'usebanner']);
347

Mark Otto's avatar
Mark Otto committed
348
349
350
  // Fonts distribution task.
  grunt.registerTask('dist-fonts', ['copy']);

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

354
  // Default task.
355
  grunt.registerTask('default', ['test', 'dist', 'build-customizer']);
356

357
358
359
360
361
  // 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']);

362
363
364
365
366
367
368
369
  // 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
370
          return type == 'fonts' ? true : new RegExp('\\.' + type + '$').test(path)
371
372
        })
        .forEach(function (path) {
373
374
          var fullPath = type + '/' + path
          return files[path] = (type == 'fonts' ? btoa(fs.readFileSync(fullPath)) : fs.readFileSync(fullPath, 'utf8'))
375
376
377
378
        })
      return 'var __' + type + ' = ' + JSON.stringify(files) + '\n'
    }

fat's avatar
fat committed
379
    var files = getFiles('js') + getFiles('less') + getFiles('fonts')
380
    fs.writeFileSync('docs-assets/js/raw-files.js', files)
381
  });
382
};