module.exports = function(grunt) { var path = require('path'); var SOURCE_DIR = 'wp/'; var BUILD_DIR = 'build/'; // Load tasks. require('matchdep').filterDev('grunt-*').forEach( grunt.loadNpmTasks ); // Project configuration. grunt.initConfig({ autoprefixer: { options: { browsers: ['Android >= 2.1', 'Chrome >= 21', 'Explorer >= 7', 'Firefox >= 17', 'Opera >= 12.1', 'Safari >= 6.0'] }, 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/version.php' // Exclude version.php ], dest: BUILD_DIR }, { src: 'wp-config-sample.php', dest: BUILD_DIR } ] }, version: { options: { processContent: function( src ) { return src.replace( /^(\$wp_version.+?)-src';/m, '$1\';' ); } }, files: [ { 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 ) { return src.replace( /([^\.])*\.\.\/src/ig , '/../build' ); } } } }, sass: { colors: { expand: true, cwd: SOURCE_DIR, dest: BUILD_DIR, ext: '.css', src: ['wp-admin/css/colors/*/colors.scss'], options: { outputStyle: 'expanded' } } }, cssmin: { core: { expand: true, cwd: SOURCE_DIR, dest: BUILD_DIR, ext: '.min.css', src: [ 'wp-admin/css/*.css', 'wp-includes/css/*.css', // Exceptions '!wp-admin/css/farbtastic.css' ] }, rtl: { expand: true, cwd: BUILD_DIR, dest: BUILD_DIR, ext: '.min.css', src: [ 'wp-admin/css/*-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 }, 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/qunit.js', '!tests/qunit/editor/**' ], options: grunt.file.readJSON('tests/qunit/.jshintrc') }, themes: { expand: true, cwd: SOURCE_DIR + 'wp-content/themes', src: [ '**/*.js', '!twenty{eleven,twelve,thirteen}/**', // Third party scripts '!twentyfourteen/js/html5.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-admin/js/iris.min.js', '!wp-includes/js/backbone*.js', '!wp-includes/js/swfobject.js', '!wp-includes/js/underscore*.js', '!wp-includes/js/zxcvbn.min.js', '!wp-includes/js/colorpicker.js', '!wp-includes/js/hoverIntent.js', '!wp-includes/js/json2.js', '!wp-includes/js/tw-sack.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 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; } } }, 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'] } }, 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-includes/js/backbone.min.js', '!wp-includes/js/swfobject.js', '!wp-includes/js/underscore.min.js', '!wp-includes/js/zxcvbn.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 + '/**/*.js', '!' + BUILD_DIR + '/wp-content/**/*.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 } }, 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. // Copy task. grunt.registerTask('copy:all', ['copy:files', 'copy:version']); // RTL task. grunt.registerTask('rtl', ['cssjanus:core', 'cssjanus:colors']); // Color schemes task. grunt.registerTask('colors', ['sass:colors', 'autoprefixer:colors']); // Pre-commit task. grunt.registerTask('precommit', 'Runs front-end dev/test tasks in preparation for a commit.', ['autoprefixer:core', 'imagemin:core', 'jshint', 'qunit:compiled']); // Build task. grunt.registerTask('build', ['clean:all', 'copy:all', 'cssmin:core', 'colors', 'rtl', 'cssmin:rtl', 'cssmin:colors', 'uglify:core', 'concat:tinymce', 'compress:tinymce', 'clean:tinymce', 'jsvalidate:build']); // Testing tasks. grunt.registerMultiTask('phpunit', 'Runs PHPUnit tests, including the ajax 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']); // 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); }); };