New WP Bundling Workflow


Updated Sep 19th, 2021

Using the new @wordpress/script feature instead of our own webpack.config.js file.

From LearnWebCode

Started by seeing example in progress. Installed new version of “Fiction-University” theme on local.

(install plugin => import site (admin-admin; guest-guest) => open in vscode => npm install => npm run start)

Note NPM Install failed because I needed to update npm and node.

Had a look around:

Noticed a cleaner “functions.php” file, (no node_modules exclude function?). No webpack.config.js file. All CSS files (except the theme declaration file in the root) ends in “.scss” and all of the actual CSS code looks the same. Notice the new “inc, template-parts, build and src” folders. Mixins now in a “baseline.scss” file and we have a “utlity-classes.scss” file. PostCSS is now SASS! No “if checks” on class instantiation in the “index.js” file, these checks are in the class’ constructor function. Seeing the word “nonce” a lot in the code in “functions.php” file and by “Axios” requests. Also noticed a bug on the menu animation.

Also checked out the new workflow in the brads-boilerplate-starter theme

Simple “header, footer, functions, index” setup. Functions file just loads the JavaScript and CSS files, unclear if it is dynamic for the local dev environment and doesn’t appear to be. The “index.js” file has a simple React component with an instantiated Person class. There are some base “scss” files as well for variables, mixins, global styles, etc. This base theme needs a “screenshot.png” file for the theme. Looking at the theme on the front-end, after activation, super simple UI with header text, the example React component with increment on click, page title as a link, footer text, and simple log in the console. Excellent.

Note: the CSS related to the example React component is imported into the “index.js” file and not the component file itself.

Working This New Process In

Incorporate into this new workflow into existing themes? Undecided.

Going forward should I use “Brad’s Starter Theme” or merge this new workflow into my own current “custom wp starter” theme, (Mobile Menu, Google Fonts, Some Basic Functions, Etc.)? Undecided. May be better to update an existing theme like ECP, and then use that as a starter.

Announcement in Udemy

The new simplified way of working with JavaScript and CSS in this course is now live. The new system:

Instructions to Migrate Existing Site to Workflow

Here are a few key points:

Migration Guide Steps:

1. Delete your node_modules folder

2. Delete your package-lock.json file

3. Delete your package.json file and replace it with the new package.json file from the university static repo.

4. Delete your webpack.config.js file

5. Delete your entire “css” folder and replace it with the “css” folder from the university static repo.

6. Rename your “js” folder to “src” – and rename your main scripts.js file to index.js

7. Within your functions.php file, update your “university_files” function to look like this instead:

function university_files() {
 wp_enqueue_script('googleMap', '//', NULL, '1.0', true);
 wp_enqueue_script('main-university-js', get_theme_file_uri('/build/index.js'), array('jquery'), '1.0', true);
 wp_enqueue_style('custom-google-fonts', '//,300i,400,400i,700,700i|Roboto:100,300,400,400i,700,700i');
 wp_enqueue_style('font-awesome', '//');
 wp_enqueue_style('university_main_styles', get_theme_file_uri('/build/style-index.css'));
 wp_enqueue_style('university_extra_styles', get_theme_file_uri('/build/index.css'));
 wp_localize_script('main-university-js', 'universityData', array(
  'root_url' => get_site_url(),
  'nonce' => wp_create_nonce('wp_rest')

8. If it’s difficult to copy and paste from the code above please note you can use the reference zip file from Lesson #104 to copy and paste the same function code.

9. Run npm install. Be patient, the official “@wordpress/scripts” package has a lot of dependencies so it takes a minute or two to install.

10. Run npm run start (the old “npm run dev” and “npm run devFast” are still available and simply call the start task).

11. Refresh your local dev site in your browser to test that it’s loading CSS and JS.

Issues on Above Conversion

Some surprising things about the new workflow: so few dependiencies, no webpack, no babel, No postcss, No sass, etc. WordPress handles the bundling.

Just glidejs, @wordpress/scripts, axios, and normalize.css

-In addition I will need lodash (go top button)
-In addition I will need to remove anything else, like lazysizes (WP handles now)
-Rename CSS files to .scss extension
-update mixins definetions with @mixin instead of @define-mixin and @include instead of @mixin
-rename js folder to src and main.js to index.js
-update functions.php function to hookup js, and css in build file, (scrapped the wp_localize_script line).

Need to refresh the page to see the changes and we no longer need the if ( line in index.js file.

Starting with the brad’s boilerplate on the git repo is preferred if possible.

Original webpack.config.js File

  SUPER IMPORTANT: This config assumes your theme folder is named
  exactly 'ecp-theme-2021' and that you have a folder
  inside it named 'bundled-assets' - If you'd like to adapt this
  config to work with your own custom folder structure and names
  be sure to adjust the publicPath value on line #116. You do NOT
  need to update any of the other publicPath settings in this file,
  only the one on line #116.

const currentTask = process.env.npm_lifecycle_event
const path = require("path")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
const ManifestPlugin = require("webpack-manifest-plugin")
const fse = require("fs-extra")

const postCSSPlugins = [require("postcss-import"), require("postcss-mixins"), require("postcss-simple-vars"), require("postcss-nested"), require("postcss-hexrgba"), require("postcss-color-function"), require("autoprefixer")]

class RunAfterCompile {
  apply(compiler) {
    compiler.hooks.done.tap("Update functions.php", function () {
      // update functions php here
      const manifest = fse.readJsonSync("./bundled-assets/manifest.json")

      fse.readFile("./functions.php", "utf8", function (err, data) {
        if (err) {

        const scriptsRegEx = new RegExp("/bundled-assets/scripts.+?'", "g")
        const vendorsRegEx = new RegExp("/bundled-assets/vendors.+?'", "g")
        const cssRegEx = new RegExp("/bundled-assets/styles.+?'", "g")

        let result = data.replace(scriptsRegEx, `/bundled-assets/${manifest["scripts.js"]}'`).replace(vendorsRegEx, `/bundled-assets/${manifest["vendors~scripts.js"]}'`).replace(cssRegEx, `/bundled-assets/${manifest["scripts.css"]}'`)

        fse.writeFile("./functions.php", result, "utf8", function (err) {
          if (err) return console.log(err)

let cssConfig = {
  test: /\.css$/i,
  use: ["css-loader?url=false", { loader: "postcss-loader", options: { plugins: postCSSPlugins } }]

let config = {
  entry: {
    scripts: "./js/scripts.js"
  plugins: [],
  module: {
    rules: [
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-react", ["@babel/preset-env", { targets: { node: "12" } }]]

if (currentTask == "devFast") {
  config.devtool = "source-map"
  config.output = {
    filename: "bundled.js",
    publicPath: "http://localhost:3000/"
  config.devServer = {
    before: function (app, server) {
        If you want the browser to also perform a traditional refresh
        after a save to a JS file you can modify the line directly
        below this comment to look like this instead. I'm using this approach
        instead of just disabling Hot Module Replacement beacuse this way our
        CSS updates can still happen immediately without a page refresh.

        If you're using a slower computer and the new bundle is not ready
        by the time this is reloading the browser you can always just set the 
        "hot" property a few lines below this to false instead of true. That
        will work on all computers and the only trade off is the browser will
        perform a traditional refresh even for CSS changes as well.

      // server._watch(["./**/*.php", "./**/*.js"])
      server._watch(["./**/*.php", "!./functions.php"])
    public: "http://localhost:3000",
    publicPath: "http://localhost:3000/",
    disableHostCheck: true,
    contentBase: path.join(__dirname),
    contentBasePublicPath: "http://localhost:3000/",
    hot: true,
    port: 3000,
    headers: {
      "Access-Control-Allow-Origin": "*"
  config.mode = "development"

if (currentTask == "build" || currentTask == "buildWatch") {
  config.output = {
    publicPath: "/wp-content/themes/ecp-theme-2021/bundled-assets/",
    filename: "[name].[chunkhash].js",
    chunkFilename: "[name].[chunkhash].js",
    path: path.resolve(__dirname, "bundled-assets")
  config.mode = "production"
  config.optimization = {
    splitChunks: { chunks: "all" }
  config.plugins.push(new CleanWebpackPlugin(), new MiniCssExtractPlugin({ filename: "styles.[chunkhash].css" }), new ManifestPlugin({ publicPath: "" }), new RunAfterCompile())

module.exports = config

React in the New Workflow

In the desired php file you have an empty div with an id so we can hook in to

<!-- example react component -->
<div id="render-react-example-here"></div>
<!-- end example react component -->

In the src/index.js file we import in the component, and React and ReactDOM. Then we hook into the div above:

import ExampleReactComponent from './scripts/ExampleReactComponent'
import React from 'react'
import ReactDOM from 'react-dom'

ReactDOM.render(<ExampleReactComponent />, document.querySelector("#render-react-example-here"))

The actual code for the component is in a “src/scripts/YourComponent.js” file

import React, { useState } from "react"

function ExampleReactComponent() {
  const [clickCount, setClickCount] = useState(0)

  return (
    <div className="example-react-component" onClick={() => setClickCount(prev => prev + 1)}>
      <h1>Hello from React!</h1>
      <p>You have clicked on this component {clickCount} times.</p>

export default ExampleReactComponent

The CSS for the component is kept in a separate file and uses a global className. The css file is not imported into the component but instead the main.scss file.

When is it good to Use a React Component?

Should each page section be a component? Can you still use the useContext hook?