Lets Build Single Page Application - Part I
Bài đăng này đã không được cập nhật trong 8 năm
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.
All rights reserved