1 | /*!
|
---|
2 | * Bootstrap's Gruntfile
|
---|
3 | * https://getbootstrap.com/
|
---|
4 | * Copyright 2013-2019 Twitter, Inc.
|
---|
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
---|
6 | */
|
---|
7 |
|
---|
8 | module.exports = function (grunt) {
|
---|
9 | 'use strict';
|
---|
10 |
|
---|
11 | // Force use of Unix newlines
|
---|
12 | grunt.util.linefeed = '\n';
|
---|
13 |
|
---|
14 | RegExp.quote = function (string) {
|
---|
15 | return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
|
---|
16 | };
|
---|
17 |
|
---|
18 | var fs = require('fs');
|
---|
19 | var path = require('path');
|
---|
20 | var generateGlyphiconsData = require('./grunt/bs-glyphicons-data-generator.js');
|
---|
21 | var BsLessdocParser = require('./grunt/bs-lessdoc-parser.js');
|
---|
22 | var getLessVarsData = function () {
|
---|
23 | var filePath = path.join(__dirname, 'less/variables.less');
|
---|
24 | var fileContent = fs.readFileSync(filePath, { encoding: 'utf8' });
|
---|
25 | var parser = new BsLessdocParser(fileContent);
|
---|
26 | return { sections: parser.parseFile() };
|
---|
27 | };
|
---|
28 | var generateRawFiles = require('./grunt/bs-raw-files-generator.js');
|
---|
29 | var generateCommonJSModule = require('./grunt/bs-commonjs-generator.js');
|
---|
30 | var configBridge = grunt.file.readJSON('./grunt/configBridge.json', { encoding: 'utf8' });
|
---|
31 |
|
---|
32 | Object.keys(configBridge.paths).forEach(function (key) {
|
---|
33 | configBridge.paths[key].forEach(function (val, i, arr) {
|
---|
34 | arr[i] = path.join('./docs/assets', val);
|
---|
35 | });
|
---|
36 | });
|
---|
37 |
|
---|
38 | // Project configuration.
|
---|
39 | grunt.initConfig({
|
---|
40 |
|
---|
41 | // Metadata.
|
---|
42 | pkg: grunt.file.readJSON('package.json'),
|
---|
43 | banner: '/*!\n' +
|
---|
44 | ' * Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
|
---|
45 | ' * Copyright 2011-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
|
---|
46 | ' * Licensed under the <%= pkg.license %> license\n' +
|
---|
47 | ' */\n',
|
---|
48 | jqueryCheck: configBridge.config.jqueryCheck.join('\n'),
|
---|
49 | jqueryVersionCheck: configBridge.config.jqueryVersionCheck.join('\n'),
|
---|
50 |
|
---|
51 | // Task configuration.
|
---|
52 | clean: {
|
---|
53 | dist: 'dist',
|
---|
54 | docs: 'docs/dist'
|
---|
55 | },
|
---|
56 |
|
---|
57 | jshint: {
|
---|
58 | options: {
|
---|
59 | jshintrc: 'js/.jshintrc'
|
---|
60 | },
|
---|
61 | grunt: {
|
---|
62 | options: {
|
---|
63 | jshintrc: 'grunt/.jshintrc'
|
---|
64 | },
|
---|
65 | src: ['Gruntfile.js', 'package.js', 'grunt/*.js']
|
---|
66 | },
|
---|
67 | core: {
|
---|
68 | src: 'js/*.js'
|
---|
69 | },
|
---|
70 | test: {
|
---|
71 | options: {
|
---|
72 | jshintrc: 'js/tests/unit/.jshintrc'
|
---|
73 | },
|
---|
74 | src: 'js/tests/unit/*.js'
|
---|
75 | },
|
---|
76 | assets: {
|
---|
77 | src: ['docs/assets/js/src/*.js', 'docs/assets/js/*.js', '!docs/assets/js/*.min.js']
|
---|
78 | }
|
---|
79 | },
|
---|
80 |
|
---|
81 | jscs: {
|
---|
82 | options: {
|
---|
83 | config: 'js/.jscsrc'
|
---|
84 | },
|
---|
85 | grunt: {
|
---|
86 | src: '<%= jshint.grunt.src %>'
|
---|
87 | },
|
---|
88 | core: {
|
---|
89 | src: '<%= jshint.core.src %>'
|
---|
90 | },
|
---|
91 | test: {
|
---|
92 | src: '<%= jshint.test.src %>'
|
---|
93 | },
|
---|
94 | assets: {
|
---|
95 | options: {
|
---|
96 | requireCamelCaseOrUpperCaseIdentifiers: null
|
---|
97 | },
|
---|
98 | src: '<%= jshint.assets.src %>'
|
---|
99 | }
|
---|
100 | },
|
---|
101 |
|
---|
102 | concat: {
|
---|
103 | options: {
|
---|
104 | banner: '<%= banner %>\n<%= jqueryCheck %>\n<%= jqueryVersionCheck %>',
|
---|
105 | stripBanners: false
|
---|
106 | },
|
---|
107 | core: {
|
---|
108 | src: [
|
---|
109 | 'js/transition.js',
|
---|
110 | 'js/alert.js',
|
---|
111 | 'js/button.js',
|
---|
112 | 'js/carousel.js',
|
---|
113 | 'js/collapse.js',
|
---|
114 | 'js/dropdown.js',
|
---|
115 | 'js/modal.js',
|
---|
116 | 'js/tooltip.js',
|
---|
117 | 'js/popover.js',
|
---|
118 | 'js/scrollspy.js',
|
---|
119 | 'js/tab.js',
|
---|
120 | 'js/affix.js'
|
---|
121 | ],
|
---|
122 | dest: 'dist/js/<%= pkg.name %>.js'
|
---|
123 | }
|
---|
124 | },
|
---|
125 |
|
---|
126 | uglify: {
|
---|
127 | options: {
|
---|
128 | compress: true,
|
---|
129 | mangle: true,
|
---|
130 | ie8: true,
|
---|
131 | output: {
|
---|
132 | comments: /^!|@preserve|@license|@cc_on/i
|
---|
133 | }
|
---|
134 | },
|
---|
135 | core: {
|
---|
136 | src: '<%= concat.core.dest %>',
|
---|
137 | dest: 'dist/js/<%= pkg.name %>.min.js'
|
---|
138 | },
|
---|
139 | customize: {
|
---|
140 | src: configBridge.paths.customizerJs,
|
---|
141 | dest: 'docs/assets/js/customize.min.js'
|
---|
142 | },
|
---|
143 | docs: {
|
---|
144 | src: configBridge.paths.docsJs,
|
---|
145 | dest: 'docs/assets/js/docs.min.js'
|
---|
146 | }
|
---|
147 | },
|
---|
148 |
|
---|
149 | less: {
|
---|
150 | options: {
|
---|
151 | ieCompat: true,
|
---|
152 | strictMath: true,
|
---|
153 | sourceMap: true,
|
---|
154 | outputSourceFiles: true
|
---|
155 | },
|
---|
156 | core: {
|
---|
157 | options: {
|
---|
158 | sourceMapURL: '<%= pkg.name %>.css.map',
|
---|
159 | sourceMapFilename: 'dist/css/<%= pkg.name %>.css.map'
|
---|
160 | },
|
---|
161 | src: 'less/bootstrap.less',
|
---|
162 | dest: 'dist/css/<%= pkg.name %>.css'
|
---|
163 | },
|
---|
164 | theme: {
|
---|
165 | options: {
|
---|
166 | sourceMapURL: '<%= pkg.name %>-theme.css.map',
|
---|
167 | sourceMapFilename: 'dist/css/<%= pkg.name %>-theme.css.map'
|
---|
168 | },
|
---|
169 | src: 'less/theme.less',
|
---|
170 | dest: 'dist/css/<%= pkg.name %>-theme.css'
|
---|
171 | },
|
---|
172 | docs: {
|
---|
173 | options: {
|
---|
174 | sourceMapURL: 'docs.css.map',
|
---|
175 | sourceMapFilename: 'docs/assets/css/docs.css.map'
|
---|
176 | },
|
---|
177 | src: 'docs/assets/less/docs.less',
|
---|
178 | dest: 'docs/assets/css/docs.css'
|
---|
179 | },
|
---|
180 | docsIe: {
|
---|
181 | options: {
|
---|
182 | sourceMap: false
|
---|
183 | },
|
---|
184 | src: 'docs/assets/less/ie10-viewport-bug-workaround.less',
|
---|
185 | dest: 'docs/assets/css/ie10-viewport-bug-workaround.css'
|
---|
186 | }
|
---|
187 | },
|
---|
188 |
|
---|
189 | postcss: {
|
---|
190 | options: {
|
---|
191 | map: {
|
---|
192 | inline: false,
|
---|
193 | sourcesContent: true
|
---|
194 | },
|
---|
195 | processors: [
|
---|
196 | require('autoprefixer')(configBridge.config.autoprefixer)
|
---|
197 | ]
|
---|
198 | },
|
---|
199 | core: {
|
---|
200 | src: 'dist/css/<%= pkg.name %>.css'
|
---|
201 | },
|
---|
202 | theme: {
|
---|
203 | src: 'dist/css/<%= pkg.name %>-theme.css'
|
---|
204 | },
|
---|
205 | docs: {
|
---|
206 | src: 'docs/assets/css/docs.css'
|
---|
207 | },
|
---|
208 | examples: {
|
---|
209 | options: {
|
---|
210 | map: false
|
---|
211 | },
|
---|
212 | expand: true,
|
---|
213 | cwd: 'docs/examples/',
|
---|
214 | src: ['**/*.css'],
|
---|
215 | dest: 'docs/examples/'
|
---|
216 | }
|
---|
217 | },
|
---|
218 |
|
---|
219 | stylelint: {
|
---|
220 | options: {
|
---|
221 | configFile: 'grunt/.stylelintrc',
|
---|
222 | reportNeedlessDisables: false
|
---|
223 | },
|
---|
224 | dist: [
|
---|
225 | 'less/**/*.less'
|
---|
226 | ],
|
---|
227 | docs: [
|
---|
228 | 'docs/assets/less/**/*.less'
|
---|
229 | ],
|
---|
230 | examples: [
|
---|
231 | 'docs/examples/**/*.css'
|
---|
232 | ]
|
---|
233 | },
|
---|
234 |
|
---|
235 | cssmin: {
|
---|
236 | options: {
|
---|
237 | compatibility: 'ie8',
|
---|
238 | sourceMap: true,
|
---|
239 | sourceMapInlineSources: true,
|
---|
240 | level: {
|
---|
241 | 1: {
|
---|
242 | specialComments: 'all'
|
---|
243 | }
|
---|
244 | }
|
---|
245 | },
|
---|
246 | core: {
|
---|
247 | src: 'dist/css/<%= pkg.name %>.css',
|
---|
248 | dest: 'dist/css/<%= pkg.name %>.min.css'
|
---|
249 | },
|
---|
250 | theme: {
|
---|
251 | src: 'dist/css/<%= pkg.name %>-theme.css',
|
---|
252 | dest: 'dist/css/<%= pkg.name %>-theme.min.css'
|
---|
253 | },
|
---|
254 | docs: {
|
---|
255 | src: 'docs/assets/css/docs.css',
|
---|
256 | dest: 'docs/assets/css/docs.min.css'
|
---|
257 | }
|
---|
258 | },
|
---|
259 |
|
---|
260 | copy: {
|
---|
261 | fonts: {
|
---|
262 | expand: true,
|
---|
263 | src: 'fonts/**',
|
---|
264 | dest: 'dist/'
|
---|
265 | },
|
---|
266 | docs: {
|
---|
267 | expand: true,
|
---|
268 | cwd: 'dist/',
|
---|
269 | src: [
|
---|
270 | '**/*'
|
---|
271 | ],
|
---|
272 | dest: 'docs/dist/'
|
---|
273 | }
|
---|
274 | },
|
---|
275 |
|
---|
276 | connect: {
|
---|
277 | server: {
|
---|
278 | options: {
|
---|
279 | port: 3000,
|
---|
280 | base: '.'
|
---|
281 | }
|
---|
282 | }
|
---|
283 | },
|
---|
284 |
|
---|
285 | jekyll: {
|
---|
286 | options: {
|
---|
287 | bundleExec: true,
|
---|
288 | config: '_config.yml',
|
---|
289 | incremental: false
|
---|
290 | },
|
---|
291 | docs: {},
|
---|
292 | github: {
|
---|
293 | options: {
|
---|
294 | raw: 'github: true'
|
---|
295 | }
|
---|
296 | }
|
---|
297 | },
|
---|
298 |
|
---|
299 | pug: {
|
---|
300 | options: {
|
---|
301 | pretty: true,
|
---|
302 | data: getLessVarsData
|
---|
303 | },
|
---|
304 | customizerVars: {
|
---|
305 | src: 'docs/_pug/customizer-variables.pug',
|
---|
306 | dest: 'docs/_includes/customizer-variables.html'
|
---|
307 | },
|
---|
308 | customizerNav: {
|
---|
309 | src: 'docs/_pug/customizer-nav.pug',
|
---|
310 | dest: 'docs/_includes/nav/customize.html'
|
---|
311 | }
|
---|
312 | },
|
---|
313 |
|
---|
314 | htmllint: {
|
---|
315 | options: {
|
---|
316 | ignore: [
|
---|
317 | 'Element "img" is missing required attribute "src".'
|
---|
318 | ],
|
---|
319 | noLangDetect: true
|
---|
320 | },
|
---|
321 | src: ['_gh_pages/**/*.html', 'js/tests/**/*.html']
|
---|
322 | },
|
---|
323 |
|
---|
324 | watch: {
|
---|
325 | src: {
|
---|
326 | files: '<%= jshint.core.src %>',
|
---|
327 | tasks: ['jshint:core', 'exec:karma', 'concat']
|
---|
328 | },
|
---|
329 | test: {
|
---|
330 | files: '<%= jshint.test.src %>',
|
---|
331 | tasks: ['jshint:test', 'exec:karma']
|
---|
332 | },
|
---|
333 | less: {
|
---|
334 | files: 'less/**/*.less',
|
---|
335 | tasks: ['less', 'copy']
|
---|
336 | },
|
---|
337 | docs: {
|
---|
338 | files: 'docs/assets/less/**/*.less',
|
---|
339 | tasks: ['less']
|
---|
340 | }
|
---|
341 | },
|
---|
342 |
|
---|
343 | exec: {
|
---|
344 | browserstack: {
|
---|
345 | command: 'cross-env BROWSER=true karma start grunt/karma.conf.js'
|
---|
346 | },
|
---|
347 | karma: {
|
---|
348 | command: 'karma start grunt/karma.conf.js'
|
---|
349 | }
|
---|
350 | }
|
---|
351 | });
|
---|
352 |
|
---|
353 |
|
---|
354 | // These plugins provide necessary tasks.
|
---|
355 | require('load-grunt-tasks')(grunt, { scope: 'devDependencies' });
|
---|
356 | require('time-grunt')(grunt);
|
---|
357 |
|
---|
358 | // Docs HTML validation task
|
---|
359 | grunt.registerTask('validate-html', ['jekyll:docs', 'htmllint']);
|
---|
360 |
|
---|
361 | var runSubset = function (subset) {
|
---|
362 | return !process.env.TWBS_TEST || process.env.TWBS_TEST === subset;
|
---|
363 | };
|
---|
364 | var isUndefOrNonZero = function (val) {
|
---|
365 | return typeof val === 'undefined' || val !== '0';
|
---|
366 | };
|
---|
367 |
|
---|
368 | // Test task.
|
---|
369 | var testSubtasks = [];
|
---|
370 | // Skip core tests if running a different subset of the test suite
|
---|
371 | if (runSubset('core')) {
|
---|
372 | testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'stylelint:dist', 'test-js', 'docs']);
|
---|
373 | }
|
---|
374 | // Skip HTML validation if running a different subset of the test suite
|
---|
375 | if (runSubset('validate-html') &&
|
---|
376 | // Skip HTML5 validator on Travis when [skip validator] is in the commit message
|
---|
377 | isUndefOrNonZero(process.env.TWBS_DO_VALIDATOR)) {
|
---|
378 | testSubtasks.push('validate-html');
|
---|
379 | }
|
---|
380 | // Only run BrowserStack tests if there's a BrowserStack access key
|
---|
381 | if (typeof process.env.BROWSER_STACK_USERNAME !== 'undefined' &&
|
---|
382 | // Skip BrowserStack if running a different subset of the test suite
|
---|
383 | runSubset('browserstack') &&
|
---|
384 | // Skip BrowserStack on Travis when [skip browserstack] is in the commit message
|
---|
385 | isUndefOrNonZero(process.env.TWBS_DO_BROWSERSTACK)) {
|
---|
386 | testSubtasks.push('exec:browserstack');
|
---|
387 | }
|
---|
388 |
|
---|
389 | grunt.registerTask('test', testSubtasks);
|
---|
390 | grunt.registerTask('test-js', ['jshint:core', 'jshint:test', 'jshint:grunt', 'jscs:core', 'jscs:test', 'jscs:grunt', 'exec:karma']);
|
---|
391 |
|
---|
392 | // JS distribution task.
|
---|
393 | grunt.registerTask('dist-js', ['concat', 'uglify:core', 'commonjs']);
|
---|
394 |
|
---|
395 | // CSS distribution task.
|
---|
396 | grunt.registerTask('dist-css', ['less:core', 'less:theme', 'postcss:core', 'postcss:theme', 'cssmin:core', 'cssmin:theme']);
|
---|
397 |
|
---|
398 | // Full distribution task.
|
---|
399 | grunt.registerTask('dist', ['clean:dist', 'dist-css', 'copy:fonts', 'dist-js']);
|
---|
400 |
|
---|
401 | // Default task.
|
---|
402 | grunt.registerTask('default', ['clean:dist', 'copy:fonts', 'test']);
|
---|
403 |
|
---|
404 | grunt.registerTask('build-glyphicons-data', function () {
|
---|
405 | generateGlyphiconsData.call(this, grunt);
|
---|
406 | });
|
---|
407 |
|
---|
408 | // task for building customizer
|
---|
409 | grunt.registerTask('build-customizer', ['build-customizer-html', 'build-raw-files']);
|
---|
410 | grunt.registerTask('build-customizer-html', 'pug');
|
---|
411 | grunt.registerTask('build-raw-files', 'Add scripts/less files to customizer.', function () {
|
---|
412 | var banner = grunt.template.process('<%= banner %>');
|
---|
413 | generateRawFiles(grunt, banner);
|
---|
414 | });
|
---|
415 |
|
---|
416 | grunt.registerTask('commonjs', 'Generate CommonJS entrypoint module in dist dir.', function () {
|
---|
417 | var srcFiles = grunt.config.get('concat.core.src');
|
---|
418 | var destFilepath = 'dist/js/npm.js';
|
---|
419 | generateCommonJSModule(grunt, srcFiles, destFilepath);
|
---|
420 | });
|
---|
421 |
|
---|
422 | // Docs task.
|
---|
423 | grunt.registerTask('docs-css', ['less:docs', 'less:docsIe', 'postcss:docs', 'postcss:examples', 'cssmin:docs']);
|
---|
424 | grunt.registerTask('lint-docs-css', ['stylelint:docs', 'stylelint:examples']);
|
---|
425 | grunt.registerTask('docs-js', ['uglify:docs', 'uglify:customize']);
|
---|
426 | grunt.registerTask('lint-docs-js', ['jshint:assets', 'jscs:assets']);
|
---|
427 | grunt.registerTask('docs', ['docs-css', 'lint-docs-css', 'docs-js', 'lint-docs-js', 'clean:docs', 'copy:docs', 'build-glyphicons-data', 'build-customizer']);
|
---|
428 |
|
---|
429 | grunt.registerTask('prep-release', ['dist', 'docs', 'jekyll:github']);
|
---|
430 | };
|
---|