Using Grunt with grunt-replace to replace handlebars/mustache with package.json variables
Recently while testing out some Node.js modules for Grunt I came across grunt-replace, and my first though was, this would be great for setting up boilerplate PHP files for my WordPress plugins. Because I already set the name, slug, version, and other details in the package.json file, it would be great to use a template I could create for PHP files and just use Grunt to take the details from package.json and setup my base plugin files using a template syntax like Handlebars or Mustache. Luckily for me it worked out perfectly, but did require a little bit of troubleshooting and head scratching, but here’s how to make it work …
Below you will find an example package.json file. One thing to note, name has to be lowercase without spaces, I decided to just use that as the WordPress slug, but you could always add slug if you want.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ "name": "smyles-plugin", "title": "sMyles Plugin", "acronym": "sp", "version": "1.0.0", "homepage": "http://plugins.smyl.es", "author_homepage": "http://smyl.es", "class": "sMyles_Plugin", "CAP_NAME": "SMYLES_PLUGIN", "main": "Gruntfile.js", "devDependencies": { "grunt": "~0.4.2", "grunt-replace": "~0.7.8" }, "engines": { "node": ">=0.8.0", "npm": ">=1.1.0" } } |
As you can see above I added a few custom variables that will be used in the templates below. You can add as many as you want, it all depends on your setup. You will need to make sure that grunt-replace is in devDependencies, this can be done easily by running npm install grunt-replace --save-dev
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
'use strict'; module.exports = function (grunt) { var pkg = grunt.file.readJSON( 'package.json' ); grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), replace: { dist: { options: { patterns: [ { match: /({{(\w*)}})/g, replacement: function ( match, offset, string, source, target ) { return pkg[string]; } } ] }, files: [ { expand: true, flatten: true, src: ['smyles-plugin.php'], dest: 'build/' } ] } } }); grunt.loadNpmTasks( 'grunt-replace' ); grunt.registerTask('setup', ['replace']); }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?php /** * Plugin Name: {{title}} * Plugin URI: {{homepage}} * Description: * Author: Myles McNamara * Author URI: {{author_homepage}} * Version: {{version}} * Text Domain: {{plugin_text_domain}} */ Class {{class}} { const PLUGIN_SLUG = '{{name}}'; const PROD_ID = '{{title}}'; const VERSION = '{{version}}'; function __construct() { if ( ! defined( '{{CAP_NAME}}' ) ) define( '{{CAP_NAME}}', {{class}}::VERSION ); add_action( 'admin_enqueue_scripts', array( $this, 'admin_assets' ) ); } public function admin_assets() { wp_enqueue_style( '{{acronym}}-styles', {{CAP_NAME}}_PLUGIN_URL . '/assets/css/{{acronym}}.min.css' ); } } |
Above is an example plugin file that we will be replacing the Handlebars/Mustache template variables with our details from the package.json file. I removed as much as I could from the PHP file to keep it as simple as possible. Now we will go through each section to explain how it works.
The package.json file is pretty self explanatory, just setup whatever variables you want to use, and Grunt will pull the details from that file and replace them in your PHP file.
The Gruntfile.js is the meat of everything, but the main area you need to focus on is replacement where the real magic happens…
1 2 3 4 |
match: /({{(\w*)}})/g, replacement: function ( match, offset, string, source, target ) { return pkg[string]; } |
The first line match uses RegEx to match the Handlebars/Mustache template variables, and replacement uses an anonymous function to return the data for replacement.
1 |
match: /({{(\w*)}})/g, |
Match will do just that, match (and group) all {{something}} , it will also group just the wording inside which in this case is something .
1 2 3 |
replacement: function ( match, offset, string, source, target ) { return pkg[string]; } |
The replacement section calls an anonymous function with all the attributes you see above, but we will only need a couple of them. If you’re familiar with Grunt you already know that normally you could just use something like this to reference the object and not need to use an anonymous function:
1 |
replacement: '<%= pkg.name %>' |
Unfortunately this will not work for our case as that is an internal Grunt template string and we need to get the property value of the object based off a variable (which is obtained via RegEx). So, the solution I found to this problem was to simply use an anonymous function, and then inside that anonymous function, reference the pkg object and the property based off our variable, which in this case is string .
The one thing I want to point out is that I had to load the pkg object at the top of the Grunt file in order to reference it in the replace anonymous function. You will notice that pkg is defined right below the module.exports and right above the grunt.initConfig, this is required in order to work correctly.
1 2 3 4 5 |
module.exports = function (grunt) { var pkg = grunt.file.readJSON( 'package.json' ); grunt.initConfig({ |
That’s all there is to it! You should now have a new file under the /build directory that has been updated with all of your details from the package.json file. Profit!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?php /** * Plugin Name: sMyles Plugin * Plugin URI: http://plugins.smyl.es * Description: * Author: Myles McNamara * Author URI: http://smyl.es * Version: 1.0.0 * Text Domain: undefined */ Class sMyles_Plugin { const PLUGIN_SLUG = 'smyles-plugin'; const PROD_ID = 'sMyles Plugin'; const VERSION = '1.0.0'; function __construct() { if ( ! defined( 'SMYLES_PLUGIN' ) ) define( 'SMYLES_PLUGIN', sMyles_Plugin::VERSION ); add_action( 'admin_enqueue_scripts', array( $this, 'admin_assets' ) ); } public function admin_assets() { wp_enqueue_style( 'sp-styles', SMYLES_PLUGIN_PLUGIN_URL . '/assets/css/sp.min.css' ); } } |