We need a new website.
We hear these words from our clients a lot. And while it’s most often very true—they do, in fact, need a new website—it’s only part of the truth. Because websites don’t exist in isolation. They’re part of a digital ecosystem of interrelated tools that, designed properly, work together to solve business goals. And the website itself is a collection of tools and technologies that must work seamlessly together, so our ever-evolving approach to website engineering must be strategic, forward-thinking, and robust.
Webpack, React, and WordPress are part of a technology bundle we’ve created to help us modernize our development stack, allowing us to build these robust websites and ecosystems more effectively and efficiently. Read on for how we got here—and why we consider this collection of tools the right foundation for the realities of today.
- A unified build system that avoids code duplication and maximizes resource sharing
- High performance, optimal delivery of client side code
- An enjoyable and fluent developer experience that brings the best of current client side development to the WordPress workflows we love
But before we get into some code samples and all the riveting details, a little bit of history. If you want to skip the nostalgia fest, jump on down to “Let’s Work it Out.”
Which File Was That Again?
This obviously led to a lot of code duplication if not managed well and real difficulties managing vendor dependencies. For example, seven files rely on Select2, but three at an older version, and oh, yes—those third-party plugins we dropped in, well two of those are also loading up other versions of it for their use. Then we got to the minifying of all these files effectively and attempting to reduce the amount of network requests for this peppering of fragments from 23 directories. This saw many optimization add-ons using PHP to intercept all enqueued scripts and concatenating them, but this was prone to issues with order of execution and other problems.
We had to find a better way to manage all of this.
The Birth of SquareOne
Our solution to these and other issues we were facing was the creation of our internal framework, called SquareOne. It consists of a range of methodologies and patterns we have developed—and continue to evolve—to vastly improve our day-to-day work with WordPress. Its high-level goal is to provide for all of our projects a consistent and reliable codebase that takes care of common tasks and shared utilities. It’s a modular system that scales smoothly from smaller projects all the way up to demanding, enterprise-level WordPress applications, all with minimal friction.
Regarding frontend development and our build system, SquareOne was initially based on Grunt and used Bower for vendor management more than six years ago. We used Sass for styles and still managed multiple feature-based plugins, each of which had its own dedicated Grunt/Bower subsystem if needed. There wasn’t much gain over the original issues, so we continued to explore ways to unify the codebase, vendor management, and its frontend build systems.
With the solid traction NPM was gaining, thousands of developers started to share their frameworks, libraries, and utilities in an easy-to-consume system that spelled the death of Bower and the like, at least for all but the masochistic. A new paradigm had settled in quickly and was prepared to fill our workflows, servers, browsers, and hard disks with its enthusiasm.
These rapid changes also brought forth some of the most influential and heavily used frameworks for frontend development—systems such as Angular, Vue, or our framework of choice, React. Why “influential”? Because the purpose they filled (building complex applications that run in the browser) caused the ecosystem to explode even further. This occurred both in plugins/libraries that enhance their core functionality, as well as build systems that could effectively manage the complex dependency trees and resultant asset bundles that these new applications required.
Webpack, What a Lovely Headache
Hello Unity, My Old Friend
We now had a much better build flow in place. We had also made some other important steps in our evolution of SquareOne:
- We implemented a single core theme and plugin, allowing our architecture to function more easily as one seamless application rather than a collection of discrete, unrelated plugins, each with their own autoloaders, build processes, and dependency trees. Additionally, this paradigm allows us to use a single Service Container to handle any dependency injection or class composition needs, providing a unified system for transporting dependencies among the various pieces of the application.
- We moved to PostCSS to streamline our CSS work and gain more granular control over our bundle output.
Story over, right?
Nah, let’s make this more complex. Somebody somewhere said, “Hey, we should build two of those admin screens and a few sections on the frontend of this PHP-served WordPress app in React.”
The Best of Both Worlds?
We had been building React apps and integrating them into WordPress quite frequently over the last four years. We had also built Node-served SaaS platforms, like Loxi, utilizing full server-side rendering. There were also pure React frontends like our own tri.be website, or our own custom page builder for WordPress, Panel Builder. But the hybrid injections of React to drive a portion of a standard LAMP-served WordPress build were proving more difficult to engineer than the ease of building a singular React-based app on a Node-hosted platform or the like.
- Babel polyfill can’t be used twice, so the React apps would have to use runtime
- High possibility of duplicated dependencies in the bundles
- Difficulty sharing custom utilities in a concise way across builds
- Back to multiple build system and dependency version management
You may be thinking, “Why are you using React to drive portions of the WordPress frontend/admin? Just build it all in plain JS.” This is a reasonable suggestion for many cases, and we do follow that rule when we can. But we have found enough use cases where React was the right tool for the job on some of an otherwise vanilla JS-based system.
Now we had to find an effective way to manage this all from one truly unified build flow.
Let’s Work It Out
Well, that’s more than enough background for those of you who slogged through, let’s get to the current build system we use to achieve the goals we set out at the beginning of this article. This will be a combination of a few code samples, along with links to a public repo. Speaking of which, you can load up this GitHub link in a new tab and refer to the code as the principles are being discussed.
Before we start, a few notes on this system. While pathways to source and build files correlate to the directory structure we use, this is very malleable. You are not required to align with our paradigm of a single “core” plugin and theme, and all paths can be easily adjusted to whatever structure you like to use. It’s also possible you don’t care about having an admin or theme bundle generated, depending on what you want to do. You can simply ignore the unneeded aspects, or if you like, clean out those portions from the build, though they don’t add any overhead until they are in use.
Let’s get to it.
As mentioned, our build system lives at the root of the WordPress install. This allows us to easily manage bundles anywhere across the system from one central place. We moved from Grunt to Gulp a while ago, and we still require it on top of Webpack to handle some of the other tasks we need to accomplish. These could have been scripted in other ways, but the convenience of the Gulp ecosystem and availability of many tools made it the right fit for us.
If you haven’t already, be sure to load up the example repository in another tab now.
Our package.json is very hefty and contains all the tools we use out of the box across our projects. It also contains granular paths (used in the Gulp/Webpack config files) for each aspect of the output files across the pcss and JS directories. This is the place where you would adjust those paths to suit your theme and/or plugin structure. Also note that we prefix all variables with an underscore. Lastly, we set our browserslist array here, just under the variables stack, so adjust accordingly.
Our Gulpfile handles a lot of tasks. These are broken up by task type into the gulp_tasks directory located at root and then imported into the main Gulpfile using require-dir. They are then wired up using the registerTasks function below the gulpTasks array. This allows us to scale to a very large set of tasks while keeping them clean and organized.
Note that we also run a Browsersync server for live reloading and device sync. This is run using the dev task, and then our only other task in this example is dist, which bundles both the scripts and styles. This can be broken up into sub-tasks that fit the flow you desire easily, and we do so on a project-by-project basis.
Our Webpack config is also broken up into manageable files, with partials located in the webpack directory found at root. Since we want to be able to run a variety of different bundles with minimal code duplication, we also use this technique to break out common aspects of the various tasks and merge those into the unique parts of the flows using webpack-merge.
You may also note that the root webpack-config relies on node env to load the correct file for the task being run. This way, not only can we target dev and production bundles for our admin and theme tasks, but we can also use arbitrary NODE_ENV keys to run our React app chunks in hmr-enabled dev mode during work on them. More on that in the React section later.
This is our common tasks file. Not much to note here, other than special handling for a few of the libraries we use that require that. This is merged into all our theme and admin tasks.
This file contains our theme development tasks. We merge in common, setup our entry and output paths and naming conventions for the chunks, and enable webpack bundle analyzer to keep track of our bundle size.
We also have a system in place to inject polyfills as chunks based on browser detection of features, as can be seen in the setupEnvironment method below. This means polyfills only inject based on need, and all subsequent code is deferred until the chunk-loading promise is resolved.
Any other code that should only load based on a context should also use chunking here. Observe the commented out example React app loading as a chunk in the sample below. You can use element exists, a route check, or some other method like a body class exists check to do your own conditional chunk injection for any other portion of the imported sub modules as needed.
You can configure your React system however you like. We just include our entry point here for the sake of completeness.
There is a little bit of configuration we will need to do in order to switch into Hot Module Replacement mode when we are working on React apps, as opposed to when they are just loading as chunks in the main bundles.
First we need to define a constant in our local-config.php (or however you handle WP dev config) like so:
Then we setup this enqueue to run when that flag is true in the same file we use to enqueue our other scripts.
This points to our Webpack dev server output file when we run our NPM script that kicks in the React app Webpack configuration, e.g. app:example in our package.json above. Combined with the fact that our chunk loading for our React app won’t execute if we are in HMR-enabled dev mode, we can avoid double loading of that code when working on any of the React apps.
Of course, engineering systems like this are not for everyone. While the tools are very powerful once you get comfortable with them, the reality is that they can be very time-consuming and at times brittle when managing a build stack like this. As such, ways to simplify this kind of tooling overhead are on many a dev’s list, and we are seeing many promising solutions cropping up, with SnowPack currently at the forefront. As these approaches mature, you can bet that we will be weighing them for merging into our systems. But for now, that wonderful ringing in our ears is still saying: Webpack and its ecosystem are the best way to build a modern development stack.