React App Embedded into WordPress page

Goal: Create WordPress plugin which will inject React based web app\widget into normal WordPress page\post\sidebar.

This is useful when you need to create an complex fully interactive widget\app like calculator for example, and you want it to be a part of WordPress page.

In this article I’ll describe one of the approaches, though it may be not perfect, we’ll dig deeper into other build approaches in next articles.

Let’s split our big task into sub-tasks to better understand how we achieve it:

  1. Create WordPress plugin
    • it will enqueue our js\css and register shortcode to render root element of React app
    • yes shortcodes are old school approach (compared to Gutenberg Blocks) but they work well for this use case and we keep it simpler for now.
    • it will also pass in App’s settings via data attribute of root element.
  2. Generate React app via npx create-react-app
    • the idea is to utilize a ready to use solution for scaffolding and Webpack configuration, so newbies like me 🙂 in react world could follow standard React tutorials to build his first React App.
    • also you can use approach explained in this article to simply inject your existing React App, generated via npx create-react-app into WP based site.
  3. Modify build\start npm scripts to work well with WordPress.
    • by default react build Webpack’s configuration will generate js files split in chunks – it makes it harder to link them via wp_enqueue_scripts as we used to in WordPress.
    • so we’ll write a code which will partially override config and Webpack will generate js\css which will be easy to use in WP.
  4. Write JSX\React code and build it.

Result plugin can be fetched from github https://github.com/MikhailRoot/embeded-react-app-in-wp

1. Create WordPress plugin

It will register a shortcode and enqueue scripts and styles for our React App.

https://github.com/MikhailRoot/embeded-react-app-in-wp/blob/master/plugin.php

NB: I use

define('ENV_DEV', true); // in wp-config.php

to load generated by yarn start css\js files while development, set it to false when you need to use css\js generated via yarn build.

In a shortcode I pass in json string of settings as data-default-settings attribute for our App’s root element.

2. Generate React App

In your new plugin’s directory simply run npx create-react-app app and wait a bit.

We’ll use yarn add for the sake of brevity but you can use npm as well. Just pick one (anyway you can switch back any time if needed).

Let’s cleanup a bit via removing redundant files from src directory of app. These are:

  • logo.svg – because in WordPress we handle images URLs differently – you can use relative to plugin’s path links to images.
  • serviceWorker.js

As well as references to them in index.js and App.js

You can remove all files inside /app/public except index.html as we don’t need them.

Remove .git directory inside react app’s folder – as you’ll probably want to track changes via git but for a whole plugin and not just react part of it. And in .gitignore remove line

/build

So our main.js and main.css files which are result of build process will be tracked with git.

Next step is optional – we’ll install node-sass I just prefer to work with SCSS files rather then plain CSS, so in case you like it as well – just cd to your newly generated react app directory and then run yarn add node-sass as described here https://create-react-app.dev/docs/adding-a-sass-stylesheet/

Create scss folder under src and move css files into it, renaming them to .scss and change imports of these files in js to point to our new .scss files like:

import './scss/App.scss';

in App.js, and

import './scss/index.scss';

for index.js

3. Modify build\start npm scripts to work well with WordPress

In your React App’s directory, run yarn add rewire --dev – we use rewire to be able to redefine webpack.config provided by create-react-app boilerplate without a need to eject react app or modify config files which reside in node_modules

Now create new directory called scripts – here we’ll set our JS files which will do magic for us.

start-non-split.js – this file will change Webpack config on fly so that it will not split JS code in chunks and will not inject hash into filename.

const externals = require( './externals' );
const rewire = require( 'rewire' );
const defaults = rewire( 'react-scripts/scripts/start.js' );
const configFactory = defaults.__get__( 'configFactory' );

defaults.__set__( 'configFactory', ( env ) => {
   const config = configFactory( env );

   config.externals = {
      ...config.externals,
      ...externals,
   };

   config.optimization.splitChunks = {
      cacheGroups: {
         default: false,
      },
   };
   config.optimization.runtimeChunk = false;
   config.output.filename = 'static/js/[name].js';
   config.output.chunkFilename = 'static/js/[name].js';
   return config;
} );

build-non-split.js – this is used for building production js\css files it will again build them as single files making it easier to link in WP.

const externals = require( './externals' );
const rewire = require( 'rewire' );
const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' );
const defaults = rewire( 'react-scripts/scripts/build.js' );
const config = defaults.__get__( 'config' );

/* we inject our Externals to keep our bundles clean and slim */
config.externals = {
   ...config.externals,
   ...externals,
};

/*
 * we set jsonFunction of webpack to our custom one
 * so multiple js bundles built with webpack could be safely loaded,
 * as we are aware that our plugin isn't the only one which can be potentially built with webpack.
 */
config.output.jsonpFunction = 'YourReactApp_webpackJsonpjs';

/* we disable chunks optimization because we want single js\css file to be loaded by plugin. */
config.optimization.splitChunks = {
   cacheGroups: {
      default: false,
   },
};
config.optimization.runtimeChunk = false;
config.output.filename = 'static/js/[name].js';
config.output.chunkFilename = 'static/js/[name].js';

/*
* lets find `MiniCssExtractPlugin` type of object in plugins array and redefine it's options.
* And remove all unnecessary plugins.
*/
const disabledPlugins = [
   'GenerateSW',
   'ManifestPlugin',
   'InterpolateHtmlPlugin',
   'InlineChunkHtmlPlugin',
   'HtmlWebpackPlugin',
];
config.plugins = config.plugins.reduce( ( plugins, pluginItem ) => {

   if ( disabledPlugins.indexOf( pluginItem.constructor.name ) >= 0 ) {
      return plugins;
   }

   if ( pluginItem instanceof MiniCssExtractPlugin ) {
      plugins.push(
         new MiniCssExtractPlugin( {
            filename: 'static/css/[name].css',
            chunkFilename: 'static/css/[name].css',
         } )
      );
   } else {
      plugins.push( pluginItem );
   }

   return plugins;
}, [] );

externals.js – this file contains shared scripts which can be provided by WP Core so we don’t need them to be bundled into our build.

module.exports = {
   react: 'React',
   'react-dom': 'ReactDOM',
};

In my case after I’ve marked React and ReactDOM as external ones JS bundle’s size decreased from 160kb to 56kb – those get’s loaded from wp-includes as a dependency any way, but imagine you have bunch of such Apps and each will load extra version of bundled React library. To overcome this we mark them as external – this way we keep our bundles clean and light, and at the same time we leverage WordPress scripts dependency manager to link necessary dependencies for us.

Then open package.json file and in scripts section update start command to

"start": "node ./scripts/start-non-split.js",

and build command to

"build": "node ./scripts/build-non-split.js"

So your package.json file will look like this:

{
  "name": "app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.3.2",
    "@testing-library/user-event": "^7.1.2",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.1"
  },
  "scripts": {
    "start": "node ./scripts/start-non-split.js",
    "build": "node ./scripts/build-non-split.js",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "node-sass": "^4.14.0",
    "rewire": "^5.0.0"
  }
}

4. Write JSX\React code and build it.

Now we are almost ready, let’s update index.js file which initializes our React app on root element inserted via shortcode.

import React from "react";
import ReactDOM from "react-dom";
import "./scss/index.scss";
import App from "./App";

const rootEl = document.getElementById("md-react-app");
if (rootEl) {
  const settings = JSON.parse( rootEl.getAttribute( 'data-default-settings' ) );
  ReactDOM.render(
    <React.StrictMode>
      <App settings={settings} />
    </React.StrictMode>,
    rootEl
  );
}

Here we also read initialization settings data provided by WP, and pass them into App as prop.

If you’ll use Redux you can use these data to generate initial state for App.

Let’s update our App.js file to utilize our settings from props.

import React from 'react';
import './scss/App.scss';

function App(props) {

  const items = props.settings.some_items || [];

  return (
    <div className="App">
      <header className="App-header">
        <h1>{ props.settings.l18n.main_title }</h1>
        <ul>
          {
            items.map((item, index)=>{
              return (
                  <li key={index}>{item}</li>
              )
            })
          }
        </ul>
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

Now we are ready to test everything together.

  • activate our plugin and place [md-react-app] shortcode on page.
  • in terminal cd to plugin’s directory /app and run yarn build – it will build js\css then just open a page where you’ve placed a shortcode and app will be rendered there.

For development:

  • add define('ENV_DEV', true); to your wp-config.php
  • in plugin’s /app sub directory run yarn start it will build css\js and spin up a dev server to serve updated css\js whenever you make change in source files. (it doesn’t support live reloading yet but for quickstart it’s enough).
  • It will also open a new tab in browser leading to localhost:3000 and showing error message that React is not defined just close and ignore it. It says React is not defined just because we’ve declared it as external and we haven’t bundled it into our result main.js
  • It will work inside WordPress page because we’ve declared wp-element as dependency in our plugin, so WordPress will load React on it’s own.
  • So just refresh your WordPress page where you’ve placed our new shortcode and here’s your React App inside WordPress page.
  • NB: in ENV_DEV true mode you need to yarn start otherwise it will not be able to load css\js, set it to false or comment out for production.

Happy codding 🙂

Laravel 5.3 Elixir nghtml2js extenstion with watcher

Today I’ll share experience of creating custom extension for Elixir gulp based build system on Laravel 5.3

There are countless ways of gulp usage, Elixir is built to simplify common gulp tasks implementation.  If you develop AngularJs  powered application you’ll definitelly will wan’t to complile your directives/components templates into Angular module and minify it to preload them to template cache.

Continue reading “Laravel 5.3 Elixir nghtml2js extenstion with watcher”

Laravel homestead xdebug with PHPStorm

This is “yet another howto” set up xdebug on Laravel homestead  with PhpStorm.

Homestead has xdebug extension installed out of the box.  Latest PhpStorm versions offer  Zero-configuration Web Application Debugging so you dont need to create specific debugging configuration in IDE. The only thing you need to do is to add some more configuration options to xdebug config file.

Continue reading “Laravel homestead xdebug with PHPStorm”

AngularJS $http service with WordPress

Some time ago I needed to create a branch of feature rich WordPress plugins for client’s specific needs.  I like AngularJS for its structured and concise nature, decided to use it in these projects.  I’ve written a part of client side logic then I realized that I can’t trigger wp_ajax_ hooks via Angular’s $http service as with jQuery ajax methods.

Continue reading “AngularJS $http service with WordPress”

Element overflow fixing

Hi! today we’ll discuss one of the cutest solutions for html element overflow problem.

Mistakes in sizing and positioning of containers and elements on a web page results in usually unneeded horizontal scroll bar. And if your markup is complex enough it’s usually takes a lot of time to find elements which caused overflow. Sure you can stay blind to this by adding overflow:hidden; but! you shouldn’t do this anyway. Let’s dive into one cute solution for this…

Continue reading “Element overflow fixing”

WordPress VanillaForums integration plugin improved

Hi everyone! Today We’ll discuss VanillaForums integration plugin with WordPress. And I’ll explain solutions for problems we were facing integrating WordPress with VanillaForums. The result as improved plugin you can download from here.

Out of the box it provides us VanillaForums powered comments for posts, and cool Single Sign On (SSO) feature which means your users can login on your wordpress website and whenever they go to your community provided by VanillaForums they will be logged in there with a same account.

For example we’ve used this SSO feature for one project where we’ve set up vanilla community to register only through SSO, so our users will be registered on our main WordPress based website first and then this account information used for vanilla accounts on a community.

All was fine except logic of creation of users on vanilla’s side.

Continue reading “WordPress VanillaForums integration plugin improved”