React + styled-components + juice でメールテンプレートの作成

React + styled-components で、サーバーサイドでのメールテンプレートの作成をすることができました。いい感じに動いています。

なお、汎用的な気がしたので、パッケージ化してみました。 

https://github.com/acro5piano/react-emailify

# 背景

Node.js + TypeScript を使ってサーバーサイドのプログラミングをしているとします。

## メールテンプレートの制約を超える

メールテンプレートの場合、

- 外部ファイルの読み込みができない
- CSSはインライン化しないと正しくレンダリングされないことがある

なので、CSSをべた書きするか、コンパイル作業が必要になります。

本記事のやり方だと、ライブラリの力でメールテンプレートということを極力意識しなくて良くなります。

## メールテンプレートを型安全にする

また、Pug等を使うと、メールテンプレートの内部を型安全にできません。せっかく TypeScript で安全にしているのに、メール部分でエラーが出たらテンション下がりますよね。

JSX での型機能を使えば、型推論を活かしつつ堅牢なメールテンプレートを作ることができます。また、フロントエンドが React だった場合に、統一したテンプレートを使えるというのもあります。

## フロントエンドの知見を活かす

styled-somponents によるコンポーネント志向のスタイリングや、 JSX でのテンプレート作成、 Props の渡し方など、フロントエンドの知見をそのままサーバーサイドでのテンプレートに活かすことができます。
場合によっては、コンポーネントと共通化できます。

アプリが React Native、 Web が React Native Dom、デスクトップが Electron、 そしてサーバーサイドのテンプレートが ReactDom/Server だったらテンション上がりそう。

# 手順

必要なモジュールをインストールします。 TypeScript 前提ですが、他でも動くはずです。

```
yarn add react react-dom styled-components juice
yarn add -D @types/react @types/react-dom @types/styled-components
```

`emailify` という HOC を作ります。

- `react-dom/server` の `renderToString` で、 React Component を HTML 文字列にコンパイルする
- juice を使って、 HTML 文字列のCSSをインライン化する


```typescript:emailify.tsx
import * as React from 'react'
import { renderToString } from 'react-dom/server'
import { ServerStyleSheet } from 'styled-components'
import juice from 'juice'

const HtmlTemplate = ({ body, styles, title }: any) => `
<!DOCTYPE html>
  <html>
    <head>
      <title>${title}</title>
      ${styles}
    </head>
    <body style="margin:0">
      <div id="app">${body}</div>
    </body>
</html>
`

const emailify = <T extends {}>(Component: React.ComponentType<T>) => (props: T, title: string) => {
  const sheet = new ServerStyleSheet()

  const body = renderToString(sheet.collectStyles(<Component {...props} />))
  const styles = sheet.getStyleTags()

  return juice(HtmlTemplate({ body, styles, title }))
}

export default emailify
```

# 使い方

使う側はこんな感じです。 `emailify` に React コンポーネントを食わせると、メール用のテンプレートを返す関数になります。

なお、 TypeScript が型推論してくれるので、 ↑ で書いたジェネリクスの部分は気にしなくて良いです。


```typescript
import * as React from 'react'
import styled from 'styled-components'
import emailify from 'react-emailify'

interface Props {
  message: string
}

// You can use styled-components, which will be inline in the email template.
const EmailContainer = styled.div`
  font-weight: bold;
  color: #888;
`

// This is a email component, which can be reused across your project
const Template = ({ message }: Props) => (
  <EmailContainer>{message}</EmailContainer>
)

// Here we emailify the template with `emailify` HOC.
const emailTemplate = emailify(Template)

// Here we compile React Compnent to string
// by invoking emailify HOC with template vars and title.
const message = 'Hello World'
const title = 'Hi from React Emailify'
const emailString = emailTemplate({ message }, title)

console.log(emailString)
```

こんな感じの HTML が出力されます。

```html
<!DOCTYPE html>
<html>
  <head>
    <title>Hi from React Emailify</title>
  </head>
  <body style="margin:0">
    <div id="app">
      <div class="sc-bdVaJa cUChTh" style="font-weight: bold; color: #888;">
        Hello World
      </div>
    </div>
  </body>
</html>
```

# メリット

- Pug などが不要
    - Reactは必要ですが
- 型安全
    - テンプレートエンジンに渡した値が `null` でエラーになった・・・みたいになりません
    - 他のコンポーネントを呼び出しても、入出力が明確
- コンポーネント志向
    - `styled-components` により CSS が閉じられているので、再利用性が高いと思われます
- 勝手にインライン CSS の HTML になる