PHP with ReactJs - End to end setup
Setting up ReactJs with PHP 7 using Laravel framework
tl;dr When I had published an article about setting up development workflow for ReactJs, it was just a basic introduction to production ready dev. In this article I’ll be discussing about setting up development enviournment with PHP, where I’ll be covering some basics which will be helpful in case you want to setup with any other language than PHP with ReactJs. As framework in PHP I’ve selected Laravel 5.1 to explain things in this article, but it’ll be applicable to all the frameworks.
Before we start, I would like to mention that all the steps mentioned here are tested on Ubuntu 14.04 server, but if you are using Mac OS or Windows I will suggest you to take a look at Vagrant if it helps, also take a look at VagrantFile in case you choose to install vagrant.
Before we start, we need LAMP stack running so that we can test our changes, here is an article by Justin Ellingwood which I would suggest to follow up for installing and setting up LAMP stack. Or if you would like to install and setup PHP 7, fire below commands in your terminal and follow the instructions-
sudo LC_ALL=en_US.UTF-8 add-apt-repository ppa:ondrej/php
sudo apt-get update
sudo apt-get install -y language-pack-en-base mysql-server-5.6 apache2 php7.0 php7.0-mysql php7.0-cli php7.0-mcrypt php7.0-curl php7.0-mbstring php7.0-fpm php7.0-xml php7.0-xmlrpc curl git zip unzip libapache2-mod-php7.0 nodejs npm
curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer
sudo mysql_secure_installation
sudo ln -s `which nodejs` /usr/bin/node
Now we can create Laravel project by running following command-
cd ~/
composer create-project --prefer-dist laravel/laravel <MyProject>
Where you can replace the projcet name MyProject with desired name; after you run this command it will fetch all the dependency under the MyProject directory in our case. Once it is done, you can configure Laravel directory to be served by Apache by pointing to ~/MyProject/public
directory. My sample apache configuraion looks like below snippet-
<VirtualHost *:80>
ServerAdmin me@example.com
DocumentRoot /path/to/public/dir
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
<Directory /path/to/public/dir>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
allow from all
</Directory>
</VirtualHost>
And below is the content of ~/MyProject/public/.htaccess
file, which allows us to have clean URL-
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>
RewriteEngine On
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R=301]
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
In case you want to install Sass, here is the article which can be followed. Now let’s take a look at directory structure of the project we created in ~/MyProject/
MyProject/
app/
bootstrap/
config/
database/
migrations/
public/
.htaccess
app.css
index.php
js/
resources/
sass/
componenets/
lang/
views/
storage/
tests/
vendor/
.env
...
So here is breif description about the directories which we’ll discuss in the article-
- app- The application code resides under this direcotory.
- public- This directory keeps all the public assets, and if you will notice this is the same directory which we have added in apache configuration.
- resources- This directory keeps all the raw Sass and React components.
Now create a file as ~/MyProject/resources/components/package.json
with following content-
{
"name": "components",
"version": "0.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Ritesh Shrivastav",
"license": "BSD-2-Clause",
"devDependencies": {
"babel-loader": "~6.2.4",
"babel-plugin-add-module-exports": "~0.1.2",
"babel-plugin-react-html-attrs": "~2.0.0",
"babel-plugin-transform-class-properties": "~6.6.0",
"babel-plugin-transform-decorators-legacy": "~1.3.4",
"babel-preset-es2015": "~6.6.0",
"babel-preset-react": "~6.5.0",
"babel-preset-stage-0": "~6.5.0"
},
"dependencies": {
"history": "~2.0.1",
"react": "~0.14.7",
"react-dom": "~0.14.7",
"react-router": "~2.0.0",
"webpack": "~1.12.14",
"axios": "~0.9.1",
"react-helmet": "~3.0.1"
}
}
And ~/MyProject/resources/components/webpack.config.js
with following content-
var fs = require('fs');
var path = require('path');
var webpack = require('webpack');
module.exports = {
devtool: 'source-map',
entry: './client.js',
output: {
path : __dirname + '../../../public/js/build',
filename : '[name].js',
chunkFilename : '[name]-[chunkhash].js',
publicPath : '/js/build/'
},
progress: true,
module: {
loaders: [ {
test : /\.js?$/,
loader : 'babel-loader',
exclude : /node_modules/,
query : {
presets : ['react', 'es2015', 'stage-0'],
plugins : ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy']
}
} ]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress : {
warnings : false
}
}),
new webpack.optimize.CommonsChunkPlugin('shared.js'),
new webpack.optimize.MinChunkSizePlugin({minChunkSize: 1000}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production'),
}
})
]
}
If you will notice the content of webpack configuration, I have specified client.js to be entry point. Below is the content of client.js file, which should be kept under the components/ directory-
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import App from './app/App';
import NotFound from './app/errors/NotFound';
import React from 'react';
import ReactDOM from 'react-dom';
const app = document.getElementById('app');
ReactDOM.render(
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute
getComponent={(location, callback) => {
require.ensure([], function (require) {
callback(null, require('./app/native/NativeSearch').default);
});
}}
/>
<Route
path="search"
getComponent={(location, callback) => {
require.ensure([], function (require) {
callback(null, require('./app/native/SearchResult').default);
});
}}
/>
<Route name="404" path="*" component={NotFound} />
</Route>
</Router>,
app);
Few basic notes about client.js
- On the top of client.js, if you will notice I have imported modules in which App, NotFound are the react components. If you are saving react components with .jsx extension then this will not work instead you need to import those modules by writing complete extension of the file. But if you want to avoid that, save file with .js extension.
- Webpack provides require.ensure method, which can be used to split the code into chunks.
- For handling 404 routes we have configured NotFound component, so basically using
path="*"
we can handle all the requests for which it didn’t matched with the above paths.
Since we have added package.json and webpack configuration, now we can install the dependency by running following command-
cd ~/MyProject/resources/components/
npm install
# we need it
sudo npm install webpack -g
Once npm installs webpack globaly, we can use webpack command to bundle our components, and on successfull build it will generate two files shared.js and main.js. In laravel my layout file resources/views/layouts/main.blade.php
contains following code-
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" href="/app.css">
<link rel="icon" href="icon.png?v=1.0.0" type="image/png">
@yield('head')
</head>
<body>
<div id="app">
@yield('content')
</div>
<script type="text/javascript" src="/js/build/shared.js"></script>
<script type="text/javascript" src="/js/build/main.js"></script>
</body>
</html>
Which can be used by any further blade files. Below are few basic problems what each React developers face initially-
SEO friendly React apps
Having SEO friendly app is the biggest issue when we use any javascript frameworks because all search engines doesn’t execute javascript after crawling(now a days few web-crawler does it, such as google).
There are two types of routing in React: push-state, and hash-based. Each of them have their strengths and weaknesses. Here is a good writeup by James about routing.
In case if you are using push-state routing, you’ll need to handle requests from server-side for all the URLs as well. And if your application is hidden behind login screen this’ll not be the perfect option to choose. But in such situation when your application content is live to public, then it makes sense to have pretty URLs.
Speed up loading of App
When we talk about loading a single page application without any delay, generally this issue is caused because we load our whole application at once. But if we divide our code in such a manner, such that if X component is required than only it should load X’s definition. This problem can be addressed by dividing build code into chunks, and it’s done effectively by webpack and react-router; which we have used.