Fullstack Rails/React #1.1: Налаштовуємось

rr

Отож, друзі, як і обіцяв, ми продовжуємо :-)

Минула стаття

Сьогодні ми розгорнемо Rails із master гілки, створимо наш перший api-застосунок, а також зробимо всі базові налаштування; створимо базову структуру нашого фронтенд застосунку, встановимо базові залежності, такі як react, webpack, babel, express, налаштуємо express сервер, а також напишемо наш hello world.

Я дуже надіюся, що у вас вже встановлені ruby (2.2.1) та nodejs (0.12/4.0.0). І ви хоч раз із ними працювали, і пояснювати вам що це і для чого це не потрібно.

Проте якщо це не так, прошу звертатись до мене за допомогою контактів, які розташовані нижче.


Ruby on Rails 5

Я люблю все нове, люблю тестувати. Так як поки я не користуюся зовсім нестабільними фічами Рельси версії 5, такі як Action Cabble, Turbolinks3, а всього лиш rails-api gem, який об’єднали із ядром, то можу без проблем* створити новий застосунок з допомогою командrails new todoapp-server --api

Ви, звичайно, можете працювати зі звичною для вас Rails 4.2, та встановивши rails-api gem, створити новий застосунок із допомогою rails-api new todoapp-server.

[* не зовсім без проблем, так як тепер по-іншому потрібно запускати наші рельси (дивись нижче)]

То ж, перше що нам потрібно, клонувати репозиторій rails, вказавши куди, щоб не засмічувати наш проект:

$ git clone https://github.com/rails/rails.git ~/path_to_folder/rails

Після того як клонування пройшло успішно (а це може зайняти трішки часу), зайти в папку проекту і запустити команду:

$ bundle install     # також можна використовувати просто bundle

І все, готово.

todoapp-server

Після встановлення всіх залежностей, можемо створити наш застосунок:

$ bundle exec path_to_rails/railtities/exe/rails new path_to_project/todoapp-server --api --edge
$ cd path_to_project/todoapp-server
$ bundle

Все готово, у нас є новий проект на rails 5.

Давайте тепер подивимось що у нас в Gemfile.

source 'https://rubygems.org'

gem 'rails', github: "rails/rails"
gem 'sprockets-rails', github: "rails/sprockets-rails"
gem 'sprockets', github: "rails/sprockets"
gem 'sass-rails', github: "rails/sass-rails"
gem 'arel', github: "rails/arel"
gem 'rack', github: "rack/rack"

# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Unicorn as the app server
# gem 'unicorn'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Use ActiveModelSerializers to serialize JSON responses
gem 'active_model_serializers', '~> 0.10.0.rc2'

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# gem 'rack-cors'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug'
end

group :development do
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

В принципі нічого незвичного, чи не так? Але є одна відмінність: немає групи ґемів, яка відповідає за assets. Все тому що нам не потрібні компілятори sass чи coffee для api-сервера, також не потрібні jQuery, Turbolinks чи щось у тому роді.

Крім того у нас Active Model Serializers, який буде серіалізувати (видозмінювати) наш json так, як нам потрібно.

Так як ми плануємо розміщувати наші сервер і фронтенд на одному домені (у нашому випадку це localhost), то нам потрібно дозволити Cross-Origin Resource Sharing (CORS), тобто дозволити запити в контексті одного домену. Це можна зробити наступним чином: знайдіть лінійку із gem 'rack-cors' та відкоментуйте її і ще раз запустіть команду bundle install.

А тоді вставити (скоріше відкоментувати) невеличкий блок коду в файл todoapp-server/config/initializers/cors.rb

# todoapp-server/config/initializers/cors.rb

Rails.application.config.middleware.insert_before 0, "Rack::Cors" do
  allow do
    origins 'http://localhost:3000' # адреса нашого майбутнього фронтенд застосунку

    resource '*', # можна обмежити доступ лише до певних ресурсів
    # крім того можна дозволити лише певні види хедерів та запитів
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

І все, готово. Тепер можна запустити наш сервер на порті 3001 з допомогою команди:

$ bundle exec bin/rails server -p 3001

Якщо ви ведете розробку з-під vagrant, вам доведеться також вказати ip-адресу 0.0.0.0, щоб мати доступ на хост-машині через localhost:3001.

$ bundle exec bin/rails server -b 0.0.0.0 -p 3001

а також прописати в Vagrantfile наступне:

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  ...

  config.vm.network :forwarded_port, guest: 3000, host: 3000
  config.vm.network :forwarded_port, guest: 3001, host: 3001

  ...

end

Тепер можна й закомітити наші зміни:

$ git init
$ git add .
$ git commit -am "setup rails 5 project"

React

Розібравшись із нашим сервером, можна приступити до налаштування клієнтської сторони.

Залежності

Ми будемо встановлювати всі залежності через npm, тому у папці todoapp-client виконуємо команду ініціалізації нового проекту npm:

$ npm init  

та просто клікаємо enter, якщо ви не хочете заповняти всі поля.

Крім того давайте встановимо наші залежності:

$ npm i --save react react-dom

Якщо ви з-під вагранту, і у вас виникають якісь містичні помилки під час встановлення, вам, можливо, доведеться в кінці кожної команди дописувати --no-bin (принаймні мені потрібно це робити).

Крім того нам потрібні ще розробницькі залежності.

Отож, ми будемо писати на EcmaScript 2015 (також es6, es next і т.д.) – останній стандарт Javascript, і для того щоб це діло працювало у всіх-всіх браузерах, змушені використовуватиbabel, який конвертує все в es5; бандлити все діло буде в нас webpack; крім того ми хочемо, щоб наші компоненти рендерились заново, якщо відбуваються зміни в їх файлі, тому ми будемо використовувати react-transform; піднімати наш весь фронт-енд будемо на expressсервері.

Встановимо спершу babel:

$ npm i --save-dev babel-core babel-loader

Тепер встановимо webpack та декілька мідлварів до нього:

$ npm i --save-dev webpack webpack-hot-middleware webpack-dev-middleware

# крім того вам можливо буде потрібно встановити вебпак глобально

$ npm i -g webpack

Давайте встановимо тепер react-transform зі всією інфраструктурою:

$ npm i --save-dev babel-plugin-react-transform react-transform-catch-errors react-transform-hmr redbox-react

[Пічалька для користувачів вагранту: react-transform та react-hot-loader чомусь не працють з-під вагранту, якщо хтось знайде вирішення цієї проблеми, будь ласка, напишіть мені]

І на останок потрібно встановити express

$ npm i --save-dev express

А також не забути закомітити наші зміни:

$ git add .
$ git commit -am "add base dependencies for todoapp-client"

Базові налаштування

Встановивши всі потрібні нам залежності, можна приступити до створення різних конфігураційних файлів.

Розпочнімо із .babelrc, який є файлом налаштувань babel:

// .babelrc

{
  "stage": 0, // зазвичай краще так не робити, але...
  "env": {    // а тут ми налаштовуємо наш react-transform
    "development": {
      "plugins": ["react-transform"],
      "extra": {
        "react-transform": {
          "transforms": [{
            "transform": "react-transform-hmr",
            "imports": ["react"],
            "locals": ["module"]
          }, {
            "transform": "react-transform-catch-errors",
            "imports": ["react", "redbox-react"]
          }]
        }
      }
    }
  }
}

Крім того, створимо два конфіга webpack – один для розробки, інший для продакшену:

// webpack.config.dev.js

// весь наш застосунок буде в папці app
// вхідною точкою нашого застосунку буде файл app/index.js
// бандлитись все буде в папку dist/bundle.js

var path = require('path');
var webpack = require('webpack');

module.exports = {
  devtool: 'eval',
  entry: [
    'webpack-hot-middleware/client',
    './app/index' 
  ],
  output: {
    path: path.join(__dirname, 'dist'), 
    filename: 'bundle.js',
    publicPath: '/static/'
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ],
  module: {
    loaders: [{
      test: /\.js$/,
      loaders: ['babel'],
      include: path.join(__dirname, 'app')
    }]
  }
};
// webpack.config.prod.js

var path = require('path');
var webpack = require('webpack');

module.exports = {
  devtool: 'source-map',
  entry: [
    './app/index'
  ],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js',
    publicPath: '/static/'
  },
  plugins: [
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.DefinePlugin({
      'process.env': {
        'NODE_ENV': JSON.stringify('production')
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false
      }
    })
  ],
  module: {
    loaders: [{
      test: /\.js$/,
      loaders: ['babel'],
      include: path.join(__dirname, 'app')
    }]
  }
};

А тепер давайте напишемо наш розробницький сервер:

// server.js

var path = require('path');
var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.config.dev');

var HOST = 'localhost';
var PORT = 3000;

var app = express();
var compiler = webpack(config);

app.use(require('webpack-dev-middleware')(compiler, {
  noInfo: true,
  publicPath: config.output.publicPath
}));

app.use(require('webpack-hot-middleware')(compiler));

app.get('*', function(req, res) {
  res.sendFile(path.join(__dirname, '/app/index.html'));
});

app.listen(PORT, HOST, function(err) {
  if (err) {
    console.log(err);
    return;
  }

  console.log('Listening at http://' + HOST + ':' + PORT);
});

А ще нам потрібно вписати декілька скриптів для npm:

// package.json

...

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "NODE_ENV=production webpack --config webpack.config.prod.js",
    "start": "node server.js"
  }

...

Ми круті! Ми вже на фінішній прямій до закінчення всіх налаштувань.

Можна відзначити це коммітом:

$ git add .
$ git commit -am "add base configs"

Базова структура та Hello World!

Розпочнімо з того, що створимо папку app, а в ній папку components, та файл index.js таindex.html:

$ mkdir app && cd app
$ mkdir components
$ touch index.js
$ touch index.html

Спершу створимо базову структуру в файлі index.html:


<!doctype html>
<html>
  <head>
    <title>Simple Todo App</title>
  </head>
  <body>
    <div id='root'> <!-- сюди рендериться реакт-застосунок -->
    </div>
    <script src="/static/bundle.js"></script>
  </body>
</html>

Продовжимо це все тим, що напишемо наш файл, якиє є вхідною точкою нашого застосунку –index.js, де ми просто рендеримо наш компонент TodoApp, який ми зараз напишемо, в ноду нашого index.html, все просто:

// index.js

import React from 'react';
import ReactDOM from 'react-dom';

import {TodoApp} from './components';

const rootElement = document.getElementById('root');

ReactDOM.render(<TodoApp />, rootElement);

Ну і напишемо наш Hello World!

// components/TodoApp.js

import React, {Component} from 'react';

export default class TodoApp extends Component {
  render() {
    return (
      <div>Hello World!</div>
    );
  }
}

А також покладемо в папку із нашими компонентами файл index.js, який буде всього лиш експортувати усі наші компоненти із даної папки, щоб пізніше можна було написати
import {Component, AnotherComponent} from './components';:

// components/index.js

export TodoApp from './TodoApp';

Ну і все, дітки, можемо запустити наш сервер, перейти у браузер і побачити Hello World!:

$ npm start

І все працює! Ура!

Тепер можна й закомітити:

$ git add .
$ git commit -am "Hello world"

Підводне каміння

Під час розробки ви можете зіткнутись із деякими проблемами. Можу вас запевнити, що код цей весь працює, так як я його пишу тоді ж, як пишу статтю.

  • Можливо у вас буде в терміналі помилка, що не можливо знайти якийсь модуль, то спробуйте запустити npm i ще раз.
  • Можливо в консолі браузера пише, що реакт не може створити елемент, який null – перегляньте чи немає десь звичайної синтаксичної помилки, або ж чи всюди є export(власне у мене бувало, що я зависав на довший час і не міг зрозуміти чому не працює, а я всього лиш забув export прописати в файлі компонента) та чи правильний import.
  • Не забувайте користуватись console.log(), якщо потрібно.
  • Ще раз наголошую, що react-transform та react-hot-loader не працють з-під ваграрнту, і якщо ви їх якось подружили – напишіть мені, буду дуже вдячний.

Що далі?

В наступній статті ми нарешті додамо якісь фічі до наших сервера та клієнта:

  • будемо використовувати redux!
  • зможемо інітіалізувати наш застосунок;
  • зможемо додавати тудушки;
  • видаляти наші тудушки;
  • позначати їх виконаними.

А стаття вже зовсім скоро!

Корисні посилання:

Тут невеличка збірка корисних посилань, які можуть вам бути у нагоді зараз:

Контакти

Якщо вам подобається те що я роблю, або ж не подобається, або ж хочете мене поправити, чи може допомогти, чи просто вам нудно і хочете з кимось поспілкуватись, можете мені написати:

Репозиторій постійно буде знаходитись за посиланням
codeguida/fullstack-rr.