Integrate ReactJS to a CakePHP 2.x Application from Scratch
This is a basic integration of ReactJS into a CakePHP 2.x application.
Before we start, make sure you already have a running CakePHP 2.x application.
If you don’t have a running CakePHP app, you may clone this basic CakePHP 2.x application running on Docker: https://github.com/onoya/dockerized-cakephp-app.(This is an application from my blog post about Creating CakePHP 2.x Application with Docker from Scratch).
You may download the final result of this setup in GitHub.
Prerequisites
- Node.js (I recommend installing the latest stable version)
- Yarn or NPM (In this post, I’m going to use Yarn)
Make sure to have all the prerequisites above installed.
I recommend using NVM for installing and managing node versions. Here is a great article on how to install NVM to your machine: The Best Way to Install Node.js.
Getting Started
Once you have all the prerequisites, go to the project repo, and generate the package.json file with yarn::
yarn init -y
After running the command above, a package.json
file will be generated.
Before we start installing some dependencies, if you are using Git to manage this project, you’ll have to update the .gitignore
file and append the following lines below. If not, you may proceed to the next step.
/node_modules yarn-error.log
Webpack and Babel
The first dependency we’re going to install is going to be webpack.
yarn add -D webpack webpack-cli
We’re going to be using JSX and ES6, but not all browsers can understand them. So we will be needing Babel to transpile next generation JavaScripts into a backwards compatible version of JavaScript code.
yarn add -D babel-loader @babel/core @babel/preset-env @babel/preset-react
As you can see, we have installed some presets. @babel/preset-env handles compiling ES6 into ES5, and @babel/preset-react handles compiling JSX into JavaScript.
Next we’re going to configure Babel to use the presets that we have installed by creating .babelrc
file inside the project root directory.
{ "presets" : ["@babel/preset-env", "@babel/preset-react"] }
Now we’re going to write a basic webpack configuration. Create the configuration file webpack.config.js
.
const path = require('path'); const BUILD_DIR = path.resolve(__dirname, 'app/webroot/'); const APP_DIR = path.resolve(__dirname, 'app/src/'); const config = { entry: `${APP_DIR}/index.js`, output: { path: `${BUILD_DIR}/js/`, filename: 'bundle.js', }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, include: APP_DIR, use: { loader: 'babel-loader', }, }, ], }, }; module.exports = config;
As you can see from the configuration above, app/src/index.js
will be the entry point.
It will compile it, and output a bundle.js
file inside app/webroot/js/
.
So we have to create the app/src
directory and create index.js
inside.
mkdir -p app/src touch app/src/index.js
Now let’s add a build script inside package.json
.
{ "name": "cakephp2-react-integration", "version": "1.0.0", "main": "index.js", "repository": "git@github.com:onoya/cakephp2-react-integration.git", "author": "Naoya Ono <ono.naoyaa@gmail.com>", "license": "MIT", "scripts": { "build": "webpack -p", "build-dev": "webpack -d --watch" }, "devDependencies": { "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-preset-env": "^1.6.1", "babel-preset-react": "^6.24.1", "webpack": "^3.10.0" } }
We have added 2 scripts: build and build-dev. We use build to bundle our React app for production, and build-dev for development.
We’re done with Webpack and Babel setup.You may try building the development version of bundle.js
by running the command below:
yarn build-dev
If you want to build the production version of it, you just have to run yarn build
instead.
ReactJS
It’s time to integrate React. Let’s install them by running the command below:
yarn add react react-dom
Before we move on to creating our first component and rendering them, let’s create a basic directory structure for our components. We’re going to create a components
directory inside app/src/
. This is where we are going to put our react components.
mkdir -p app/src/components
Let’s now create our first React component.
touch app/src/components/App.js
import React, { Component } from 'react'; class App extends Component { constructor() { super(); } render() { return ( <p>Hello World</p>); } } export default App;
After creating the component, we’ll have to edit app/src/index.js
to render the component to the DOM.
import React from 'react'; import ReactDOM from 'react-dom'; import App from './components/App'; ReactDOM.render(<App />, document.getElementById('app'));
Now let’s try to see if it’s working by replacing the content of app/View/Layouts/default.ctp
with below:
<!DOCTYPE html><html><head><?php echo $this->Html->charset(); ?> <title>React Integrated CakePHP Application</title></head><body><div id="app"></div><?= $this->Html->script('bundle'); ?> </body></html>
Our react component will be rendered in <div id="app"></div>
.
Try to open up your browser and visit the site. You should see a page with a text Hello World
.
Configure CakePHP routing for Single Page App
Our react application is working fine and rendered correctly.But there’s one problem with our current setup right now. Try to append the url with any text like: localhost:8000/test
.
Now try to open the error log. It’s located at app/tmp/logs/error.log
. You may see some MissingControllerException. This is due to our current routing configuration. CakePHP will try to find for the controller. To stop that, we have to update our routes file.
Open app/Config/routes.php and replace the content with the code below:
/** * Route everything to PagesController::display() */ Router::connect('/*', array('controller' => 'pages', 'action' => 'display')); /** * Load all plugin routes. See the CakePlugin documentation on * how to customize the loading of plugin routes. */ CakePlugin::routes(); /** * Load the CakePHP default routes. Only remove this if you do not want to use * the built-in default routes. */ require CAKE . 'Config' . DS . 'routes.php';
And lastly, open app/Controller/PagesController.php and replace the content of display() method.
public function display() { $this->render('home'); }
Now you won’t get those Missing Controller Exceptions anymore.
Additional Webpack Configuration
Styled components are gaining popularity nowadays. Some of the popular styling libraries such as Aphrodite and JSS will need transpilers for styling and etc. To begin with, we’ll have to install the following dependencies.
yarn add -D css-loader style-loader file-loader url-loader
Now let us update webpack.config.js
const path = require('path'); const BUILD_DIR = path.resolve(__dirname, 'app/webroot/'); const APP_DIR = path.resolve(__dirname, 'app/src/'); const config = { entry: `${APP_DIR}/index.js`, output: { path: `${BUILD_DIR}/js/`, filename: 'bundle.js', }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, include: APP_DIR, use: { loader: 'babel-loader', }, }, { test: /\.css$/, include: /node_modules/, use: ['style-loader', 'css-loader'], }, { test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' }, ], }, }; module.exports = config;
And that’s it!