WordPress plugin dependencies with Composer and Grunt
August 22, 2015
Using Composer and Grunt to manage WordPress plugin dependencies.
The problem
I use Composer to manage any PHP library dependencies and writing mostly WordPress plugins and themes this means I often find myself conflicted in the following scenario:
I'm developing plugin B that depends on plugin A.
The first approach is to add a message, either in the readme file or as an admin notice, stating that plugin B will require plugin A to work; the user installing plugin B will then install plugin A manually and re-activate plugin B.
A second approach might be to use some plugin solution or a library solution to the problem.
A third approach, the one I use, is to use Composer to pull in the required WordPress plugin inside the current one, an embedding of sorts. This latter approach will require each dependency plugin to declare a composer.json
file or have the dependent plugin ("B") use the Composer package option to provide a virtual one.
The limits of the latter possibility are few but those dictated by common sense: embedding BuddyPress in a plugin might not be the best course of action.
Composer update
When I download a plugin I expect it to work out of the box.
This means that distributing a plugin that will require the user to run composer update
from the root folder is not the best of approaches.
I will run composer update
locally and make sure all of the plugins fundamental dependencies are there in their updated and "lighter" version.
The following composer.json
file is taken from the Restricted Content framework plugin
{
"name": "lucatume/tad-restricted-content",
"type": "wordpress-plugin",
"description": "A WordPress content restriction framework",
"license": "GPL 2.0",
"authors": [
{
"name": "Luca Tumedei",
"email": "luca@theaveragedev.com"
}
],
"minimum-stability": "dev",
"repositories": [
{
"type": "git",
"url": "https://github.com/lucatume/wp-browser.git"
},
{
"type": "git",
"url": "https://github.com/lucatume/tad-reschedule.git"
},
{
"type": "git",
"url": "https://github.com/WebDevStudios/CMB2.git"
}
],
"require": {
"composer/installers": "~1.0",
"xrstf/composer-php52": "1.*",
"lucatume/tad-reschedule": "~1.0",
"webdevstudios/cmb2": "~2.1"
},
"extra": {
"installer-paths": {
"vendor/{$vendor}/{$name}": [
"lucatume/tad-reschedule",
"webdevstudios/cmb2"
]
}
},
"require-dev": {
"lucatume/wp-browser": "dev-master",
"lucatume/function-mocker": "dev-master"
},
"autoload": {
"psr-0": {
"trc_": "src/"
},
"files": [
"vendor/webdevstudios/cmb2/init.php",
"vendor/lucatume/tad-reschedule/tad-reschedule.php"
]
},
"scripts": {
"post-install-cmd": [
"xrstf\\Composer52\\Generator::onPostInstallCmd",
"grunt after-composer-update --verbose"
],
"post-update-cmd": [
"xrstf\\Composer52\\Generator::onPostInstallCmd",
"grunt after-composer-update --verbose"
],
"post-autoload-dump": [
"xrstf\\Composer52\\Generator::onPostInstallCmd",
"grunt after-composer-update --verbose"
]
}
}
I'm taking advantage of any possibility Composer can offer and I can use in a PHP 5.2
project to autoload the embedded plugins bootstrap files, init.php
from Custom Meta Boxes 2 and the tad-reschedule.php
file from tad-reschedule utility plugin as well as the plugin classes themselves.
In the repositories
section of the file I'm specifying I will pull in the latest version directly from GitHub for both and that means I will get a lot of folders and files I do not need, first among them the .git
folder, when running compser update
.
Grunt to the rescue
In the last lines I'm creating PHP 5.2
compatible autoload files and running a Grunt task after that.
The plugin npm
dependencies are specified in the package.json
file
{
"name": "tad-restricted-content",
"version": "0.0.10",
"description": "A WordPress content restriction framework.",
"main": "tad-restricted-content.php",
"repository": {
"type": "git",
"url": "git+https://lucatume@github.com/lucatume/tad-content-restriction.git"
},
"keywords": [
"wordpress",
"plugin",
"content",
"restriction"
],
"author": "Luca Tumedeie <luca@theAverageDev.com>",
"license": "GPL-2.0",
"bugs": {
"url": "https://github.com/lucatume/tad-content-restriction/issues"
},
"homepage": "https://github.com/lucatume/tad-content-restriction#readme",
"devDependencies": {
"grunt": "^0.4.5",
"grunt-contrib-clean": "^0.6.0",
"grunt-git": "^0.3.5"
}
}
In the Gruntfile.js
file I'm using the tasks to remove nested .git
folders and other files and folders that will not be needed in a distributed version of the plugin like the tests
folder, Codeception, PHPUnit and Travis configuration files.
After that the task will add the clean dependencies to the plugin repository.
module.exports = function ( grunt ) {
var dependencies = ["vendor/lucatume/tad-reschedule/", "vendor/webdevstudios/cmb2/"],
delete_patterns = [".git/**", "tests/**", ".gitignore", "/Gruntfile.js", "/example-functions.php", "composer.{json,lock}", "{.travis,.scrutinizer,codeception*,}.yml", "coverage.clover", "phpunit.xml.dist"],
clean_dist_patterns = [],
git_add_patterns = [];
for ( i = 0; i < dependencies.length; i++ ) {
git_add_patterns.push( dependencies[i] + '**' );
for ( k = 0; k < delete_patterns.length; k++ ) {
clean_dist_patterns.push( dependencies[i] + delete_patterns[k] );
}
}
grunt.initConfig( {
pkg: grunt.file.readJSON( 'package.json' ),
clean: {
dist: clean_dist_patterns
},
gitadd: {
dist: {
options: {
verbose: true,
force: true
},
files: {
src: git_add_patterns
}
}
}
} );
grunt.loadNpmTasks( 'grunt-contrib-clean' );
grunt.loadNpmTasks( 'grunt-git' );
grunt.registerTask( 'after-composer-update', ['clean:dist', 'gitadd:dist'] );
};
The end result is that running composer update
will:
- update the plugin dependencies, both libraries and other plugins
- clean a subset of those dependencies that are needed in the plugin distribution version removing files and folders that would not be used
- force add those dependencies to the plugin repository
It's not covering everything I might ever need but it's a starting point.