はじめに
今回はBlitzにtwin.macroを導入する方法をメモしておきます。
twin.macroの良さを知ってからいつもとりあえずtwin.macroを入れられないかを考えています。
デザインはできないので他はあまり知らないですけど、twin.macroとはprogrammaticで仲良くなれそうだと思っています。
結論からいうとNext.jsへの導入方法とほとんど変わりません。
今回もemotionを使う方向で進めていきます。
環境
- Node 15.11.0
- yarn 1.22.10
- Blitz 0.39.0
プロジェクトの作成
$ blitz new blitz-twin
$ cd blitz-twin
Pick a form library (you can switch to something else later if you want) とフォームのライブラリを聞かれますがとりあえずReact Final Formを選択しておきます。
依存関係のインストール
公式に他のライブラリやフレームワークでの導入方法がありますが、 あまり大差はないかなと思っています。お決まりですかね。
$ yarn add @emotion/css @emotion/react @emotion/server @emotion/styled @emotion/client
$ yarn add -D @emotion/babel-plugin babel-plugin-macros twin.macro tailwindcss
twin.macroの設定をpackage.jsonの追加する。
twin.macroはbabelマクロを使用しますのでその設定を記述します。
こちらはお好みですがbabel-plugin-macros.config.jsに書く方法もあります。
記述量が少ないのでpackage.jsonに書いてしまっていいのかなと思っています。
package.json
{
...
+ "babelMacros": {
+ "twin": {
+ "preset": "emotion"
+ }
+ }
}
babel.config.jsの修正
こちらは悩んだところですが、以下の様な記述でOKでした。
babel.config.js
module.exports = {
- presets: ["blitz/babel"],
- plugins: [],
+ presets: [
+ [
+ "blitz/babel",
+ {
+ "preset-react": {
+ runtime: "automatic",
+ importSource: "@emotion/react"
+ }
+ }
+ ]
+ ],
+ plugins: ["@emotion/babel-plugin", "babel-plugin-macros"],
}
グローバルのスタイルの追加
ブラウザ間の表示を揃えるためにtwin.macroが提供してくれているグローバルのスタイルを追加します。
リセットCSSとかサニタイズCSSとか呼ばれているものですかね。
Next.jsと同様に_app.tsxに記述することでグローバルにスタイルを適応することができます。
src/pages/_app.tsx
import {
AppProps,
ErrorBoundary,
ErrorComponent,
AuthenticationError,
AuthorizationError,
ErrorFallbackProps,
useQueryErrorResetBoundary,
} from "blitz"
+ import { GlobalStyles } from "twin.macro"
import LoginForm from "app/auth/components/LoginForm"
export default function App({ Component, pageProps }: AppProps) {
const getLayout = Component.getLayout || ((page) => page)
return (
<ErrorBoundary
FallbackComponent={RootErrorFallback}
onReset={useQueryErrorResetBoundary().reset}
>
+ <GlobalStyles />
{getLayout(<Component {...pageProps} />)}
</ErrorBoundary>
)
}
...
twin.macro用の型定義ファイルを追加する。
twin.macroはjsx,tsx内でtw,styled,cssといったattributeを使用しますのでそちらの型定義を追加します。
直下にtypes.tsが配置されていたので今回はこちらも直下に配置します。
twin.d.ts
import 'twin.macro'
import { css as cssImport } from '@emotion/react'
import { CSSInterpolation } from '@emotion/serialize'
import styledImport from '@emotion/styled'
declare module 'twin.macro' {
// The styled and css imports
const styled: typeof styledImport
const css: typeof cssImport
}
declare module 'react' {
// The css prop
interface HTMLAttributes<T> extends DOMAttributes<T> {
css?: CSSInterpolation
}
// The inline svg css prop
interface SVGProps<T> extends SVGProps<SVGSVGElement> {
css?: CSSInterpolation
}
}
ちらつきの防止
上記までの設定でtwin.macroが使えるようになっていますが、初期レンダリング時にちらつく場合は以下のように記述することによって、 重要なスタイルを抜き出して最初に読み込むようにしてくれます。
src/pages/_document.tsx
- import {Document, Html, DocumentHead, Main, BlitzScript /*DocumentContext*/} from 'blitz'
+ import {Document, Html, DocumentHead, Main, BlitzScript, DocumentContext} from 'blitz'
+ import { extractCritical } from "@emotion/server"
class MyDocument extends Document {
- // Only uncomment if you need to customize this behaviour
- // static async getInitialProps(ctx: DocumentContext) {
- // const initialProps = await Document.getInitialProps(ctx)
- // return {...initialProps}
- // }
+ static async getInitialProps(ctx: DocumentContext) {
+ const initialProps = await Document.getInitialProps(ctx)
+ const page = await ctx.renderPage()
+ const styles = extractCritical(page.html)
+ return {
+ ...initialProps,
+ ...page,
+ styles: (
+ <>
+ {initialProps.styles}
+ <style
+ data-emotion-css={styles.ids.join(' ')}
+ dangerouslySetInnerHTML={{ __html: styles.css }}
+ />
+ </>
+ )
+ }
+ }
...
}
export default MyDocument
おわりに
Blitzもtwin.macroもとても良いなと思っているので合わせられるのが嬉しいです。 twin.macroの導入方法ですが一部不明な部分もありますのでそちらも調べたいなと思っています。