Johnathan.org

On Developing a WordPress Theme

A few weeks ago I started the process of moving this blog over to WordPress from Ghost—and I talked about why I did that in a previous post—so now that I’ve made the change, the next natural step felt like talking about how I developed a theme for the first time.

Yep, I’ve never done this before, and it turned out pretty well… I think.

Local Environment

In my opinion, maintaining a proper working development environment is one of the hardest things to do. Over time it can experience something I like to call “dev rot.” To make this as easy as possible, I invoked the assistance from a tool by the great folks over at Flywheel called Local. Local does one thing: let you build, maintain, and tear down WordPress development environments on a whim. It runs a Virtualbox-powered VM under the hood and if you use the Flywheel managed WordPress hosting service, allows you to push your development environment straight up to them for one-click production pushes. It’s a fantastic tool and keeps things super clean.

Structure

One thing I tried to figure out while I was developing was how I wanted to structure all the code. I came up with this rough directory structure. Some of these folders are unique to my current theme, but the general idea is pretty clear:

theme
├── app
│   ├── assets
│   │   ├── css
│   │   │   └── maps
│   │   ├── fonts
│   │   ├── images
│   │   ├── js
│   │   │   └── fa
│   │   ├── scss
│   │   │   ├── animations
│   │   │   ├── base
│   │   │   ├── layout
│   │   │   └── module
│   │   └── svg
│   └── js
├── gulp
│   └── tasks
└── johnathan-org
└── assets
├── css
│   └── maps
├── fonts
├── images
├── js
│   └── fa
└── svg

within the theme directory, app houses the code, gulp are where my automation tasks reside, and johnathan-org is my distribution directory. This would be unique to the theme name. The app name is a carryover from when I developed the theme for Ghost. In fact, a lot of this code was carried over and reused at least partially if not completely as is. The css directory is generated when gulp watch is run, something I’ll cover in a bit.

Automation Tasks

This was the hardest part of developing because I had never used any kind of serious development tools for assembling a front-end design of any kind. I discovered Gulp and all the things it can do and was instantly hooked. Here’s the part where I talk about all of my gulp files.

Gulp

My base gulpfile.js only contains a couple lines of code:

// /gulpfile.js
var requireDir = require('require-dir');

// Require all tasks in gulp/tasks, including subfolders
requireDir('./gulp/tasks', {recurse: true});

The first being bringing in require-dir in order to do the second thing, suck in all the tasks in the gulp/tasks directory. Within this directory, I have two main tasks: gulp watch and gulp dist. gulp watch triggers a few different sub tasks:

// gulp/tasks/watch.js

var gulp = require('gulp');
var runSequence = require('run-sequence');

gulp.task('watch', function (callback) {
runSequence(
'sass:compile',
'copy:dev',
'browsersync:production',
() => {
gulp.watch('app/**/*.php', () => {
runSequence(
'copy:dev',
'browsersync:reload'
);
});
gulp.watch('app/assets/scss/**/*.scss', () => {
runSequence(
'sass:compile',
'copy:dev',
'browsersync:reload'
);
});
gulp.watch('app/assets/js/**/*.js', () => {
runSequence(
'copy:dev',
'browsersync:reload'
);
});
return callback;
}
);
});
// gulp/tasks/dist.js

var gulp = require('gulp');
var runSequence = require('run-sequence');

gulp.task('dist', function (callback) {
runSequence(
'delete',
'copy',
'sass:compile',
[
'optimize:css',
'optimize:js',
'optimize:images'
],
'zip',
'vrev',
callback
);
});

It probably goes without saying that these tasks aren’t probably the most efficient or there’s a better way to do all that I’m doing, but the fun thing about developing anything: there’s more than way to do everything.

Here are those individual task files:

// browsersync.js

var gulp = require('gulp');
var browserSync = require('browser-sync').create();
var proxy = 'johnathan-org-staging:80';

gulp.task('browsersync:production', function (callback) {
browserSync.init({
proxy: proxy
});

callback();
});

gulp.task('browsersync:reload', function (callback) {
browserSync.reload();
callback();
});
// copy.js
var gulp = require('gulp');
var files = ['app/**/*.php',
'app/*.txt',
'app/screenshot.png',
'app/browserconfig.xml',
'app/assets/css/**/*',
'app/assets/fonts/**/*',
'app/style.css',
'app/assets/js/*',
'app/assets/images/*',
'app/assets/svg/*'];
var base = './app';
var prodDest = './johnathan-org';
var devDest = '/Users/jlyman/Local\ Sites/johnathanorg-staging/app/public/wp-content/themes/johnathan-org';

gulp.task('copy', () => {
return gulp
.src(files, {base: base})
.pipe(gulp.dest(prodDest));
});

gulp.task('copy:dev', () => {
return gulp
.src(files, {base: base})
.pipe(gulp.dest(devDest));
});
// delete.js
var gulp = require('gulp');
var del = require('del');

gulp.task('delete', () => {
del.sync('johnathan-org');
});
// gzip.js
var gulp = require('gulp');
var gzip = require('gulp-gzip');

gulp.task('gzip', () => {
return gulp
.src('johnathan-org/assets/**/*.{css,js}')
.pipe(gzip())
.pipe(gulp.dest('johnathan-org/assets'));
});
// optimize.js
var gulp = require('gulp');
var cssnano = require('gulp-cssnano');
var imagemin = require('gulp-imagemin');
var uglify = require('gulp-uglify');
var size = require('gulp-size');

gulp.task('optimize:css', () => {
return gulp
.src('dist/assets/css/**/*.css')
.pipe(cssnano())
.pipe(gulp.dest('johnathan-org/assets/css'))
.pipe(size({
showFiles: true
}));
});

gulp.task('optimize:images', () => {
return gulp
.src('app/assets/images/**/*.{jpg,jpeg,png,gif,svg}')
.pipe(imagemin())
.pipe(gulp.dest('johnathan-org/assets/images'))
.pipe(size({
showFiles: true
}));
});

gulp.task('optimize:js', () => {
return gulp
.src('johnathan-org/assets/js/**/*.js')
.pipe(uglify())
.pipe(gulp.dest('johnathan-org/assets/js'))
.pipe(size({
showFiles: true
}));
});
// sass.js
var gulp = require('gulp');
var sass = require('gulp-sass');
var sourcemaps = require('gulp-sourcemaps');
var clean = require('gulp-clean');
var runSequence = require('run-sequence');

gulp.task('sass:compile', () => {
return gulp
.src('app/assets/scss/**/*.scss')
.pipe(sourcemaps.init())
.pipe(sass().on('error', sass.logError))
.pipe(sourcemaps.write('./maps'))
.pipe(gulp.dest('app/assets/css'));
});

I created vrev to be able to use wp_enqueue_* and be able to cite version numbers. To prevent any kind of caching locally, the implementations use PHP’s rand() function. In production, I need to set it to the version within my root package.json, which is where this task comes into play.

// version-rev.js
var gulp = require('gulp');
var replace = require('gulp-replace');
var fs = require('fs');
var pJson = JSON.parse(fs.readFileSync('./package.json'));
var fnFile = 'johnathan-org/functions.php';
var vString = "'" + pJson.version + "'";

gulp.task('vrev', () => {
return gulp
.src(fnFile)
.pipe(replace('rand(100000,999999)', vString))
.pipe(gulp.dest('johnathan-org'));
});
// zip.js
var gulp = require('gulp');
var zip = require('gulp-zip');
var size = require('gulp-size');

gulp.task('zip', () => {
return gulp
.src('johnathan-org/**/*')
.pipe(zip('johnathan-org.zip'))
.pipe(gulp.dest('./'))
.pipe(size());
});

And that’s about it!

The biggest thing I took away from this whole process was how quickly I can make changes now and be able to iterate on previous work without a huge amount of burden or overhead.

(permalink)

Johnathan Lyman
Kenmore, WA,
United States
 
blogging, design, technology, software, development, gaming, photography