253c618a43959ba78ad1aa008d2faa61053a7274.svn-base 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. /*!
  2. * Bootstrap's Gruntfile
  3. * http://getbootstrap.com
  4. * Copyright 2013-2014 Twitter, Inc.
  5. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
  6. */
  7. module.exports = function (grunt) {
  8. 'use strict';
  9. // Force use of Unix newlines
  10. grunt.util.linefeed = '\n';
  11. RegExp.quote = function (string) {
  12. return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
  13. };
  14. var fs = require('fs');
  15. var path = require('path');
  16. var glob = require('glob');
  17. var npmShrinkwrap = require('npm-shrinkwrap');
  18. var mq4HoverShim = require('mq4-hover-shim');
  19. var generateCommonJSModule = require('./grunt/bs-commonjs-generator.js');
  20. var configBridge = grunt.file.readJSON('./grunt/configBridge.json', { encoding: 'utf8' });
  21. Object.keys(configBridge.paths).forEach(function (key) {
  22. configBridge.paths[key].forEach(function (val, i, arr) {
  23. arr[i] = path.join('./docs/assets', val);
  24. });
  25. });
  26. // Project configuration.
  27. grunt.initConfig({
  28. // Metadata.
  29. pkg: grunt.file.readJSON('package.json'),
  30. banner: '/*!\n' +
  31. ' * Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
  32. ' * Copyright 2011-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
  33. ' * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n' +
  34. ' */\n',
  35. jqueryCheck: 'if (typeof jQuery === \'undefined\') {\n' +
  36. ' throw new Error(\'Bootstrap\\\'s JavaScript requires jQuery\')\n' +
  37. '}\n',
  38. jqueryVersionCheck: '+function ($) {\n' +
  39. ' var version = $.fn.jquery.split(\' \')[0].split(\'.\')\n' +
  40. ' if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {\n' +
  41. ' throw new Error(\'Bootstrap\\\'s JavaScript requires jQuery version 1.9.1 or higher\')\n' +
  42. ' }\n' +
  43. '}(jQuery);\n\n',
  44. // Task configuration.
  45. clean: {
  46. dist: 'dist',
  47. docs: 'docs/dist'
  48. },
  49. // JS build configuration
  50. lineremover: {
  51. es6Import: {
  52. files: {
  53. '<%= concat.bootstrap.dest %>': '<%= concat.bootstrap.dest %>'
  54. },
  55. options: {
  56. exclusionPattern: /^(import|export)/g
  57. }
  58. }
  59. },
  60. babel: {
  61. dev: {
  62. options: {
  63. sourceMap: true,
  64. modules: 'ignore'
  65. },
  66. files: {
  67. 'js/dist/util.js' : 'js/src/util.js',
  68. 'js/dist/alert.js' : 'js/src/alert.js',
  69. 'js/dist/button.js' : 'js/src/button.js',
  70. 'js/dist/carousel.js' : 'js/src/carousel.js',
  71. 'js/dist/collapse.js' : 'js/src/collapse.js',
  72. 'js/dist/dropdown.js' : 'js/src/dropdown.js',
  73. 'js/dist/modal.js' : 'js/src/modal.js',
  74. 'js/dist/scrollspy.js' : 'js/src/scrollspy.js',
  75. 'js/dist/tab.js' : 'js/src/tab.js',
  76. 'js/dist/tooltip.js' : 'js/src/tooltip.js',
  77. 'js/dist/popover.js' : 'js/src/popover.js'
  78. }
  79. },
  80. dist: {
  81. options: {
  82. modules: 'ignore'
  83. },
  84. files: {
  85. '<%= concat.bootstrap.dest %>' : '<%= concat.bootstrap.dest %>'
  86. }
  87. },
  88. umd: {
  89. options: {
  90. modules: 'umd'
  91. },
  92. files: {
  93. 'dist/js/umd/util.js' : 'js/src/util.js',
  94. 'dist/js/umd/alert.js' : 'js/src/alert.js',
  95. 'dist/js/umd/button.js' : 'js/src/button.js',
  96. 'dist/js/umd/carousel.js' : 'js/src/carousel.js',
  97. 'dist/js/umd/collapse.js' : 'js/src/collapse.js',
  98. 'dist/js/umd/dropdown.js' : 'js/src/dropdown.js',
  99. 'dist/js/umd/modal.js' : 'js/src/modal.js',
  100. 'dist/js/umd/scrollspy.js' : 'js/src/scrollspy.js',
  101. 'dist/js/umd/tab.js' : 'js/src/tab.js',
  102. 'dist/js/umd/tooltip.js' : 'js/src/tooltip.js',
  103. 'dist/js/umd/popover.js' : 'js/src/popover.js'
  104. }
  105. }
  106. },
  107. eslint: {
  108. options: {
  109. configFile: 'js/.eslintrc'
  110. },
  111. target: 'js/src/*.js'
  112. },
  113. jscs: {
  114. options: {
  115. config: 'js/.jscsrc'
  116. },
  117. grunt: {
  118. src: ['Gruntfile.js', 'grunt/*.js']
  119. },
  120. core: {
  121. src: 'js/src/*.js'
  122. },
  123. test: {
  124. src: 'js/tests/unit/*.js'
  125. },
  126. assets: {
  127. options: {
  128. requireCamelCaseOrUpperCaseIdentifiers: null
  129. },
  130. src: ['docs/assets/js/src/*.js', 'docs/assets/js/*.js', '!docs/assets/js/*.min.js']
  131. }
  132. },
  133. stamp: {
  134. options: {
  135. banner: '<%= banner %>\n<%= jqueryCheck %>\n<%= jqueryVersionCheck %>\n+function ($) {\n',
  136. footer: '\n}(jQuery);'
  137. },
  138. bootstrap: {
  139. files: {
  140. src: '<%= concat.bootstrap.dest %>'
  141. }
  142. }
  143. },
  144. concat: {
  145. options: {
  146. stripBanners: false
  147. },
  148. bootstrap: {
  149. src: [
  150. 'js/src/util.js',
  151. 'js/src/alert.js',
  152. 'js/src/button.js',
  153. 'js/src/carousel.js',
  154. 'js/src/collapse.js',
  155. 'js/src/dropdown.js',
  156. 'js/src/modal.js',
  157. 'js/src/scrollspy.js',
  158. 'js/src/tab.js',
  159. 'js/src/tooltip.js',
  160. 'js/src/popover.js'
  161. ],
  162. dest: 'dist/js/<%= pkg.name %>.js'
  163. }
  164. },
  165. uglify: {
  166. options: {
  167. compress: {
  168. warnings: false
  169. },
  170. mangle: true,
  171. preserveComments: 'some'
  172. },
  173. core: {
  174. src: '<%= concat.bootstrap.dest %>',
  175. dest: 'dist/js/<%= pkg.name %>.min.js'
  176. },
  177. customize: {
  178. src: configBridge.paths.customizerJs,
  179. dest: 'docs/assets/js/customize.min.js'
  180. },
  181. docsJs: {
  182. src: configBridge.paths.docsJs,
  183. dest: 'docs/assets/js/docs.min.js'
  184. }
  185. },
  186. qunit: {
  187. options: {
  188. inject: 'js/tests/unit/phantom.js'
  189. },
  190. files: 'js/tests/index.html'
  191. },
  192. // CSS build configuration
  193. scsslint: {
  194. options: {
  195. config: 'scss/.scsslint.yml',
  196. reporterOutput: null
  197. },
  198. src: ['scss/*.scss', '!scss/_normalize.scss']
  199. },
  200. postcss: {
  201. options: {
  202. map: true,
  203. processors: [mq4HoverShim.postprocessorFor({ hoverSelectorPrefix: '.bs-true-hover ' })]
  204. },
  205. core: {
  206. src: 'dist/css/*.css'
  207. }
  208. },
  209. autoprefixer: {
  210. options: {
  211. browsers: [
  212. 'Android 2.3',
  213. 'Android >= 4',
  214. 'Chrome >= 35',
  215. 'Firefox >= 31',
  216. 'Explorer >= 9',
  217. 'iOS >= 7',
  218. 'Opera >= 12',
  219. 'Safari >= 7.1'
  220. ]
  221. },
  222. core: {
  223. options: {
  224. map: true
  225. },
  226. src: 'dist/css/*.css'
  227. },
  228. docs: {
  229. src: 'docs/assets/css/docs.min.css'
  230. },
  231. examples: {
  232. expand: true,
  233. cwd: 'docs/examples/',
  234. src: ['**/*.css'],
  235. dest: 'docs/examples/'
  236. }
  237. },
  238. cssmin: {
  239. options: {
  240. // TODO: disable `zeroUnits` optimization once clean-css 3.2 is released
  241. // and then simplify the fix for https://github.com/twbs/bootstrap/issues/14837 accordingly
  242. compatibility: 'ie8',
  243. keepSpecialComments: '*',
  244. sourceMap: true,
  245. noAdvanced: true
  246. },
  247. core: {
  248. files: [
  249. {
  250. expand: true,
  251. cwd: 'dist/css',
  252. src: ['*.css', '!*.min.css'],
  253. dest: 'dist/css',
  254. ext: '.min.css'
  255. }
  256. ]
  257. },
  258. docs: {
  259. src: 'docs/assets/css/docs.min.css',
  260. dest: 'docs/assets/css/docs.min.css'
  261. }
  262. },
  263. usebanner: {
  264. options: {
  265. position: 'top',
  266. banner: '<%= banner %>'
  267. },
  268. files: {
  269. src: 'dist/css/*.css'
  270. }
  271. },
  272. csscomb: {
  273. options: {
  274. config: 'scss/.csscomb.json'
  275. },
  276. dist: {
  277. expand: true,
  278. cwd: 'dist/css/',
  279. src: ['*.css', '!*.min.css'],
  280. dest: 'dist/css/'
  281. },
  282. examples: {
  283. expand: true,
  284. cwd: 'docs/examples/',
  285. src: '**/*.css',
  286. dest: 'docs/examples/'
  287. },
  288. docs: {
  289. src: 'docs/assets/css/src/docs.css',
  290. dest: 'docs/assets/css/src/docs.css'
  291. }
  292. },
  293. copy: {
  294. docs: {
  295. expand: true,
  296. cwd: 'dist/',
  297. src: [
  298. '**/*'
  299. ],
  300. dest: 'docs/dist/'
  301. }
  302. },
  303. connect: {
  304. server: {
  305. options: {
  306. port: 3000,
  307. base: '.'
  308. }
  309. }
  310. },
  311. jekyll: {
  312. options: {
  313. config: '_config.yml'
  314. },
  315. docs: {},
  316. github: {
  317. options: {
  318. raw: 'github: true'
  319. }
  320. }
  321. },
  322. htmllint: {
  323. options: {
  324. ignore: [
  325. 'Element “img” is missing required attribute “src”.',
  326. 'Bad value “X-UA-Compatible” for attribute “http-equiv” on element “meta”.',
  327. 'Attribute “autocomplete” not allowed on element “input” at this point.',
  328. 'Attribute “autocomplete” not allowed on element “button” at this point.',
  329. 'Element “div” not allowed as child of element “progress” in this context. (Suppressing further errors from this subtree.)',
  330. '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).',
  331. 'The “datetime” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.'
  332. ]
  333. },
  334. src: '_gh_pages/**/*.html'
  335. },
  336. watch: {
  337. src: {
  338. files: '<%= jscs.core.src %>',
  339. tasks: ['babel:dev']
  340. },
  341. sass: {
  342. files: 'scss/**/*.scss',
  343. tasks: ['dist-css', 'docs']
  344. },
  345. docs: {
  346. files: 'docs/assets/scss/**/*.scss',
  347. tasks: ['dist-css', 'docs']
  348. }
  349. },
  350. sed: {
  351. versionNumber: {
  352. pattern: (function () {
  353. var old = grunt.option('oldver');
  354. return old ? RegExp.quote(old) : old;
  355. })(),
  356. replacement: grunt.option('newver'),
  357. recursive: true
  358. }
  359. },
  360. 'saucelabs-qunit': {
  361. all: {
  362. options: {
  363. build: process.env.TRAVIS_JOB_ID,
  364. concurrency: 10,
  365. maxRetries: 3,
  366. maxPollRetries: 4,
  367. urls: ['http://127.0.0.1:3000/js/tests/index.html?hidepassed'],
  368. browsers: grunt.file.readYAML('grunt/sauce_browsers.yml')
  369. }
  370. }
  371. },
  372. exec: {
  373. npmUpdate: {
  374. command: 'npm update'
  375. },
  376. bundleUpdate: {
  377. command: function () {
  378. // Update dev gems and all the test gemsets
  379. return 'bundle update && ' + glob.sync('test-infra/gemfiles/*.gemfile').map(function (gemfile) {
  380. return 'BUNDLE_GEMFILE=' + gemfile + ' bundle update';
  381. }).join(' && ');
  382. }
  383. }
  384. },
  385. buildcontrol: {
  386. options: {
  387. dir: '_gh_pages',
  388. commit: true,
  389. push: true,
  390. message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
  391. },
  392. pages: {
  393. options: {
  394. remote: 'git@github.com:twbs/derpstrap.git',
  395. branch: 'gh-pages'
  396. }
  397. }
  398. }
  399. });
  400. // These plugins provide necessary tasks.
  401. require('load-grunt-tasks')(grunt, { scope: 'devDependencies',
  402. // Exclude Sass compilers. We choose the one to load later on.
  403. pattern: ['grunt-*', '!grunt-sass', '!grunt-contrib-sass'] });
  404. require('time-grunt')(grunt);
  405. // Docs HTML validation task
  406. grunt.registerTask('validate-html', ['jekyll:docs']);
  407. var runSubset = function (subset) {
  408. return !process.env.TWBS_TEST || process.env.TWBS_TEST === subset;
  409. };
  410. var isUndefOrNonZero = function (val) {
  411. return val === undefined || val !== '0';
  412. };
  413. // Test task.
  414. var testSubtasks = [];
  415. // Skip core tests if running a different subset of the test suite
  416. if (runSubset('core') &&
  417. // Skip core tests if this is a Savage build
  418. process.env.TRAVIS_REPO_SLUG !== 'twbs-savage/bootstrap') {
  419. testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'test-scss', 'test-js', 'docs']);
  420. }
  421. // Skip HTML validation if running a different subset of the test suite
  422. if (runSubset('validate-html') &&
  423. // Skip HTML5 validator on Travis when [skip validator] is in the commit message
  424. isUndefOrNonZero(process.env.TWBS_DO_VALIDATOR)) {
  425. testSubtasks.push('validate-html');
  426. }
  427. // Only run Sauce Labs tests if there's a Sauce access key
  428. if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined' &&
  429. // Skip Sauce if running a different subset of the test suite
  430. runSubset('sauce-js-unit') &&
  431. // Skip Sauce on Travis when [skip sauce] is in the commit message
  432. isUndefOrNonZero(process.env.TWBS_DO_SAUCE)) {
  433. testSubtasks.push('connect');
  434. testSubtasks.push('saucelabs-qunit');
  435. }
  436. grunt.registerTask('test', testSubtasks);
  437. grunt.registerTask('test-js', ['eslint', 'jscs:core', 'jscs:test', 'jscs:grunt', 'qunit']);
  438. // JS distribution task.
  439. grunt.registerTask('dist-js', ['concat', 'lineremover', 'babel:dist', 'stamp', 'uglify:core', 'commonjs']);
  440. grunt.registerTask('test-scss', ['scsslint']);
  441. // CSS distribution task.
  442. // Supported Compilers: sass (Ruby) and libsass.
  443. (function (sassCompilerName) {
  444. require('./grunt/bs-sass-compile/' + sassCompilerName + '.js')(grunt);
  445. })(process.env.TWBS_SASS || 'libsass');
  446. // grunt.registerTask('sass-compile', ['sass:core', 'sass:extras', 'sass:docs']);
  447. grunt.registerTask('sass-compile', ['sass:core', 'sass:docs']);
  448. grunt.registerTask('dist-css', ['sass-compile', 'postcss:core', 'autoprefixer:core', 'usebanner', 'csscomb:dist', 'cssmin:core', 'cssmin:docs']);
  449. // Full distribution task.
  450. grunt.registerTask('dist', ['clean:dist', 'dist-css', 'dist-js']);
  451. // Default task.
  452. grunt.registerTask('default', ['clean:dist', 'test']);
  453. // Version numbering task.
  454. // grunt change-version-number --oldver=A.B.C --newver=X.Y.Z
  455. // This can be overzealous, so its changes should always be manually reviewed!
  456. grunt.registerTask('change-version-number', 'sed');
  457. grunt.registerTask('commonjs', ['babel:umd', 'npm-js']);
  458. grunt.registerTask('npm-js', 'Generate npm-js entrypoint module in dist dir.', function () {
  459. var srcFiles = Object.keys(grunt.config.get('babel.umd.files')).map(function (filename) {
  460. return './' + path.join('umd', path.basename(filename))
  461. })
  462. var destFilepath = 'dist/js/npm.js';
  463. generateCommonJSModule(grunt, srcFiles, destFilepath);
  464. });
  465. // Docs task.
  466. grunt.registerTask('docs-css', ['autoprefixer:docs', 'autoprefixer:examples', 'csscomb:docs', 'csscomb:examples', 'cssmin:docs']);
  467. grunt.registerTask('docs-js', ['uglify:docsJs']);
  468. grunt.registerTask('lint-docs-js', ['jscs:assets']);
  469. grunt.registerTask('docs', ['docs-css', 'docs-js', 'lint-docs-js', 'clean:docs', 'copy:docs']);
  470. grunt.registerTask('prep-release', ['dist', 'docs', 'jekyll:github', 'htmlmin', 'compress']);
  471. // Publish to GitHub
  472. grunt.registerTask('publish', ['buildcontrol:pages']);
  473. // Task for updating the cached npm packages used by the Travis build (which are controlled by test-infra/npm-shrinkwrap.json).
  474. // This task should be run and the updated file should be committed whenever Bootstrap's dependencies change.
  475. grunt.registerTask('update-shrinkwrap', ['exec:npmUpdate', '_update-shrinkwrap']);
  476. grunt.registerTask('_update-shrinkwrap', function () {
  477. var done = this.async();
  478. npmShrinkwrap({ dev: true, dirname: __dirname }, function (err) {
  479. if (err) {
  480. grunt.fail.warn(err);
  481. }
  482. var dest = 'test-infra/npm-shrinkwrap.json';
  483. fs.renameSync('npm-shrinkwrap.json', dest);
  484. grunt.log.writeln('File ' + dest.cyan + ' updated.');
  485. done();
  486. });
  487. });
  488. // Task for updating the cached RubyGem packages used by the Travis build (which are controlled by test-infra/Gemfile.lock).
  489. // This task should be run and the updated file should be committed whenever Bootstrap's RubyGem dependencies change.
  490. grunt.registerTask('update-gemfile-lock', ['exec:bundleUpdate']);
  491. };