LoginSignup
0

More than 1 year has passed since last update.

posted at

updated at

Organization

React Starter Kitというboilerplateを使って3年たった話

:family: はじめに

本記事は、Supershipグループ Advent Calendar 2020 - Qiitaの6日目の記事になります。

:new_moon_with_face: React Starter Kitとは

まず、React Start Kitの説明からですが、React Starter Kitは当時(3年前)は最先端だったWeb技術を組み合わせKriasoft社がベストプラクティスを凝縮したBoilerplateになります。

主な構成要素は

  • React ・・・ 割愛
  • Express ・・・ node製のweb framework
  • Webpack ・・・ 設定大好き。モジュールバンドラ
  • GraphQL ・・・ Web APIの規格
  • sequelize ・・・ ORM
  • Node.js ・・・ 割愛

https://awesomestacks.dev/react-starter-kit

と一通り揃った構成となっております。

:bread: 特徴

特徴的なところは、isomorphicを採用しており、FrontendとBackendをJavascriptに共通化(同形)することによりロジックを共有することができるようになる設計を採用している所です。
こちらは、開発する上では非常に便利でフロントとバックが分かれるのが当たり前になりつつある昨今。
同じような処理をクライアントサイドとバックエンドサイドで書いてしまうなんてことが起きてしまうのですが、共通化してしまえばそんなことも無くなります。

また、GraphQLも採用されておりGraphQLを手軽に導入できます。
他にも、Flowであったりhuskyが最初っから用意されているので痒いところにも手が届いています。

:octocat: ローカル持ってきて見てみましょう

:two_men_holding_hands: Clone

$ git clone git@github.com:kriasoft/react-starter-kit.git <project-name>
$ cd <project-name>

:minidisc: Cloneが終わったらパッケージのインストール

$ yarn install

:package: パッケージのインストールが終わったら起動してみましょう

$ yarn start

startすると自動的にブラウザが起動して
http://localhost:3000/
が開いた状態になってるはずです。

script 説明
start アプリケーション起動
build ~/distにビルドされる
test Jestでユニットテストを実行
lint lint-jsとlint-cssの両方が走ります
flow:check Flowのチェックを実行(まんま..)

他にもスクリプトがありますので気が向いた時にpackage.jsonを覗いてみてください。

:books: ディレクトリ構成、ファイル紹介

次にボイラプレートのディレクトリ構成を見てみましょう(一部抜粋)
重要な所には◆マークをつけておきます。

├── babel.config.js
├── docs             // このボイラーブレートの使い方やちょっとしたサンプルなどがある
├── jest.config.js
├── package.json
├── public                // ロゴなどスタティックファイルを格納する場所
├── src                   // アプリケーションソース置き場
│   ├── config.js         // グローバル設定ファイル
│   ├── components      // Reactコンポーネント格納場所
│   │   ├── App.js
│   │   └── ...

## Backend系
│   ├── server.js         // ◆ バックエンドサイド起動スクリプト 
│   ├── data              // バックエンドのGraphQL処理系のファイル格納する場所
│   │   ├── models        // データベースモデル
│   │   ├── queries       // GraphQLのクエリ処理(データ取得)格納する場所
│   │   ├── schema.js     // GraphQLのスキーマ
│   │   ├── sequelize.js  // ORMの定義ファイル
│   │   └── types         // GraphQLのタイプ定義を格納する場所

## Client(Frontend)系
│   ├── client.js         // ◆ クライアントサイド(フロントエンド)起動スクリプト
│   ├── routes            // Clientサイドのページ処理を格納する場所
│   │   ├── about         // ページディレクトリ
│   │   ├── login         // 同上
│   │   ├── home          // 同上
│   │   ├── ...
│   │   └── index.js      // 各ページのルーティング設定ファイル
│   ├── history.js        // ブラウザヒストリー設定ファイル
│   └── router.js         // ◆ UniversalRouter設定ファイル

## テスト
├── test                  // 単体テスト格納場所

## ビルド、デプロイ処理系
└── tools
    ├── build.js            // ビルド処理
    ├── deploy.js           // デプロイ処理
    ├── ...
    ├── lib                 // utility系
    ├── postcss.config.js   // PostCSS設定ファイル
    ├── runServer.js        // nodeサーバ起動ファイル
    ├── start.js            // 開発サーバ起動ファイル(HMRとかbrowserSyncをいい感じで起動する)
    └── webpack.config.js

いかがでしょうか。
思っていたよりシンプルな構成でだったのではないでしょうか。

:eyes: server.jsをチラ見

最後にバックエンドを起動している部分をチラ見して終わりましょう

src/server.js

Express起動

説明不要かと思いますが、Expressを起動しています

server.js
const app = express();

Middlewareを設定

Expressで使用するMiddlewareを設定しています
スタティックファイルを読み込み、cookie周りも読み込んでいますね

server.js
// 
// Register Node.js middleware
// -----------------------------------------------------------------------------
app.use(express.static(path.resolve(__dirname, 'public')));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

JWT設定

認証用のトークンはJwtを使用していますね
諸々の引数をここで設定していますね

server.js
// 
// -----------------------------------------------------------------------------
app.use(
  expressJwt({
    secret: config.auth.jwt.secret,
    credentialsRequired: false,
    getToken: req => req.cookies.id_token,
  }),
);

GraphQL設定

GraphQLのエンドポイントの設定。develop環境だとGraphiQLの画面が見れる
(GraphiQLのお陰で普及率が上がってるんではないかと思うぐらい便利)

server.js
// 
// -----------------------------------------------------------------------------
app.use(
  '/graphql',
  expressGraphQL(req => ({
    schema,
    graphiql: __DEV__,
    rootValue: { request: req },
    pretty: __DEV__,
  })),
);

SSR

SSRの処理がここに書いてありますね
ざっくりですが、CSSを読み込みルーティング毎にチャンクを設定しrenderToStaticMarkupでレスポンスしているのがわかりますね

server.js
// 
// -----------------------------------------------------------------------------
app.get('*', async (req, res, next) => {
  try {
    const css = new Set();

    const insertCss = (...styles) => {
      styles.forEach(style => css.add(style._getCss()));
    };

    // Universal HTTP client
    const fetch = createFetch(nodeFetch, {
      baseUrl: config.api.serverUrl,
      cookie: req.headers.cookie,
      schema,
      graphql,
    });

    const context = {
      fetch,
      pathname: req.path,
      query: req.query,
    };

    const route = await router.resolve(context);

    if (route.redirect) {
      res.redirect(route.status || 302, route.redirect);
      return;
    }

    const data = { ...route };
    data.children = ReactDOM.renderToString(
      <App context={context} insertCss={insertCss}>
        {route.component}
      </App>,
    );
    data.styles = [{ id: 'css', cssText: [...css].join('') }];

    // ルートごとのチャンクを設定
    const scripts = new Set();
    const addChunk = chunk => {
      if (chunks[chunk]) {
        chunks[chunk].forEach(asset => scripts.add(asset));
      } else if (__DEV__) {
        throw new Error(`Chunk with name '${chunk}' cannot be found`);
      }
    };
    addChunk('client');
    if (route.chunk) addChunk(route.chunk);
    if (route.chunks) route.chunks.forEach(addChunk);

    data.scripts = Array.from(scripts);
    data.app = {
      apiUrl: config.api.clientUrl,
    };

    // 最終的にrenderToStaticMarkupしてレスポンスする
    const html = ReactDOM.renderToStaticMarkup(<Html {...data} />);
    res.status(route.status || 200);
    res.send(`<!doctype html>${html}`);
  } catch (err) {
    next(err);
  }
});

HMRの設定

HMR (Hot Module Replacement)
webpackが提供する自動でモジュールを置き換えてくれる機能です
こちらの設定もしていますね

server.js
//
// Hot Module Replacement
// -----------------------------------------------------------------------------
if (module.hot) {
  app.hot = module.hot;
  module.hot.accept('./router');
}

export default app;

一部ではありますが、ボイラープレート中の処理をご紹介いたしました。

3年経ってどうだったか

では実際に3年使ってみてどうだったかをお話ししたいと思います

良かったこと

良かったことは、やはり特徴にもあるクライアントサイドとバックエンドサイドで同じ処理が使えることですね。
同じような関数をバラバラに書かなくてもよく、クライアント/バックエンドの言語も一緒なので学習コスト面から行っても非常に生産的だと思います。

また、予め開発に便利なパッケージを用意してあるのでORMは何使おうかとかリンターやCSSの処理はどうする?などと悩まなくていいのが良かったと思います。

課題

しかし、流石に年数が立つと様々な課題が出てきました。

  • Typescriptじゃない

こちらはFlowを採用しているので型を導入はしているのですが、やはり昨今のTypescriptの勢いを見るとこちらが気になります

  • メンテされていない
    • nodeのバージョンが古いまま、パッケージも同様

現在は活発にメンテナンスはされていないようで、nodeのバージョンが>= 8.16.2となっているので、適時自分たちでメンテナンスしていかないといけません。

まとめ

良くも悪くもisomorphicを味わいたい人にはうってつけのBoilerplateなのではないでしょうか、
一度覚えればクライアントサイドもバックエンドも同一言語なのでフロントはReact(JS)でバックはGoを採用しているシステムよりは学習コストが圧倒的に低いです。
ですので、小規模から中規模のアプリケーションの作成には最適なのかと思います。
しかし、情勢が変わるのが早いのがフロントエンドの世界...
Vercel社が進めているJAMstackであったりBFFがありどれが今後隆盛を極めるのかわかりません。
月並ではありますが、構築しようとしているシステムや既存のリソースを踏まえながら適材適所使っていくしかないと思います。その選択の際に本記事が役に立つと幸いです。

一緒に働きませんか? 働きましょうよ

私の所属するチームでは、人材を募集しています。
バックエンドからフロントエンドまで幅広い技術を触ってみたい方、
広告配信に興味の方どんどんご応募ください!お待ちしています!

Supershipではプロダクト開発やサービス開発に関わる人を絶賛募集しております。
ご興味がある方は以下リンクよりご確認ください。
Supership株式会社 採用サイト
是非ともよろしくお願いします。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0