In the second part of our tutorial, we’ll be talking about the theme’s initial folder/file structure, installing required Node packages, configuring Webpack and setting up a Node development server with hot reloading. I’ll show you how to properly bootstrap a Redux-powered WordPress theme. If you want to see how the project will look at the end of this article, look at its GitHub repository (branch 1_structure_node_webpack
).
Tutorial navigation
- React single-page WordPress REST API theme tutorial
- React WP theme: structure, Node packages and Webpack (current)
- React WP theme: Smart vs Dumb components + React Router
- React WP theme: Creating dumb components
.
. to be written
.
Theme folder structure
Create a basic WordPress theme: index.php
, style.css
, header.php
, footer.php
. We don’t need anything special, just to have a valid theme.
Now, create these files in the root of our theme:
- `package.json` — manage npm packages (Node) (similar to composer.json)
- `webpack.config.js` — main configuration file for Webpack
- `server.js` — configuration file for our Node dev server
We also need to create an src/
folder where the client-side app will live. We won’t writing much custom CSS due to using the excellent Bootstrap framework, but we should organize the Bootstrap’s CSS a little so create a folder css/
where we’ll put it.

Installing Node packages
Before we can start writing Webpack configuration, we need to install some Node packages. Keep in mind that React, Redux, and all the other libraries are always in development so the version we’ll be using in this tutorial will likely change while we complete it.
Our main dependencies are React and Redux. Although Redux plays nicely with React, they are not connected by default. That’s why we also need a react-redux
library which has the bindings from React to Redux. The next thing we need is React-Router, a pretty cool and smallish library for routing in React. In order to have an HTML5-based page transitions (history.pushState), we also need a library called history
, which is then used as the “history driver” in React-Router. Aaand, that’s it!
Well, I lied. What about development dependencies? Yeah, we need them too. The most important one is Webpack for building and outputting the complete app. Without loaders and plugins, Webpack is (almost) of no use — we need to install them:
- `babel-loader`: For processing React JSX and ES6. Install also `babel-core`.
- `css-loader`: It allows us to load CSS like other libraries e.g. `import ‘../css/bootstrap.css‘;`.
- `extract-text-webpack-plugin`: Instead of having the CSS in JavaScript, Webpack outputs it to a separate file.
- `file-loader`: For loading static files such as images.
- `style-loader`: What’s the difference between this and `css-loader`? I honestly don’t know.
- `html-webpack-plugin`: It generates the main HTML file with our app and CSS, useful for development purposes.
- `react-hot-loader`: One of the coolest things ever. We no longer have to refresh the page when changing a React component (or a Redux reducer) — it will automagically refresh for us on saving the modified file.
- `webpack-dev-server`: A tiny server for development purposes.
Sigh, that’s a lot of new things to grasp. I say: Have no worries for now. You’ll see how to work with them along the way. What is more, you don’t really need to understand how each one of them works — just be able to use it.
One last thing is to add a few scripts into our packages.json
file as shortcuts to start the development server, clean the dist/
folder (where our app is built into) and build the application through Webpack. I’m including the entire file here. You can safely ignore things I didn’t mention.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "Lexi-WP-Theme", | |
"version": "0.0.1", | |
"description": "", | |
"license": "MIT", | |
"dependencies": { | |
"history": "^1.9.0", | |
"isomorphic-fetch": "^2.1.1", | |
"react": "^0.14.0-rc1", | |
"react-redux": "^3.0.1", | |
"react-router": "1.0.0-rc1", | |
"redux": "^3.0.2", | |
"redux-thunk": "^1.0.0" | |
}, | |
"devDependencies": { | |
"babel-core": "^5.6.18", | |
"babel-loader": "^5.1.4", | |
"css-loader": "^0.18.0", | |
"extract-text-webpack-plugin": "^0.8.2", | |
"file-loader": "^0.8.4", | |
"html-webpack-plugin": "^1.6.1", | |
"react-hot-loader": "^1.3.0", | |
"style-loader": "^0.12.3", | |
"webpack": "^1.9.11", | |
"webpack-dev-server": "^1.9.0" | |
}, | |
"scripts": { | |
"clean": "rm -rf dist", | |
"build": "npm run clean && webpack", | |
"start": "npm run build && node server.js" | |
} | |
} |
Copy the file into your theme’s directory and run npm install
from within the command line. All the packages should install in a while. Alright, let’s move to more interesting topic — Webpack!
Configuring Webpack
Don’t be fooled by Webpack’s innocently-looking homepage — there’s a beast underneath it, seriously. Even Facebook (Instagram) is using it, and they’ve written a pretty decent guide on how to work with it. Give it a (brief) read, then continue here. Done? Cool, now you know why Webpack rocks.
When I saw a random wepback.config.js
Webpack configuration file for the first time, it looked like Chinese to me (for them like Russian, you know what I mean). I simply began to rewrite (not copy-paste) parts of it into my one, then running npm start
and watching it crash. Eventually, I got it working and here are my results.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var path = require('path'); | |
var webpack = require('webpack'); | |
var HtmlWebpackPlugin = require('html-webpack-plugin'); | |
var ExtractTextPlugin = require('extract-text-webpack-plugin'); | |
module.exports = { | |
devtool: 'source-map', | |
entry: [ | |
'webpack-dev-server/client?http://localhost:3000', | |
'webpack/hot/only-dev-server', | |
'./src/index' | |
], | |
output: { | |
path: path.join(__dirname, 'dist'), | |
filename: 'bundle.js', | |
publicPath: '/' | |
}, | |
plugins: [ | |
new webpack.HotModuleReplacementPlugin(), | |
new HtmlWebpackPlugin({ | |
filename: 'index.html', | |
template: './src/index.template.html', | |
inject: true | |
}), | |
new webpack.NoErrorsPlugin(), | |
new ExtractTextPlugin("style.css", { | |
allChunks: true | |
}) | |
], | |
module: { | |
loaders: [ | |
{ | |
test: /\.js$/, | |
loaders: ['react-hot', 'babel'], | |
exclude: /node_modules/, | |
include: __dirname | |
}, { | |
test: /\.css$/, | |
loader: ExtractTextPlugin.extract("style-loader", "css-loader") | |
}, { | |
test: /\.png$/, | |
loader: "url-loader?limit=100000" | |
}, { | |
test: /\.jpg$/, | |
loader: "file-loader" | |
}, { | |
test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, | |
loader: 'file-loader' | |
} | |
] | |
} | |
}; |
The first thing you’ll notice is that it’s just a pure JavaScript, which is very nice. At the beginning of the file, we require()
a few libraries. Then, we just export a JS object — the Webpack’s configuration:
- `devtool: ‘source-maps’` means that Webpack will generate source maps for our JS & CSS files.
- `entry` specifies the main (entry point) file of our web application. It will be the `./src/index.js` file. We’ll create it in the next tutorial. The other two lines (`webpack-dev-server`) configure the development server. Just change the port if you need to (default is 3000)
- `output` configures the path where Webpack should put the compiled files. We set it to the `dist/` folder and tell Webpack to put everything into the `bundle.js` file. `publicPath` just says that we are on the root path.
- `plugins` is a list of the Webpack plugins we use in our app. `HotModuleReplacementPlugin` is for the hot reloading of React/Redux code. `HtmlWebpackPlugin` generates an `index.html` file in the `dist/` folder from our HTML template in the `src/` folder (`./src/index.template.html`). The `inject: true` injects all the assets (both css and js) into the head of the index.html file.
- The `NoErrorsPlugin` has something to do with errors so that no assets with errors will output. Read more.
- Finally, the `ExtractTextPlugin` moves every imported CSS file into a single `style.css` (`allChunks: true`).
Now the loaders. A loader is a little Webpack “plugin” which knows how to process the imported assets. It’s something like a “task” in other build tools (e.g. Grunt). For us, it is enough to learn how to work with them. Let’s take the first loader and inspect it closer:
{ test: /.js$/, loaders: ['react-hot', 'babel'], exclude: /node_modules/, include: __dirname }
We can see that a using a loader is just writing a JS object with a few keys:
- `test: /.js$/` is a simple regular expression used to select files which the loader should process. In this case, this loader is applied only to files ending in the `.js` file extension.
- `loaders: [‘react-hot‘, ‘babel‘]`: Here we specify which loaders should be called on the matched files (in our case, all imported JS files). `react-hot` is used for hot reloading of our React components (discussed earlier in this article). Babel is a powerful JS compiler, enabling us to write ES6 JavaScript which gets compiled down to ES5 (supported by all modern browsers), as well as to use JSX for writing React components (which, too, gets compiled to standards JS objects).
- The `exclude: /node_modules/` instructs Webpack to ignore the `node_modules/` folder as it contains the installed libraries (we did this in the Node chapter above).
- Finally, the `include: __dirname` just says that Webpack should look for .js files also in the theme’s root folder (`__dirname` is the directory in which the currently executing script resides).
Other loaders follow the same structure, except they deal with different parts of our web app. Study them a bit and move on.
Setting up development server
The last thing we’ll gonna do in this tutorial is to set up a development server. You should already have a WordPress (PHP) server running (use EasyEngine or Ansible for that). The dev server we’ll set up here is not a PHP one, it’s a Node.js one. We need it for the hot reloading to work. It’s also easier and more pleasurable to code our web app this way and then, when we move it to the production server, use only the PHP one.
We configure the whole dev server in the server.js
file. Have a look at it:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var webpack = require('webpack'); | |
var WebpackDevServer = require('webpack-dev-server'); | |
var config = require('./webpack.config'); | |
new WebpackDevServer(webpack(config), { | |
publicPath: config.output.publicPath, | |
hot: true, | |
historyApiFallback: true, | |
stats: { | |
colors: true | |
} | |
}).listen(3000, 'localhost', function(err) { | |
if (err) { | |
console.log(err); | |
} | |
console.log('Listening at localhost:3000'); | |
}); |
In the first few lines, we import the Webpack and the Webpack-dev-server libraries. On the third line, we also import the Webpack’s configuration file that we just created earlier. We’ll need it for a few constants.
Next, we instantiate a new object WebpackDevServer
, passing to it the webpack
object and a configuration object:
- `publicPath` is set to the same value as the `publicPath` in our Webpack config file.
- `hot: true` enables the hot reloading.
- `historyApiFallback` probably means that if a user’s browser doesn’t support the HTML5 History API, it will use the “hashtag” API. But I don’t know.
Then we configure it to listen on localhost:3000
and if there’s an error, print it to the browser’s console.
Conclusion
Well, that’s it for today. We’ve learned which Node packages we’ll need for our web application to work. We also learned how to use and configure Webpack. Finally, we’ve seen how to set up a simple but useful dev server. I think it’s a lot of information for a single session, so feel free to reread the article and study the links I provided in it. Most importantly, don’t be hard on yourself when you don’t understand it all. It took me some weeks before I could write about it.
In the following article, we’ll start building the actual web app, starting with some React, React-Router, and Redux. Stay tuned!
Also, please, I beg you, ask in the comments below. I know that I didn’t go much deeply in some topics and the comments are the best way to explore them. Let’s have a nice conversation, shall we.
Well, this is all still way over my head, but then again, so was Ansible earlier in the year. Keep up the series, hopefully I won’t lose steam on my explorations in the meantime! Thanks!
LikeLike
Exactly, it’s over my head too. Except for React, I started to explore these waters just a few months ago, so I think you can learn it as well. Just don’t give up!
I think the reason why this is so complicated is that no big company has created a singular framework/workflow for building sites this way yet. It’s all just developers working on this mostly in their free time. Still too early.
On the contrary, there is the famous Angular framework from Google which has everything included. But I don’t like it that much, React is ++ 🙂
LikeLike
Did I get it right that we need NodeJS only for development and can run the final WP+Theme on php server?
LikeLike
Great question. Well, yes, when the theme is finished, you have a single bundle.js JS file which contains the whole app and is loaded into the WP theme (with like wp_enqueue_scripts), so you don’t need Node anymore.
LikeLike
Thanks. So It’s a great news for people on a shared hosting
LikeLike
HI,I got a question,when I want to get the data from the backend,should I set a proxy in my webpackDevServer?
I set the proxy like this:
“`
proxy:{
‘/api/*’: {
target: {
“host”: “test.local”,
“protocol”: ‘http:’,
“port”: 80,
“path”:”/wp-json/wp/v2/”
},
ignorePath: true,
changeOrigin: true,
secure: false
}
}
“`
no matter i visit the `/api/wp-json/wp/v2/posts` or `/api/wp-json/wp/v2`,it will return the same thing.where is the mistake?
LikeLike
Sorry if this is obvious but how do I run the dev server?
LikeLike
How do you actually get web dev server to listen to your wp-theme? do you have to do everything in index.html? A Little confused on how to get to my actual website on localhost
LikeLiked by 1 person