LoginSignup
6
3

More than 3 years have passed since last update.

Express+TypeScript で React をテンプレートエンジンとして使用する

Last updated at Posted at 2019-10-23

Express+TypeScript で Server Side Rendering (SSR) して、React をテンプレートエンジンとして使用してみます。Babel は使用しません。

TypeScript は JSX に対応しているので、JSX を変換するだけなら Babel は不要です。

express-react-views をかなり参考にしています。

ディレクトリ構成

.
├── app.ts
├── createEngine.ts
├── package.json
├── pages/
│   └── Hello.tsx
└── tsconfig.json

設定ファイル

package.json
{
  "name": "react-ssr-ts",
  "version": "1.0.0",
  "license": "CC0-1.0",
  "private": true,
  "scripts": {
    "dev": "ts-node-dev --no-notify app.ts",
    "build": "tsc",
    "start": "node dist/app.js"
  },
  "dependencies": {
    "express": "^4.17.1",
    "react": "^16.11.0",
    "react-dom": "^16.11.0"
  },
  "devDependencies": {
    "@types/express": "^4.17.1",
    "@types/react": "^16.9.9",
    "@types/react-dom": "^16.9.2",
    "ts-node-dev": "^1.0.0-pre.43",
    "typescript": "^3.6.4"
  }
}

ts-node-dev はファイルに変更があると自動で再起動してくれるツールです。ts-node と node-dev を掛け合わせたようなやつです。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2019",
    "module": "commonjs",
    "jsx": "react",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  }
}

"jsx": "react" がミソです。tsc が JSX を React.createElement に置き換えてくれるので、普通に JS として実行可能になります。

ページ

pages/Hello.tsx
import React from 'react';

type HelloProps = {
  name: string;
};

const Hello: React.FC<HelloProps> = ({ name }) => (
  <h1>Hello, {name}</h1>
);

export default Hello;

ページは pages/ に置いていますが、app.ts の設定でディレクトリ名を自由に指定できます。

ページのコンポーネントは export default してください。

テンプレートエンジン

createEngine.ts
import React from 'react';
import ReactDOMServer from 'react-dom/server';

export type EngineOptions = {
  doctype?: string;
};

export const createEngine = ({ doctype = '<!doctype html>' }: EngineOptions = {}) => {
  return (path: string, options: object, callback: (e: any, rendered: string) => void): void => {
    try {
      const component = require(path).default as React.ComponentType<any>;
      const markup = ReactDOMServer.renderToStaticMarkup(
        React.createElement(component, options)
      );
      return callback(null, doctype + markup);
    } catch (e) {
      return callback(e, '');
    }
  };
};

tsx ファイルをテンプレートとして使うためのテンプレートエンジンです。渡された js または tsx ファイルを ReactDOMServer.renderToStaticMarkup で HTML の文字列に変換しています。

エントリポイント

app.ts
import express from 'express';
import { createEngine } from './createEngine';

const app = express();

const isTsNodeDev = Object.keys(require.cache).some(path => path.includes('/ts-node-dev/'));
const ext = isTsNodeDev ? 'tsx' : 'js';
app.set('views', __dirname + '/pages');
app.set('view engine', ext);
app.engine(ext, createEngine());

app.get('/:name', (req, res) => {
  res.render('Hello', { name: req.params.name });
});

app.listen(8080, () => {
  console.log('> Ready on http://localhost:8080/');
});

先ほどのテンプレートエンジンを app.engine に登録します。ページのディレクトリ名を変えたい場合は、app.set('views', __dirname + '/pages') のところを変えてください。

view engine の値を js と tsx で分けている理由ですが、ts-node-dev で実行した場合 tsx が渡されてくる一方、tsc して node dist/app.js した場合 js が渡されてくるので、それぞれの場合でテンプレートの拡張子を変える必要があります。このあたりもう少しスマートにできないものか・・・

追記: (2019-10-24)
@suin さんの『ts-node-devで起動されたかを調べる方法』を参考に、NODE_ENV で判定していたところを、ts-node-dev で起動されたかどうかで判定するよう書き換えました。ありがとうございます!

おわり

以上です。意外と簡単に React をテンプレートエンジンとして使うことができました。

普通は Next.js を使うのがいいと思います(Dynamic Routing で難がありますが)。

6
3
0

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
  3. You can use dark theme
What you can do with signing up
6
3