Gruntfile.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /* jshint node: true */
  2. module.exports = function(grunt) {
  3. "use strict";
  4. // Force use of Unix newlines
  5. grunt.util.linefeed = '\n';
  6. RegExp.quote = require('regexp-quote')
  7. var btoa = require('btoa')
  8. // Project configuration.
  9. grunt.initConfig({
  10. // Metadata.
  11. pkg: grunt.file.readJSON('package.json'),
  12. banner: '/*!\n' +
  13. ' * Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
  14. ' * Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
  15. ' * Licensed under <%= _.pluck(pkg.licenses, "url").join(", ") %>\n' +
  16. ' */\n\n',
  17. jqueryCheck: 'if (typeof jQuery === "undefined") { throw new Error("Bootstrap requires jQuery") }\n\n',
  18. // Task configuration.
  19. clean: {
  20. dist: ['dist']
  21. },
  22. jshint: {
  23. options: {
  24. jshintrc: 'js/.jshintrc'
  25. },
  26. gruntfile: {
  27. src: 'Gruntfile.js'
  28. },
  29. src: {
  30. src: ['js/*.js']
  31. },
  32. test: {
  33. src: ['js/tests/unit/*.js']
  34. }
  35. },
  36. concat: {
  37. options: {
  38. banner: '<%= banner %><%= jqueryCheck %>',
  39. stripBanners: false
  40. },
  41. bootstrap: {
  42. src: [
  43. 'js/transition.js',
  44. 'js/alert.js',
  45. 'js/button.js',
  46. 'js/carousel.js',
  47. 'js/collapse.js',
  48. 'js/dropdown.js',
  49. 'js/modal.js',
  50. 'js/tooltip.js',
  51. 'js/popover.js',
  52. 'js/scrollspy.js',
  53. 'js/tab.js',
  54. 'js/affix.js'
  55. ],
  56. dest: 'dist/js/<%= pkg.name %>.js'
  57. }
  58. },
  59. uglify: {
  60. options: {
  61. banner: '<%= banner %>',
  62. report: 'min'
  63. },
  64. bootstrap: {
  65. src: ['<%= concat.bootstrap.dest %>'],
  66. dest: 'dist/js/<%= pkg.name %>.min.js'
  67. }
  68. },
  69. recess: {
  70. options: {
  71. compile: true,
  72. banner: '<%= banner %>'
  73. },
  74. bootstrap: {
  75. src: ['less/bootstrap.less'],
  76. dest: 'dist/css/<%= pkg.name %>.css'
  77. },
  78. min: {
  79. options: {
  80. compress: true
  81. },
  82. src: ['less/bootstrap.less'],
  83. dest: 'dist/css/<%= pkg.name %>.min.css'
  84. },
  85. theme: {
  86. src: ['less/theme.less'],
  87. dest: 'dist/css/<%= pkg.name %>-theme.css'
  88. },
  89. theme_min: {
  90. options: {
  91. compress: true
  92. },
  93. src: ['less/theme.less'],
  94. dest: 'dist/css/<%= pkg.name %>-theme.min.css'
  95. }
  96. },
  97. copy: {
  98. fonts: {
  99. expand: true,
  100. src: ["fonts/*"],
  101. dest: 'dist/'
  102. }
  103. },
  104. qunit: {
  105. options: {
  106. inject: 'js/tests/unit/phantom.js'
  107. },
  108. files: ['js/tests/*.html']
  109. },
  110. connect: {
  111. server: {
  112. options: {
  113. port: 3000,
  114. base: '.'
  115. }
  116. }
  117. },
  118. jekyll: {
  119. docs: {}
  120. },
  121. validation: {
  122. options: {
  123. reset: true,
  124. relaxerror: [
  125. "Bad value X-UA-Compatible for attribute http-equiv on element meta.",
  126. "Element img is missing required attribute src."
  127. ]
  128. },
  129. files: {
  130. src: ["_gh_pages/**/*.html"]
  131. }
  132. },
  133. watch: {
  134. src: {
  135. files: '<%= jshint.src.src %>',
  136. tasks: ['jshint:src', 'qunit']
  137. },
  138. test: {
  139. files: '<%= jshint.test.src %>',
  140. tasks: ['jshint:test', 'qunit']
  141. },
  142. recess: {
  143. files: 'less/*.less',
  144. tasks: ['recess']
  145. }
  146. },
  147. sed: {
  148. versionNumber: {
  149. pattern: (function () {
  150. var old = grunt.option('oldver')
  151. return old ? RegExp.quote(old) : old
  152. })(),
  153. replacement: grunt.option('newver'),
  154. recursive: true
  155. }
  156. },
  157. 'saucelabs-qunit': {
  158. all: {
  159. options: {
  160. build: process.env.TRAVIS_JOB_ID,
  161. concurrency: 3,
  162. urls: ['http://127.0.0.1:3000/js/tests/index.html'],
  163. browsers: [
  164. // See https://saucelabs.com/docs/platforms/webdriver
  165. {
  166. browserName: 'safari',
  167. version: '6',
  168. platform: 'OS X 10.8'
  169. },
  170. {
  171. browserName: 'chrome',
  172. version: '28',
  173. platform: 'OS X 10.6'
  174. },
  175. /* FIXME: currently fails 1 tooltip test
  176. {
  177. browserName: 'firefox',
  178. version: '25',
  179. platform: 'OS X 10.6'
  180. },*/
  181. // Mac Opera not currently supported by Sauce Labs
  182. /* FIXME: currently fails 1 tooltip test
  183. {
  184. browserName: 'internet explorer',
  185. version: '11',
  186. platform: 'Windows 8.1'
  187. },*/
  188. /*
  189. {
  190. browserName: 'internet explorer',
  191. version: '10',
  192. platform: 'Windows 8'
  193. },
  194. {
  195. browserName: 'internet explorer',
  196. version: '9',
  197. platform: 'Windows 7'
  198. },
  199. {
  200. browserName: 'internet explorer',
  201. version: '8',
  202. platform: 'Windows 7'
  203. },
  204. {// unofficial
  205. browserName: 'internet explorer',
  206. version: '7',
  207. platform: 'Windows XP'
  208. },
  209. */
  210. {
  211. browserName: 'chrome',
  212. version: '31',
  213. platform: 'Windows 8.1'
  214. },
  215. {
  216. browserName: 'firefox',
  217. version: '25',
  218. platform: 'Windows 8.1'
  219. },
  220. // Win Opera 15+ not currently supported by Sauce Labs
  221. {
  222. browserName: 'iphone',
  223. version: '6.1',
  224. platform: 'OS X 10.8'
  225. },
  226. // iOS Chrome not currently supported by Sauce Labs
  227. // Linux (unofficial)
  228. {
  229. browserName: 'chrome',
  230. version: '30',
  231. platform: 'Linux'
  232. },
  233. {
  234. browserName: 'firefox',
  235. version: '25',
  236. platform: 'Linux'
  237. }
  238. // Android Chrome not currently supported by Sauce Labs
  239. /* Android Browser (super-unofficial)
  240. {
  241. browserName: 'android',
  242. version: '4.0',
  243. platform: 'Linux'
  244. }
  245. */
  246. ],
  247. }
  248. }
  249. }
  250. });
  251. // These plugins provide necessary tasks.
  252. grunt.loadNpmTasks('grunt-contrib-clean');
  253. grunt.loadNpmTasks('grunt-contrib-concat');
  254. grunt.loadNpmTasks('grunt-contrib-connect');
  255. grunt.loadNpmTasks('grunt-contrib-copy');
  256. grunt.loadNpmTasks('grunt-contrib-jshint');
  257. grunt.loadNpmTasks('grunt-contrib-qunit');
  258. grunt.loadNpmTasks('grunt-contrib-uglify');
  259. grunt.loadNpmTasks('grunt-contrib-watch');
  260. grunt.loadNpmTasks('grunt-html-validation');
  261. grunt.loadNpmTasks('grunt-jekyll');
  262. grunt.loadNpmTasks('grunt-recess');
  263. grunt.loadNpmTasks('grunt-saucelabs');
  264. grunt.loadNpmTasks('grunt-sed');
  265. // Docs HTML validation task
  266. grunt.registerTask('validate-html', ['jekyll', 'validation']);
  267. // Test task.
  268. var testSubtasks = ['dist-css', 'jshint', 'qunit', 'validate-html'];
  269. // Only run Sauce Labs tests if there's a Sauce access key
  270. if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined') {
  271. testSubtasks.push('connect');
  272. testSubtasks.push('saucelabs-qunit');
  273. }
  274. grunt.registerTask('test', testSubtasks);
  275. // JS distribution task.
  276. grunt.registerTask('dist-js', ['concat', 'uglify']);
  277. // CSS distribution task.
  278. grunt.registerTask('dist-css', ['recess']);
  279. // Fonts distribution task.
  280. grunt.registerTask('dist-fonts', ['copy']);
  281. // Full distribution task.
  282. grunt.registerTask('dist', ['clean', 'dist-css', 'dist-fonts', 'dist-js']);
  283. // Default task.
  284. grunt.registerTask('default', ['test', 'dist', 'build-customizer']);
  285. // Version numbering task.
  286. // grunt change-version-number --oldver=A.B.C --newver=X.Y.Z
  287. // This can be overzealous, so its changes should always be manually reviewed!
  288. grunt.registerTask('change-version-number', ['sed']);
  289. // task for building customizer
  290. grunt.registerTask('build-customizer', 'Add scripts/less files to customizer.', function () {
  291. var fs = require('fs')
  292. function getFiles(type) {
  293. var files = {}
  294. fs.readdirSync(type)
  295. .filter(function (path) {
  296. return type == 'fonts' ? true : new RegExp('\\.' + type + '$').test(path)
  297. })
  298. .forEach(function (path) {
  299. var fullPath = type + '/' + path
  300. return files[path] = (type == 'fonts' ? btoa(fs.readFileSync(fullPath)) : fs.readFileSync(fullPath, 'utf8'))
  301. })
  302. return 'var __' + type + ' = ' + JSON.stringify(files) + '\n'
  303. }
  304. var files = getFiles('js') + getFiles('less') + getFiles('fonts')
  305. fs.writeFileSync('docs-assets/js/raw-files.js', files)
  306. });
  307. };