├── postcss.config.js
├── src
├── style.scss
├── index.js
├── App.js
└── components
│ └── Posts.js
├── .babelrc
├── .gitignore
├── package.json
├── README.md
├── plugin.php
└── webpack.config.js
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [require("autoprefixer")],
3 | };
4 |
--------------------------------------------------------------------------------
/src/style.scss:
--------------------------------------------------------------------------------
1 | $primary-color: white;
2 | $bg: black;
3 |
4 | h1 {
5 | color: $primary-color;
6 | background-color: $bg;
7 | padding: 15px;
8 | }
9 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "modules": false
7 | }
8 | ],
9 | "@babel/preset-react"
10 | ],
11 | "plugins": ["react-hot-loader/babel"]
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache/
2 | coverage/
3 | dist/*
4 | !dist/index.html
5 | node_modules/
6 | *.log
7 |
8 | # OS generated files
9 | .DS_Store
10 | .DS_Store?
11 | ._*
12 | .Spotlight-V100
13 | .Trashes
14 | ehthumbs.db
15 | Thumbs.db
16 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import "react-hot-loader/patch";
2 | import React from "react";
3 | import ReactDOM from "react-dom";
4 | import App from "./App";
5 | import "./style.scss";
6 |
7 | var mountNode = document.getElementById("hmr-app");
8 | ReactDOM.render(, mountNode);
9 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { hot } from "react-hot-loader/root";
3 | import { Button } from "@wordpress/components";
4 | import Posts from "./components/Posts";
5 |
6 | function App({ name }) {
7 | return (
8 | <>
9 |
Hello {name}!
10 |
11 |
14 |
15 |
16 | >
17 | );
18 | }
19 |
20 | export default hot(App);
21 |
--------------------------------------------------------------------------------
/src/components/Posts.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { __ } from "@wordpress/i18n";
3 | import apiFetch from "@wordpress/api-fetch";
4 | import { Spinner } from "@wordpress/components";
5 |
6 | function Posts() {
7 | const [posts, setPosts] = useState([]);
8 | const [isFetching, setIsFetching] = useState(true);
9 |
10 | useEffect(() => {
11 | setIsFetching(true);
12 |
13 | apiFetch({
14 | path: "/wp/v2/posts",
15 | })
16 | .then((resp) => {
17 | setIsFetching(false);
18 | setPosts(resp);
19 | })
20 | .catch((err) => {
21 | setIsFetching(false);
22 | console.log(err);
23 | });
24 | }, []);
25 |
26 | if (isFetching) {
27 | return (
28 |
29 | {__("Loading posts...")}
30 |
31 | );
32 | }
33 |
34 | return (
35 |
36 |
{__("Blog Posts")}
37 |
38 |
48 |
49 | );
50 | }
51 |
52 | export default Posts;
53 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wp-react-hmr-demo",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "keywords": [],
7 | "author": "",
8 | "license": "ISC",
9 | "scripts": {
10 | "clean": "rm -rf ./dist",
11 | "build:dev": "webpack --mode development",
12 | "build": "webpack --mode production",
13 | "start": "webpack serve --hot --mode development --disable-host-check"
14 | },
15 | "dependencies": {
16 | "@wordpress/api-fetch": "^3.21.1",
17 | "@wordpress/components": "^12.0.1",
18 | "@wordpress/i18n": "^3.17.0",
19 | "react": "^17.0.1",
20 | "react-dom": "^17.0.1",
21 | "react-hot-loader": "^4.13.0"
22 | },
23 | "devDependencies": {
24 | "@babel/core": "^7.12.10",
25 | "@babel/preset-env": "^7.12.11",
26 | "@babel/preset-react": "^7.12.10",
27 | "@hot-loader/react-dom": "^17.0.1+4.13.0",
28 | "autoprefixer": "^10.2.1",
29 | "babel-loader": "^8.2.2",
30 | "clean-webpack-plugin": "^3.0.0",
31 | "copy-webpack-plugin": "^7.0.0",
32 | "css-loader": "^5.0.1",
33 | "file-loader": "^6.2.0",
34 | "mini-css-extract-plugin": "^1.3.3",
35 | "node-sass": "^5.0.0",
36 | "postcss-loader": "^4.1.0",
37 | "prettier": "^2.2.1",
38 | "sass-loader": "^10.1.0",
39 | "style-loader": "^2.0.0",
40 | "url-loader": "^4.1.1",
41 | "webpack": "^5.12.2",
42 | "webpack-cli": "^4.3.1",
43 | "webpack-dev-server": "^3.11.1"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React HMR WordPress Plugin
2 |
3 | A WordPress plugin built with React with [hot module replacement](https://webpack.js.org/concepts/hot-module-replacement/) support.
4 |
5 | ### Instructions
6 |
7 | 1. Clone the repository in your `/wp-content/plugins` folder.
8 | 1. cd into the repo in your terminal `cd wp-react-hmr-demo`
9 | 1. Install dependencies by running `yarn install`
10 | 1. Build the plugin assets: `yarn build`
11 | 1. Install the plugin from `/wp-admin/plugins/` page
12 |
13 | To start developing with [HMR](https://webpack.js.org/concepts/hot-module-replacement/) support, open your `wp-config.php` and add this constant:
14 |
15 | ```php
16 | define( 'WP_LOCAL_DEV', true );
17 | ```
18 |
19 | Now run: `yarn start`
20 |
21 | If you navigate to the plugin's menu and start changing your React code, you should see the code updates in realtime. Enjoy!
22 |
23 | ##### What is this `WP_LOCAL_DEV` constant?
24 |
25 | When you start the webpack-dev-server with `yarn start` command, it opens a server in `http://localhost:8080` and listens to the changes. The `WP_LOCAL_DEV` constant helps to detect if we are in the development environment. If configured, the plugin CSS and JS files are served from `http://localhost:8080/` instead of your WordPress installation.
26 |
27 | When not running `yarn start`, you should turn the constant to `false`.
28 |
29 | ### Commands
30 |
31 | `yarn start`: Starts in development mode with HMR support
32 |
33 | `yarn build`: Runs the build in production mode
34 |
35 | `yarn build:dev`: Runs the build in development mode
36 |
--------------------------------------------------------------------------------
/plugin.php:
--------------------------------------------------------------------------------
1 | Hello World';
29 | }
30 |
31 | function react_hmr_scripts()
32 | {
33 | $version = date('y-m-d');
34 | $dependencies = [
35 | // 'wp-polyfill',
36 | 'wp-api-fetch',
37 | ];
38 |
39 | $url = plugins_url('/dist', __FILE__);
40 |
41 | // for local development
42 | // when webpack "hot module replacement" is enabled, this
43 | // constant needs to be turned "true" on "wp-config.php"
44 | if (defined('WP_LOCAL_DEV') && WP_LOCAL_DEV) {
45 | $url = 'http://localhost:8080';
46 | }
47 |
48 | // register scripts
49 | wp_register_script('app-runtime', $url . '/runtime.js', $dependencies, $version, true);
50 | wp_register_script('app-vendors', $url . '/vendors.js', ['app-runtime'], $version, true);
51 | wp_register_script('app-script', $url . '/app.js', [ 'app-vendors' ], $version, true);
52 |
53 | // register styles
54 | wp_register_style('app-css', $url . '/app.css', [ 'wp-components' ], $version);
55 |
56 | // enqueue scripts and styles
57 | wp_enqueue_script('app-script');
58 | wp_enqueue_style('app-css');
59 | }
60 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
3 |
4 | const config = {
5 | entry: {
6 | app: "./src/index.js",
7 | },
8 | output: {
9 | path: path.resolve(__dirname, "dist"),
10 | filename: "[name].js",
11 | },
12 | externals: {
13 | "@wordpress/api-fetch": ["wp", "apiFetch"],
14 | },
15 | module: {
16 | rules: [
17 | {
18 | test: /\.(js|jsx)$/,
19 | use: "babel-loader",
20 | exclude: /node_modules/,
21 | },
22 | {
23 | test: /\.css$/,
24 | use: [
25 | MiniCssExtractPlugin.loader,
26 | {
27 | loader: "css-loader",
28 | options: {
29 | importLoaders: 1,
30 | },
31 | },
32 | "postcss-loader",
33 | ],
34 | },
35 | {
36 | test: /\.scss$/,
37 | use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
38 | },
39 | {
40 | test: /\.svg$/,
41 | use: "file-loader",
42 | },
43 | {
44 | test: /\.png$/,
45 | use: [
46 | {
47 | loader: "url-loader",
48 | options: {
49 | mimetype: "image/png",
50 | },
51 | },
52 | ],
53 | },
54 | ],
55 | },
56 | resolve: {
57 | extensions: [".js", ".jsx"],
58 | alias: {
59 | "react-dom": "@hot-loader/react-dom",
60 | },
61 | },
62 | devServer: {
63 | contentBase: "./dist",
64 | headers: {
65 | "Access-Control-Allow-Origin": "*",
66 | "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
67 | "Access-Control-Allow-Headers":
68 | "X-Requested-With, content-type, Authorization",
69 | },
70 | },
71 | optimization: {
72 | runtimeChunk: "single",
73 | splitChunks: {
74 | cacheGroups: {
75 | vendor: {
76 | test: /[\\/]node_modules[\\/]/,
77 | name: "vendors",
78 | chunks: "all",
79 | },
80 | },
81 | },
82 | },
83 | plugins: [new MiniCssExtractPlugin()],
84 | };
85 |
86 | module.exports = (env, argv) => {
87 | if (argv.hot) {
88 | // Cannot use 'contenthash' when hot reloading is enabled.
89 | config.output.filename = "[name].[hash].js";
90 | }
91 |
92 | return config;
93 | };
94 |
--------------------------------------------------------------------------------