Recommended before reading this: How to use Webpack and How to use Vue without Webpack.
Combining Vue and Webpack is very powerful. It allows you to create .vue
file that contains HTML, JS, and CSS in one place.
Wait, that sounds like a bad idea! We’ve always been separating those three, why combine them now?
Continue reading to know why this is a good thing.
TABLE OF CONTENTS
1. The Problem with Splitting HTML, JS, and CSS
2. The.vue
File
3. Package and Webpack Setup
4. App Setup (Enqueue and Shortcode)
5. The Script
1. The Problem with Splitting HTML, JS, and CSS
Let’s say you saw this HTML button in your WordPress theme:
<button id="calculate"> Calculate </button>
Do you know what it does? No! It tells nothing.
You need to find the JS file that contains its event listener. Maybe it’s even mixed within thousand lines of other code!
The .vue
file solves this problem.
2. The .vue
File
<template>
<!-- HTML -->
</template>
<script>
// JS
export default {
}
</script>
<style>
/** CSS */
</style>
That is what the file looks like: You have <template>
, <script>
, and <style>
to hold the HTML, JS, and CSS respectively.
Everything is in one place. If you see a button, the listener is just a few lines away. If you want to change its color, simply write it under the style tag.
If you’re convinced, then let’s do the initial setup.
3. Package and Webpack Setup
This is the package.json
file I’m using for most of my WordPress Vue projects:
{
"name": "vue-test",
"private": true,
"dependencies": {
"@babel/polyfill": "^7.11.5",
"vue": "^2.6.12"
},
"devDependencies": {
"@babel/cli": "^7.15.7",
"@babel/core": "^7.11.6",
"@babel/preset-env": "^7.11.5",
"@vue/test-utils": "^1.1.0",
"autoprefixer": "^10.0.0",
"babel-core": "^7.0.0-bridge.0",
"browser-sync": "^2.27.5",
"browser-sync-webpack-plugin": "^2.2.2",
"css-loader": "^4.3.0",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.3.0",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jest": "^24.4.0",
"eslint-plugin-vue": "^6.2.2",
"file-loader": "^6.1.0",
"jest": "^26.4.2",
"jest-transform-stub": "^2.0.0",
"mini-css-extract-plugin": "^0.11.2",
"node-sass": "^4.14.1",
"postcss": "^8.3.9",
"postcss-loader": "^4.0.2",
"sass-loader": "^10.0.2",
"url-loader": "^4.1.0",
"vue-jest": "^3.0.7",
"vue-loader": "^15.9.3",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.12",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12"
},
"scripts": {
"build": "webpack --mode production",
"dev": "webpack --mode development --watch",
"test": "jest"
},
"browserslist": [
"last 2 versions"
],
"babel": {
"presets": [
"@babel/preset-env"
]
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"vue"
],
"transform": {
"^.+\\.vue$": "vue-jest",
"^.+\\.js$": "babel-jest"
}
}
}
Then my webpack.config.js
setup:
const { VueLoaderPlugin } = require('vue-loader');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');
const path = require('path');
// Change this to fit your project structure
const jsPath= './js';
const cssPath = './css';
const outputPath = 'dist';
const localDomain = 'http://mysite.local';
module.exports = {
entry: {
'calc-app': `${jsPath}/calc-app.js`,
},
output: {
path: path.resolve(__dirname, outputPath),
filename: '[name].js',
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
},
{
test: /\.s?[c]ss$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader',
],
},
{
test: /\.sass$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'sass-loader',
options: {
sassOptions: { indentedSyntax: true },
},
},
],
},
{
test: /\.(jpg|jpeg|png|gif|woff|woff2|eot|ttf|svg)$/i,
use: 'url-loader?limit=2048',
},
],
},
plugins: [
new VueLoaderPlugin(),
new BrowserSyncPlugin({
proxy: localDomain,
files: [`${outputPath}/*.css`],
injectCss: true,
}, {
reload: false,
}),
new MiniCssExtractPlugin({
filename: '[name].css',
}),
],
resolve: {
alias: { vue: 'vue/dist/vue.esm.js' },
},
};
After creating those two files, run the command to install the packages. Read our Webpack tutorial if you don’t know how.
4. App Setup (Enqueue and Shortcode)
We will output the Vue app using shortcode.
I know shortcode is archaic, but it’s still the quickest way to insert a custom code into whatever page builder you’re using. It also lets us enqueue the Vue script only when it’s used.
For example, we want to create a cost calculator:
add_action('wp_enqueue_scripts', 'my_enqueue_cost_calculator');
add_shortcode('cost-calculator', 'my_shortcode_cost_calculator');
function my_enqueue_cost_calculator() {
$dir = get_stylesheet_directory_uri() . '/dist';
wp_register_style('calc-app', $dir . '/calc-app.css', [], '');
wp_register_script('calc-app', $dir . '/calc-app.js', [], '', false);
}
function my_shortcode_cost_calculator($atts, $content = null) {
wp_enqueue_style('calc-app');
wp_enqueue_script('calc-app');
// Assume this is taken from get_posts() or from API
$items = [
[ 'id' => 0, 'name' => 'Fried Rice', 'price' => 8 ],
[ 'id' => 1, 'name' => 'Dumpling', 'price' => 5 ],
[ 'id' => 2, 'name' => 'Tea', 'price' => 2 ],
];
$items_json = json_encode($items);
return "<div id='cost-calculator'>
<cost-calculator data-items='{$items_json}' />
</div>";
}
5. The Script
import Vue from 'vue';
import CostCalculator from './CostCalculator.vue';
function onReady() {
const app = new Vue({
el: '#cost-calculator',
components: {
'cost-calculator': CostCalculator,
},
});
}
document.addEventListener('DOMContentLoaded', onReady);
First, we import Vue, so we don’t need to enqueue it separately.
After that, we import the .vue
file that we will register as a component.
Then, we add a ready listener that will convert #cost-calculator
(that we echoed using shortcode) into a Vue app.
Now let’s take a look at CostCalculator.vue
:
<template>
<div class="cost-calculator">
<ul>
<li v-for="i in items" :key="i.name">
<strong>{{ i.name }}</strong>
<span>${{ i.price }}</span>
<a class="button" @click="addToCart(i)">Buy This</a>
</li>
</ul>
<h2>Total Cost: ${{ total }}</h2>
</div>
</template>
<script>
export default {
props: {
dataItems: {
type: [String],
required: true,
},
},
data() {
return {
total: 0,
items: [],
};
},
created() {
this.items = JSON.parse(this.$props.dataItems);
},
methods: {
addToCart(item) {
this.total += item.price;
},
},
};
</script>
<style lang="sass">
.cost-calculator
ul
display: flex
flex-direction: column
row-gap: 1rem
list-style-type: none
padding: 0
li
display: flex
column-gap: 1rem
.button
display: inline-flex
align-items: center
padding: 0.25rem 0.5rem
font-size: 0.75rem
</style>
As you can see, everything is in one place.
If you’re wondering what is addToCart()
function, the answer is just a few lines below it.
If you want to change the button styling, it’s just under the style tag. Additionally, we can use Sass by adding lang="sass"
in the style tag.
Conclusion
Hopefully, this article will encourage you to try using Vue in WordPress. It took me months to figure out this workflow because there aren’t many resources about it.
Vue helps immensely in creating an interactive section of your website. But not everything has to be Vue. Simple stuff like a menu toggle is better done with jQuery or plain JavaScript.
Also if you’re curious about my ESLint setup, get it here.
If you have any question, feel free to comment below.