React + styled-components で、サーバーサイドでのメールテンプレートの作成をすることができました。いい感じに動いています。
なお、汎用的な気がしたので、パッケージ化してみました。
背景
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をインライン化する
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 が型推論してくれるので、 ↑ で書いたジェネリクスの部分は気にしなくて良いです。
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 が出力されます。
<!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 になる