A Comical Guide to Webpack, Vue.js 2, Gulp and WordPress [Tutorial]
Building WordPress themes and plugins is a big market and it gets a lot of people into web development, so I named this post like I did, but it really works for most CMS, no matter if you want to write a theme for WordPress, Drupal, GHOST, Magento or what ever else is out there. I'm going to show a simple webpack config that helps you transpile your JavaScript and develop your themes as usual.
TLDR; Here's how to do stuff and me rambling in between. Use one of these links to get to your preferred headline:
- Installing Webpack
- Your WordPress Theme and Webpack
- Including Vue.js in your WordPress Theme
- Loading the Vue Component in WordPress
- Combining Gulp and Webpack
Now you might ask:
Jonathan, I LOVE WordPress, what is this Webpack nonsense?Well, Webpack is a module bundler for JavaScript! It basically is like an amazing kitchen machine, you can pour all your ingredients in it (including dependencies like angular, react or vue) and out comes your delicious JavaScript cake you can ship off to all the clients (browsers)! It can transform ES6, Typescript and tons of other cool things.
Alright Jonathan, but JavaScript is FUBAR! WordPress is GLORIOUS PHP!Wrong, friend! The backend of WordPress is written in PHP, but since you found this blog post, you're probably building a theme and a theme probably also contains some JavaScript code to have some client side validation or pretty wooshes and swooshes left, right and center! So shut up with your language affinity and read how easy JavaScript will be integrated into your theme or plugin with the divine power of WEBPACK!
Installing Webpack
Let's assume you want to build a WordPress theme (if you're not, just pretend and secretly adjust the instructions):cd /your/site/wp-content/themes
mkdir webpack-wordpress # Yes, that's my theme name
npm init -y
npm install --save-dev webpack
Now webpack is installed, but you can also install it globally to be able to run it from anywhere:
npm install -g webpack
Your WordPress Theme and Webpack
Alright, if you have a fantastic WordPress theme already, your theme folder probably looks a bit like this:├── 404.php
├── archive.php
├── assets
├── comments.php
├── footer.php
├── front-page.php
├── functions.php
├── header.php
├── inc
├── index.php
├── page.php
├── README.txt
├──sidebar.php
├── single.php
├── style.css
└── template-parts
Maybe it looks all different, the important thing is that we need to create a directory where all your JavaScript is going to live. I know JavaScript is in-animate, but we developers like to romantically think of programming languages as having the gender we're attracted to. Like a captain naming a ship.
Let's go ahead and create a home for them:
mkdir js
touch js/index.js
Next, let's create a webpack.config.js
with the following contents:
const path = require('path');
module.exports = {
entry: './js/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
Alright, we need some JavaScript, ANY will do. We just need to check that things work as expected, save this as $yourtheme/js/index.js
console.log('WebPack is pretty neat! Suck it, haters.');
Alright, so now we have hit a crossroad. If you indeed have installed webpack with the -g flag, you can simply type:
webpack
If not, you'll need to type out the entire copy this line:
./node_modules/.bin/webpack
Now, if I haven't made any mistake writing this and you were not too incapable to copy paste the code (or apply it to your actually meaningful project), you should see the following output:
Hash: 6910732e5b7367b6bc8f
Version: webpack 3.4.1
Time: 59ms
Asset Size Chunks Chunk Names
bundle.js 2.51 kB 0 [emitted] main
[0] ./js/index.js 41 bytes {0} [built]
Whoa! What's that! If you check your file tree, you will see that there now is a file inside the freshly created directory dist
called bundle.js
. Don't let your cat read this.
Ok, what does it contain? (don't read this, it's not important)
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
console.log('Wepack is pretty neat.');
/***/ })
/******/ ]);
Alright. I will admit, this isn't great. Webpack took your super nice one-liner and turned it into a 184x times larger file, doing all sorts of things! It's basically a complete nightmare!
Anyways, usually that overhead doesn't mean anything if you have a 40KB framework you're throwing at your JavaScript anyways, so let's go ahead and not worry about what it does to a single line of JavaScript. Trust me.
The point I'm trying to make is: We now have the transpiled JavaScript file in dist/bundle.js
. Great. Doesn't do us any good until we actually try to include a dependency, library or framework. Or if you shall be so ancient and dig up coffee script from its grave. Or go all type-mental and use TypeScript.
Let's do some of that!
Including Vue.js in your WordPress Theme
Alright, now what does it take to get a cool JavaScript framework going with your awesome WordPress theme? One thing that's not going to work (or at least probably not super well, unless you're going to build it so it only consumes the WordPress REST API) is using the webpack dev server and HMR (hot module reload).Ok, so what we can do is to tear out the part that does the builds from one of the multiple and amazing vue webpack templates.
In order for the following to work, we need a few more dependencies:
npm install --save babel-loader vue-loader babel-core vue vue-template-compiler
So this is what I quickly grabbed for my WordPress and Vue webpack config:
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: './js/',
output: {
path: path.resolve(__dirname, './dist/js'),
publicPath: './dist/js',
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
'scss': 'vue-style-loader!css-loader!sass-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'
}
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
]
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.common.js'
}
},
performance: {
hints: false
},
devtool: '#eval-source-map',
};
This basically does the same as before but it supports ES6 / ES2015 and Vue JS (including sweet single file components).
So let's ship up some example files and give it a run:
This is our entry point, it will mount our Vue app on the #app
element.
$your_theme/js/index.js:
import Vue from 'vue';
import hello from './components/hello.vue';
new Vue({
el: '#app',
components: {
'hello': hello
}
});
This is our component, it serves a specific purpose. In this instance it's emitting a quote from a fictional character of a sci-fi series.
$your_theme/js/components/hello.vue:
<template>
<div class="hello">
<h1>Hello There!</h1>
<blockquote>We humans are alone in this world for a reason. We've murdered and butchered anything that challenged our primacy</blockquote>
<p>-Dr. Robert Ford</p>
</div>
</template>
<script>
export default {
created(){
console.log('created!');
}
}
</script>
Ok, check engines: check.
Ok, check brakes: irrelevant, let's go.
webpack
The output your eyes will gaze upon should now be:
Hash: f0a628d7aab59a1129a6
Version: webpack 3.4.1
Time: 789ms
Asset Size Chunks Chunk Names
bundle.js 759 kB 0 [emitted] [big] main
[0] ./js/index.js 135 bytes {0} [built]
[3] (webpack)/buildin/global.js 509 bytes {0} [built]
+ 6 hidden modules
(If not please leave a comment or flame me on twitter)
In order to quickly test our results, let's use this example HTML file, and plop it into a webserver or just use serve (in your terminal, for installation:npm install -g serve
):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dummy File</title>
</head>
<body>
<div id="app">
<hello></hello>
</div>
<script src="/js/bundle.js"></script>
</body>
</html>
Now if you run serve dist
and open your browser at http://localhost:5000
, you shall see:
Loading the Vue Component in WordPress
Is that not exciting?! If it isn't, I don't know what excites you at all.Ok, but what's up with WordPress in all this? Does it ever make an appearance? The time is now. In your themes functions.php
you can just add the following:
add_action( 'wp_enqueue_scripts', load_theme_assets );
wp_enqueue_script( 'my-bundle', get_stylesheet_directory_uri() . '/dist/js/bundle.js', [], false, true );
and BOOM WordPress will load your amazing vue-web-packed JavaScript file!
Combining Gulp and Webpack
Ok, ok, I admit, this wasn't all that fantastic. The great thing is, it gets better!If you don't want to run a console command every (insert god) damn time that you change a JavaScript file, just use webpack -w
and it will watch for changes.
If you already have a gulp file, because you slurp tasty coffee that's been through jungle cat bowels already, do not despair, you can just enslave webpacks power from within gulp using THIS TASK:
var gulp = require('gulp');
var webpack = require('webpack');
var webpackConfig = require('./webpack.config.js');
var webpackRun = webpack(webpackConfig);
gulp.task('webpack', function(done) {
webpackRun.run(function(err, stats) {
if(err) {
console.log('Error', err);
}
else {
console.log(stats.toString());
}
done();
});
});
// and how to watch for stuff in gulp:
gulp.task('default', ['sass', 'webpack'], function(){
gulp.watch('scss/**/*.scss', ['sass'])
gulp.watch('js/**/*.js', ['webpack'])
gulp.watch('js/**/*.vue', ['webpack'])
});
Summary
Alright that's it! We've learned a ton of stuff about how to compile your cool JavaScript code and combine your existinggulp
builds with webpack
. I only included [vue.js][] because I had the example lying around anyways, so if you have some cool webpack configs you want to share, please do!