Gruntfile.js 9.39 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']
      }
    },

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
    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'
      }
    },
84

85
86
    uglify: {
      options: {
87
88
        banner: '<%= banner %>',
        report: 'min'
89
90
91
92
      },
      bootstrap: {
        src: ['<%= concat.bootstrap.dest %>'],
        dest: 'dist/js/<%= pkg.name %>.min.js'
XhmikosR's avatar
XhmikosR committed
93
94
95
96
97
98
99
100
101
102
      },
      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'
103
104
105
106
107
      }
    },

    recess: {
      options: {
108
109
        compile: true,
        banner: '<%= banner %>'
110
111
112
113
114
115
116
117
      },
      bootstrap: {
        src: ['less/bootstrap.less'],
        dest: 'dist/css/<%= pkg.name %>.css'
      },
      min: {
        options: {
          compress: true
118
        },
119
120
        src: ['less/bootstrap.less'],
        dest: 'dist/css/<%= pkg.name %>.min.css'
121
122
123
124
125
126
127
128
129
130
131
      },
      theme: {
        src: ['less/theme.less'],
        dest: 'dist/css/<%= pkg.name %>-theme.css'
      },
      theme_min: {
        options: {
          compress: true
        },
        src: ['less/theme.less'],
        dest: 'dist/css/<%= pkg.name %>-theme.min.css'
132
133
134
      }
    },

Mark Otto's avatar
Mark Otto committed
135
136
137
    copy: {
      fonts: {
        expand: true,
XhmikosR's avatar
XhmikosR committed
138
        src: ['fonts/*'],
Mark Otto's avatar
Mark Otto committed
139
140
141
142
        dest: 'dist/'
      }
    },

143
144
145
146
147
148
    qunit: {
      options: {
        inject: 'js/tests/unit/phantom.js'
      },
      files: ['js/tests/*.html']
    },
149

150
151
152
153
154
    connect: {
      server: {
        options: {
          port: 3000,
          base: '.'
155
        }
156
157
158
      }
    },

159
160
161
162
163
164
    jekyll: {
      docs: {}
    },

    validation: {
      options: {
165
166
        reset: true,
        relaxerror: [
XhmikosR's avatar
XhmikosR committed
167
168
          'Bad value X-UA-Compatible for attribute http-equiv on element meta.',
          'Element img is missing required attribute src.'
169
        ]
170
171
      },
      files: {
XhmikosR's avatar
XhmikosR committed
172
        src: ['_gh_pages/**/*.html']
173
174
175
      }
    },

176
177
178
179
180
181
182
183
184
185
186
187
188
    watch: {
      src: {
        files: '<%= jshint.src.src %>',
        tasks: ['jshint:src', 'qunit']
      },
      test: {
        files: '<%= jshint.test.src %>',
        tasks: ['jshint:test', 'qunit']
      },
      recess: {
        files: 'less/*.less',
        tasks: ['recess']
      }
189
190
191
192
193
194
195
196
197
198
199
    },

    sed: {
      versionNumber: {
        pattern: (function () {
          var old = grunt.option('oldver')
          return old ? RegExp.quote(old) : old
        })(),
        replacement: grunt.option('newver'),
        recursive: true
      }
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
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
    },

    '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'
            }
            */
          ],
        }
      }
294
295
    }
  });
296
297


298
299
300
301
  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-connect');
Mark Otto's avatar
Mark Otto committed
302
  grunt.loadNpmTasks('grunt-contrib-copy');
303
304
305
306
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-qunit');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');
307
308
  grunt.loadNpmTasks('grunt-html-validation');
  grunt.loadNpmTasks('grunt-jekyll');
Chris Rebert's avatar
Chris Rebert committed
309
  grunt.loadNpmTasks('grunt-jscs-checker');
310
  grunt.loadNpmTasks('grunt-recess');
311
  grunt.loadNpmTasks('grunt-saucelabs');
312
  grunt.loadNpmTasks('grunt-sed');
313

314
  // Docs HTML validation task
315
  grunt.registerTask('validate-html', ['jekyll', 'validation']);
316

317
  // Test task.
Chris Rebert's avatar
Chris Rebert committed
318
  var testSubtasks = ['dist-css', 'jshint', 'jscs', 'qunit', 'validate-html'];
319
320
321
322
  // 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');
323
324
  }
  grunt.registerTask('test', testSubtasks);
325

326
327
  // JS distribution task.
  grunt.registerTask('dist-js', ['concat', 'uglify']);
328

329
330
  // CSS distribution task.
  grunt.registerTask('dist-css', ['recess']);
331

Mark Otto's avatar
Mark Otto committed
332
333
334
  // Fonts distribution task.
  grunt.registerTask('dist-fonts', ['copy']);

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

338
  // Default task.
339
  grunt.registerTask('default', ['test', 'dist', 'build-customizer']);
340

341
342
343
344
345
  // 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']);

346
347
348
349
350
351
352
353
  // 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
354
          return type == 'fonts' ? true : new RegExp('\\.' + type + '$').test(path)
355
356
        })
        .forEach(function (path) {
357
358
          var fullPath = type + '/' + path
          return files[path] = (type == 'fonts' ? btoa(fs.readFileSync(fullPath)) : fs.readFileSync(fullPath, 'utf8'))
359
360
361
362
        })
      return 'var __' + type + ' = ' + JSON.stringify(files) + '\n'
    }

fat's avatar
fat committed
363
    var files = getFiles('js') + getFiles('less') + getFiles('fonts')
364
    fs.writeFileSync('docs-assets/js/raw-files.js', files)
365
  });
366
};