/* jshint node:true */ module.exports = function(grunt) { var path = require('path'), SOURCE_DIR = 'src/', BUILD_DIR = 'build/'; // Load tasks. require('matchdep').filterDev(['grunt-*', '!grunt-legacy-util']).forEach( grunt.loadNpmTasks ); // Load legacy utils grunt.util = require('grunt-legacy-util'); // Project configuration. grunt.initConfig({ autoprefixer: { options: { browsers: ['Android >= 2.1', 'Chrome >= 21', 'Explorer >= 7', 'Firefox >= 17', 'Opera >= 12.1', 'Safari >= 6.0'], cascade: false }, core: { expand: true, cwd: SOURCE_DIR, dest: SOURCE_DIR, src: [ 'wp-admin/css/*.css', 'wp-includes/css/*.css' ] }, colors: { expand: true, cwd: BUILD_DIR, dest: BUILD_DIR, src: [ 'wp-admin/css/colors/*/colors.css' ] } }, clean: { all: [BUILD_DIR], dynamic: { dot: true, expand: true, cwd: BUILD_DIR, src: [] }, tinymce: ['<%= concat.tinymce.dest %>'], qunit: ['tests/qunit/compiled.html'] }, copy: { files: { files: [ { dot: true, expand: true, cwd: SOURCE_DIR, src: [ '**', '!**/.{svn,git}/**', // Ignore version control directories. // Ignore unminified versions of external libs we don't ship: '!wp-includes/js/backbone.js', '!wp-includes/js/underscore.js', '!wp-includes/js/jquery/jquery.masonry.js', '!wp-includes/js/jquery/ui/*.js', '!wp-includes/js/tinymce/tinymce.js', '!wp-includes/version.php' // Exclude version.php ], dest: BUILD_DIR }, { src: 'wp-config-sample.php', dest: BUILD_DIR } ] }, 'wp-admin-rtl': { options: { processContent: function( src ) { return src.replace( /\.css/g, '-rtl.css' ); } }, src: SOURCE_DIR + 'wp-admin/css/wp-admin.css', dest: BUILD_DIR + 'wp-admin/css/wp-admin-rtl.css' }, version: { options: { processContent: function( src ) { return src.replace( /^\$wp_version = '(.+?)';/m, function( str, version ) { version = version.replace( /-src$/, '' ); // If the version includes an SVN commit (-12345), it's not a released alpha/beta. Append a date. version = version.replace( /-[\d]{5}$/, '-' + grunt.template.today( 'yyyymmdd' ) ); /* jshint quotmark: true */ return "$wp_version = '" + version + "';"; }); } }, src: SOURCE_DIR + 'wp-includes/version.php', dest: BUILD_DIR + 'wp-includes/version.php' }, dynamic: { dot: true, expand: true, cwd: SOURCE_DIR, dest: BUILD_DIR, src: [] }, qunit: { src: 'tests/qunit/index.html', dest: 'tests/qunit/compiled.html', options: { processContent: function( src ) { src = src.replace( /([^\.])*\.\.\/src/ig , '/../build' ); src = src.replace( '/jquery/ui/core.js', '/jquery/ui/core.min.js' ); return src; } } } }, browserify: { media: { files: { 'src/wp-includes/js/media/models.js' : [ SOURCE_DIR + 'wp-includes/js/media/models.manifest.js' ], 'src/wp-includes/js/media/views.js' : [ SOURCE_DIR + 'wp-includes/js/media/views.manifest.js' ], 'src/wp-includes/js/media/audio-video.js' : [ SOURCE_DIR + 'wp-includes/js/media/audio-video.manifest.js' ], 'src/wp-includes/js/media/grid.js' : [ SOURCE_DIR + 'wp-includes/js/media/grid.manifest.js' ] } } }, sass: { colors: { expand: true, cwd: SOURCE_DIR, dest: BUILD_DIR, ext: '.css', src: ['wp-admin/css/colors/*/colors.scss'], options: { outputStyle: 'expanded' } } }, cssmin: { options: { 'wp-admin': ['wp-admin', 'color-picker', 'customize-controls', 'customize-widgets', 'ie', 'install', 'login', 'press-this', 'deprecated-*'] }, core: { expand: true, cwd: SOURCE_DIR, dest: BUILD_DIR, ext: '.min.css', src: [ 'wp-admin/css/{<%= cssmin.options["wp-admin"] %>}.css', 'wp-includes/css/*.css' ] }, rtl: { expand: true, cwd: BUILD_DIR, dest: BUILD_DIR, ext: '.min.css', src: [ 'wp-admin/css/{<%= cssmin.options["wp-admin"] %>}-rtl.css', 'wp-includes/css/*-rtl.css' ] }, colors: { expand: true, cwd: BUILD_DIR, dest: BUILD_DIR, ext: '.min.css', src: [ 'wp-admin/css/colors/*/*.css' ] } }, cssjanus: { core: { options: { swapLtrRtlInUrl: false, processContent: function( src ) { return src.replace( /url\((.+?)\.css\)/g, 'url($1-rtl.css)' ); } }, expand: true, cwd: SOURCE_DIR, dest: BUILD_DIR, ext: '-rtl.css', src: [ 'wp-admin/css/*.css', 'wp-includes/css/*.css' ] }, colors: { options: { processContent: function( src ) { return src.replace( /([^/]+)\.css/gi, '$1-rtl.css' ); } }, expand: true, cwd: BUILD_DIR, dest: BUILD_DIR, ext: '-rtl.css', src: [ 'wp-admin/css/colors/*/colors.css' ] }, dynamic: { expand: true, cwd: SOURCE_DIR, dest: BUILD_DIR, ext: '-rtl.css', src: [] } }, jshint: { options: grunt.file.readJSON('.jshintrc'), grunt: { src: ['Gruntfile.js'] }, tests: { src: [ 'tests/qunit/**/*.js', '!tests/qunit/vendor/*', '!tests/qunit/editor/**' ], options: grunt.file.readJSON('tests/qunit/.jshintrc') }, themes: { expand: true, cwd: SOURCE_DIR + 'wp-content/themes', src: [ 'twenty*/**/*.js', '!twenty{eleven,twelve,thirteen}/**', // Third party scripts '!twenty{fourteen,fifteen}/js/html5.js' ] }, media: { options: { browserify: true }, expand: true, cwd: SOURCE_DIR, src: [ 'wp-includes/js/media/**/*.js', '!wp-includes/js/media/*.js', 'wp-includes/js/media/*.manifest.js' ] }, core: { expand: true, cwd: SOURCE_DIR, src: [ 'wp-admin/js/*.js', 'wp-includes/js/*.js', // WordPress scripts inside directories 'wp-includes/js/jquery/jquery.table-hotkeys.js', 'wp-includes/js/mediaelement/wp-mediaelement.js', 'wp-includes/js/plupload/handlers.js', 'wp-includes/js/plupload/wp-plupload.js', 'wp-includes/js/tinymce/plugins/wordpress/plugin.js', 'wp-includes/js/tinymce/plugins/wp*/plugin.js', // Third party scripts '!wp-admin/js/farbtastic.js', '!wp-includes/js/backbone*.js', '!wp-includes/js/swfobject.js', '!wp-includes/js/underscore*.js', '!wp-includes/js/colorpicker.js', '!wp-includes/js/hoverIntent.js', '!wp-includes/js/json2.js', '!wp-includes/js/tw-sack.js', '!**/*.min.js' ], // Remove once other JSHint errors are resolved options: { curly: false, eqeqeq: false }, // Limit JSHint's run to a single specified file: // // grunt jshint:core --file=filename.js // // Optionally, include the file path: // // grunt jshint:core --file=path/to/filename.js // filter: function( filepath ) { var index, file = grunt.option( 'file' ); // Don't filter when no target file is specified if ( ! file ) { return true; } // Normalize filepath for Windows filepath = filepath.replace( /\\/g, '/' ); index = filepath.lastIndexOf( '/' + file ); // Match only the filename passed from cli if ( filepath === file || ( -1 !== index && index === filepath.length - ( file.length + 1 ) ) ) { return true; } return false; } }, plugins: { expand: true, cwd: SOURCE_DIR + 'wp-content/plugins', src: [ '**/*.js', '!**/*.min.js' ], // Limit JSHint's run to a single specified plugin directory: // // grunt jshint:plugins --dir=foldername // filter: function( dirpath ) { var index, dir = grunt.option( 'dir' ); // Don't filter when no target folder is specified if ( ! dir ) { return true; } dirpath = dirpath.replace( /\\/g, '/' ); index = dirpath.lastIndexOf( '/' + dir ); // Match only the folder name passed from cli if ( -1 !== index ) { return true; } return false; } } }, qunit: { files: [ 'tests/qunit/**/*.html', '!tests/qunit/editor/**' ] }, phpunit: { 'default': { cmd: 'phpunit', args: ['-c', 'phpunit.xml.dist'] }, ajax: { cmd: 'phpunit', args: ['-c', 'phpunit.xml.dist', '--group', 'ajax'] }, multisite: { cmd: 'phpunit', args: ['-c', 'tests/phpunit/multisite.xml'] }, 'external-http': { cmd: 'phpunit', args: ['-c', 'phpunit.xml.dist', '--group', 'external-http'] } }, uglify: { core: { expand: true, cwd: SOURCE_DIR, dest: BUILD_DIR, ext: '.min.js', src: [ 'wp-admin/js/*.js', 'wp-includes/js/*.js', 'wp-includes/js/plupload/handlers.js', 'wp-includes/js/plupload/wp-plupload.js', 'wp-includes/js/tinymce/plugins/wordpress/plugin.js', 'wp-includes/js/tinymce/plugins/wp*/plugin.js', // Exceptions '!wp-admin/js/custom-header.js', // Why? We should minify this. '!wp-admin/js/farbtastic.js', '!wp-admin/js/iris.min.js', '!wp-admin/js/bookmarklet.js', // Minified and updated in /src with the precommit task. See uglify:bookmarklet. '!wp-includes/js/backbone.min.js', '!wp-includes/js/swfobject.js', '!wp-includes/js/underscore.min.js', '!wp-includes/js/zxcvbn.min.js' ] }, media: { expand: true, cwd: SOURCE_DIR, dest: BUILD_DIR, ext: '.min.js', src: [ 'wp-includes/js/media/audio-video.js', 'wp-includes/js/media/grid.js', 'wp-includes/js/media/models.js', 'wp-includes/js/media/views.js' ] }, jqueryui: { options: { preserveComments: 'some' }, expand: true, cwd: SOURCE_DIR, dest: BUILD_DIR, ext: '.min.js', src: ['wp-includes/js/jquery/ui/*.js'] }, bookmarklet: { options: { compress: { negate_iife: false } }, src: SOURCE_DIR + 'wp-admin/js/bookmarklet.js', dest: SOURCE_DIR + 'wp-admin/js/bookmarklet.min.js' } }, concat: { tinymce: { options: { separator: '\n', process: function( src, filepath ) { return '// Source: ' + filepath.replace( BUILD_DIR, '' ) + '\n' + src; } }, src: [ BUILD_DIR + 'wp-includes/js/tinymce/tinymce.min.js', BUILD_DIR + 'wp-includes/js/tinymce/themes/modern/theme.min.js', BUILD_DIR + 'wp-includes/js/tinymce/plugins/*/plugin.min.js' ], dest: BUILD_DIR + 'wp-includes/js/tinymce/wp-tinymce.js' } }, compress: { tinymce: { options: { mode: 'gzip', level: 9 }, src: '<%= concat.tinymce.dest %>', dest: BUILD_DIR + 'wp-includes/js/tinymce/wp-tinymce.js.gz' } }, jsvalidate:{ options: { globals: {}, esprimaOptions:{}, verbose: false }, build: { files: { src: [ BUILD_DIR + 'wp-{admin,includes}/**/*.js', BUILD_DIR + 'wp-content/themes/twenty*/**/*.js' ] } } }, imagemin: { core: { expand: true, cwd: SOURCE_DIR, src: [ 'wp-{admin,includes}/images/**/*.{png,jpg,gif,jpeg}', 'wp-includes/js/tinymce/skins/wordpress/images/*.{png,jpg,gif,jpeg}' ], dest: SOURCE_DIR } }, watch: { all: { files: [ SOURCE_DIR + '**', // Ignore version control directories. '!' + SOURCE_DIR + '**/.{svn,git}/**' ], tasks: ['clean:dynamic', 'copy:dynamic'], options: { dot: true, spawn: false, interval: 2000 } }, browserify: { files: [ SOURCE_DIR + 'wp-includes/js/media/**/*.js', '!' + SOURCE_DIR + 'wp-includes/js/media/audio-video.js', '!' + SOURCE_DIR + 'wp-includes/js/media/grid.js', '!' + SOURCE_DIR + 'wp-includes/js/media/models.js', '!' + SOURCE_DIR + 'wp-includes/js/media/views.js' ], tasks: ['browserify', 'uglify:media'] }, config: { files: 'Gruntfile.js' }, colors: { files: [SOURCE_DIR + 'wp-admin/css/colors/**'], tasks: ['sass:colors'] }, rtl: { files: [ SOURCE_DIR + 'wp-admin/css/*.css', SOURCE_DIR + 'wp-includes/css/*.css' ], tasks: ['cssjanus:dynamic'], options: { spawn: false, interval: 2000 } }, test: { files: [ 'tests/qunit/**', '!tests/qunit/editor/**' ], tasks: ['qunit'] } } }); // Register tasks. // RTL task. grunt.registerTask('rtl', ['cssjanus:core', 'cssjanus:colors']); // Color schemes task. grunt.registerTask('colors', ['sass:colors', 'autoprefixer:colors']); // JSHint task. grunt.registerTask( 'jshint:corejs', [ 'jshint:grunt', 'jshint:tests', 'jshint:themes', 'jshint:core', 'jshint:media' ] ); // Pre-commit task. grunt.registerTask('precommit', 'Runs front-end dev/test tasks in preparation for a commit.', ['autoprefixer:core', 'imagemin:core', 'jshint:corejs', 'qunit:compiled', 'uglify:bookmarklet']); // Copy task. grunt.registerTask('copy:all', ['copy:files', 'copy:wp-admin-rtl', 'copy:version']); // Build task. grunt.registerTask('build', ['clean:all', 'copy:all', 'cssmin:core', 'colors', 'rtl', 'cssmin:rtl', 'cssmin:colors', 'browserify:media', 'uglify:core', 'uglify:media', 'uglify:jqueryui', 'concat:tinymce', 'compress:tinymce', 'clean:tinymce', 'jsvalidate:build']); // Testing tasks. grunt.registerMultiTask('phpunit', 'Runs PHPUnit tests, including the ajax, external-http, and multisite tests.', function() { grunt.util.spawn({ cmd: this.data.cmd, args: this.data.args, opts: {stdio: 'inherit'} }, this.async()); }); grunt.registerTask('qunit:compiled', 'Runs QUnit tests on compiled as well as uncompiled scripts.', ['build', 'copy:qunit', 'qunit']); grunt.registerTask('test', 'Runs all QUnit and PHPUnit tasks.', ['qunit:compiled', 'phpunit']); // Travis CI tasks. grunt.registerTask('travis:js', 'Runs Javascript Travis CI tasks.', [ 'jshint:corejs', 'qunit:compiled' ]); grunt.registerTask('travis:phpunit', 'Runs PHPUnit Travis CI tasks.', 'phpunit'); // Patch task. grunt.renameTask('patch_wordpress', 'patch'); // Default task. grunt.registerTask('default', ['build']); // Add a listener to the watch task. // // On `watch:all`, automatically updates the `copy:dynamic` and `clean:dynamic` // configurations so that only the changed files are updated. // On `watch:rtl`, automatically updates the `cssjanus:dynamic` configuration. grunt.event.on('watch', function( action, filepath, target ) { if ( target !== 'all' && target !== 'rtl' ) { return; } var relativePath = path.relative( SOURCE_DIR, filepath ), cleanSrc = ( action === 'deleted' ) ? [relativePath] : [], copySrc = ( action === 'deleted' ) ? [] : [relativePath]; grunt.config(['clean', 'dynamic', 'src'], cleanSrc); grunt.config(['copy', 'dynamic', 'src'], copySrc); grunt.config(['cssjanus', 'dynamic', 'src'], copySrc); }); };