Lets Build Single Page Application - Part I
Indroduction The project that we are going to build in this lets build series, called Chanto Hanashinasai Yo! , is a lightweight anime discussion forum that has some basic functionalities like authentication, open up a thread, post & discussion and basic search for specific thread. This is ...
Indroduction
The project that we are going to build in this lets build series, called Chanto Hanashinasai Yo!, is a lightweight anime discussion forum that has some basic functionalities like authentication, open up a thread, post & discussion and basic search for specific thread. This is Part I of the series and will focus on initial project setup and organization. You can find the source code here.
Technologies
- Ruby on Rails => API and Administration
- ReactJs => Javascript framework for building UI for Client
- Webpack => Build tool used to ease development process
- Babel => ES6 transplile
- NodeJS => Javascript runtime
- RSpec => Testing framework for Ruby
- Jasmine => Testing framework for Javascript
- Git => Version control
Project Structure
We'll divide this project into three separate modules.
- Client: Deal with client build using ReactJS serve by API in the JSON format.
- API: Backend service provide data in JSON format which will be serve to client.
- Admin: Admin section for administration tasks.
With following structure:
chahayo/ ----admin/ ----api/ ----app/ ----client/ --------js/ ------------actions/ ------------compoments/ ------------constants/ ------------dispatchers/ ------------stores/ --------css/ ----config/ ----public/ ...........
To create project structure like above run these commands.
$ rails new chahayo -T -d mysql $ cd chahayo $ rails plugin new admin --mountable -T -d mysql $ rails plugin new api --mountable -T -d mysql $ mkdir client
Project configuration
Rails configuration
Adding dependencies for unit testing. I decide to use pry-rails gem for debugin purpose.
group :development, :test do gem 'pry-rails' gem 'rspec-rails', '3.4.2' gem 'factory_girl_rails' gem 'database_cleaner' end
I want to load engine base on environment variable that set during development phase, so we can switch out the engine easily with modifying Gemfile. Now open Gemfile from base directory and put in this line.
gem ENV['chahayo'], path: ENV['chahayo']
This will allow us to switch engine by set environment variable chahayo to the name of engine we want like this.
$ export chahayo=api # Load API engine $ export chahayo=admin # Switch to Admin engine
Next open up app/config.rb file and add this line. This will instruct rails to look for file from the engine first before fallback into main application which effectively override main application code.
config.railties_order = [ExtEngine, :main_app, :all]
Notice ExtEngine constant, this constant represent our engine specific class which will be set by the time the engine is loaded. To set this constant when the engine is loaded, open up admin/lib/admin.rb and api/lib/api.rb and add this line accrodingly.
# admin/lib/admin.rb Kernel.const_set('ExtEngine', Admin::Engine) # api/lib/api.rb Kernel.const_set('ExtEngine', Api::Engine)
Next step is to config routes. Open config/routes.rb file and put in following lines. This will mount engine routes into engine specific domain name.
engine_domain = ExtEngine.name.split(':')[0].downcase scope subdomain: engine_domain do mount ExtEngine => '/' end
Javascript configuration
Change into client directory and create a file call package.json then paste in
{ "name": "chahayo", "version": "1.0.0", "description": "No Anime No Life", "scripts": { "start": "webpack --progress --colors --watch" }, "keywords": [ "No", "Admin", "No", "Life" ], "author": "Norin", "license": "ISC", "dependencies": { "react": "^0.14.7", "react-dom": "^0.14.7" }, "devDependencies": { "babel-core": "^6.7.4", "babel-loader": "^6.2.4", "babel-preset-es2015": "^6.6.0", "babel-preset-react": "^6.5.0", "file-loader": "^0.8.5", "webpack": "^1.12.14" } }
Then run these commands
$ npm install $ npm install -g webpack
Webpack configuration
In order to build our code and transpile it to valid javascript, we need to configure webpack to use babel-loader to transpile our code from ES6 and transform JSX to plain javascript. Create a javascript file and named it webpack.config.js and paste in the following configuration.
var path = require('path'); var webpack = require('webpack'); module.exports = { entry: { javascript: './js/boot.js', html: './index.html' }, output: { path: '../public', filename: 'main.js' }, module: { loaders: [ { test: /.js?$/, loader: 'babel-loader', exclude: /node_modules/, query: { presets: ['es2015', 'react'] } }, { test: /.html$/, loader: 'file?name=[name].[ext]' } ] }, };
So now whenever you run npm start webpack with build and transpile all js code combine with dependecies and output that js code into chahayo/public/main.js.
Final touch
If you run rake routes now you will see something like this depends on which engine you set in chahayo environment variable.
Prefix Verb URI Pattern Controller#Action root GET / static#index api / Api::Engine {:subdomain=>"api"}
As for ReactJS lets create a testing controller, named it static with index action that render index.html file from public folder.
# app/controllers/static_controller.rb class StaticController < ApplicationController def index render file: 'public/index' end end # config/routes.rb root 'static#index'
Then in client directory lets create a testing ReactJS app to test our webpack config.
// js/boot.js import React from 'react'; import ReactDOM from 'react-dom'; import App from './components/App.js'; ReactDOM.render(<App />, document.getElementById('app')); // js/components/App.js import React from 'react'; export default class App extends React.Component { render() { return <h1>Welcome to Chahayo!</h1>; } }
And here is content of index.html file.
<body> <div id="app"></div> <script src="main.js"></script> </body>
Now if you run npm start, bootup rails server and open http://localhost:3000 in the web browser you'll the Welcome to Chahayo! header on the page.
Last word
Last but not least, I want to point out that this is not a complete configuration of a whole project. We'll be adding more configuration during development in the upcoming part if necessary. We'll dive into coding some part of our application in Part II of this series, most probably with the basic authentication system.