🐶

Chakra UIを薄く軽量に使う

2022/07/24に公開

追記

はじめに

この記事で説明しているChakraProviderの使い方は公式ドキュメントで推奨されているものではありませんのでご了承ください。

概要

  • @chakra-ui/reactではなく@chakra-ui/providerChakraProviderを使う
  • themeextendThemeを使わずcomponentsのthemeを外す
  • @chakra-ui/cliで必要な分だけの型生成を行う

実装例
https://github.com/wado63/chakra-ui-light-use-example

改めましてこんにちは

こんにちは。Chakra UIと言うReactのUIライブラリをご存知でしょうか。
僕はSassを使ったFLOCSSから入り、 styled-componentsを通り、Chakra UIへとたどり着きました。

HTMLの構造と一緒にスタイルを管理できるのと、TypeScriptの型が活かせるので書き心地がとても良いです。

Chakra UIでは以下のようにsytled-systemに沿った形式でスタイルを当てることが出来ます。

const BuildWidthChakraSection: FC<HTMLChakraProps<section>> = (props) => (
  <chakra.section {...props}>
    <chakra.h2 fontSize="20" fontWeight="bold">
      Built with Chakra UI ⚡️
    </chakra.h2>
    <chakra.p mt="8" fontSize="16" color="#999">
      A collection of websites and projects built with Chakra UI
    </chakra.p>
  </chakra.section>
);

またChakra UIでは<Modal />など機能を持ったコンポーネントを提供してくれます。
https://chakra-ui.com/docs/components/modal/usage

導入もすごい簡単で依存packageを追加して<ChakraProvider />でアプリケーションを囲ってあげればとりあえず動かすことが出来ます。
https://chakra-ui.com/getting-started

Chakra UI bundleサイズ大きい問題

HTMLにそのままCSSを当てるような形式でとても直感的に書けて簡単にUIを作れるChakra UIですが、一つ大きな問題があります。

bundleサイズめちゃでかいんです。

<Modal /><Toast />,<Drawer />など機能が作り込まれたコンポーネントをしっかり使っている場合は納得がいくのですが、styled-systemライクのスタイルの当て方をしたい僕のようなCSS手書き頑張るマンにとっては悩みの種となっています。

  • Chakra UI無し next build
// _app.tsx
import { AppProps } from "next/app";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <Component {...pageProps} />
  );
}

export default MyApp;
Page                                       Size     First Load JS
┌ ○ /                                      242 B          77.5 kB
├   /_app                                  0 B            77.2 kB
└ ○ /404                                   194 B          77.4 kB
+ First Load JS shared by all              77.2 kB
  ├ chunks/framework-84154cdd319403d1.js   45.2 kB
  ├ chunks/main-aff9f5cd95c9bd16.js        30.8 kB
  ├ chunks/pages/_app-633f4db406d1f71e.js  490 B
  └ chunks/webpack-fd82975a6094609f.js     727 B
  • Chakra UIあり next build
// _app.tsx
import { AppProps } from "next/app";
import { ChakraProvider } from "@chakra-ui/react";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>
  );
}

export default MyApp;
Page                                       Size     First Load JS
┌ ○ /                                      242 B           164 kB
├   /_app                                  0 B             163 kB
└ ○ /404                                   194 B           164 kB
+ First Load JS shared by all              163 kB
  ├ chunks/framework-7dc8a65f4a0cda33.js   45.2 kB
  ├ chunks/main-aff9f5cd95c9bd16.js        30.8 kB
  ├ chunks/pages/_app-5884c6fb68a34fcb.js  86.4 kB
  └ chunks/webpack-9b0e45c24ba97727.js     1.07 kB

_app.jsのbundleサイズが86kBも増えています。
86kB。。。すごく大きいです。

bundleサイズの問題はissueとして上がってますが、根本解決はなかなか難しそうですね。。
https://github.com/chakra-ui/chakra-ui/issues/4975

なぜbundleサイズが大きいのか

@chakra-ui/reactが提供する<ChakraProvider /><ToastProvider />が含まれている。

冒頭で紹介したChakra UIのセットアップで、アプリケーションを<ChakraProvider />で囲えば動かすことが出来ると紹介しましたが、この<ChakraProvider />には<Toast />をすぐに使えるよう<ToastProvider />が組み込まれています。

https://github.com/chakra-ui/chakra-ui/blob/main/packages/react/src/chakra-provider.tsx

@chakra-ui/providerを使う

<Toast />を使うのであればありがたいのですが、CSS手書き頑張るマンにとっては不要です。
幸いこのProviderは@chakra-ui/providerが提供する<ChakraProvider />を使っていたのでそちらを直接使うことにしました。

import { AppProps } from "next/app";
- import { ChakraProvider } from "@chakra-ui/react";
+ import { theme} from "@chakra-ui/react";
+ import { ChakraProvider } from "@chakra-ui/provider";

function MyApp({ Component, pageProps }: AppProps) {
  return (
-    <ChakraProvider>
+    <ChakraProvider theme={theme}>
      <Component {...pageProps} />
    </ChakraProvider>
  );
}

@chakra-ui/provider<ChakraProvider />はデフォルトのthemeが適用されていないので、@chakra-ui/reactからthemeを読み込むようにしています。

差し替えた後のbundleサイズを見てみると

Page                                       Size     First Load JS
┌ ○ /                                      242 B           125 kB
├   /_app                                  0 B             125 kB
└ ○ /404                                   194 B           125 kB
+ First Load JS shared by all              125 kB
  ├ chunks/framework-84154cdd319403d1.js   45.2 kB
  ├ chunks/main-aff9f5cd95c9bd16.js        30.8 kB
  ├ chunks/pages/_app-0b8d1aa93e24065f.js  47.8 kB
  └ chunks/webpack-9b0e45c24ba97727.js     1.07 kB

_app.jsのbundleサイズが86kBから47kB40kB近く下がりました🙌
<Toast />使わないだけでこれだけ下げることが出来るのは嬉しいですね。

themeも削ります

実はthemeもbundleサイズを肥大化させる要因になっています。
デフォルトのthemeは以下のようになっており、設定がオブジェクトして書かれているとてもシンプルなものです。
このthemeにはChakra UIが提供するコンポーネントのthemeも含まれているためなかなかのボリュームがあります。

https://github.com/chakra-ui/chakra-ui/blob/main/packages/theme/src/index.ts

しかしながら、このデフォルトのthemeに書かれているimport components from "./components"はCSS手書き頑張るマンに取っては不要です。
components以外のthemeも自身で定義したいので丸ごと外します。

import { AppProps } from "next/app";
- import { theme} from "@chakra-ui/react";
import { ChakraProvider } from "@chakra-ui/provider";

function MyApp({ Component, pageProps }: AppProps) {
  return (
-    <ChakraProvider theme={theme}>
+    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>
  );
}

bundleサイズがどう変わったか見てみます。

Page                                       Size     First Load JS
┌ ○ /                                      242 B           107 kB
├   /_app                                  0 B             107 kB
└ ○ /404                                   194 B           107 kB
+ First Load JS shared by all              107 kB
  ├ chunks/framework-84154cdd319403d1.js   45.2 kB
  ├ chunks/main-aff9f5cd95c9bd16.js        30.8 kB
  ├ chunks/pages/_app-d0c3f30cc5460bc8.js  29.6 kB
  └ chunks/webpack-9b0e45c24ba97727.js     1.07 kB

_app.jsのbundleサイズが47kBから30kB17kB近く下がりました🙌

Chakra UI追加前のbundleサイズと比較するとChakra UI自体で約30kBが追加されることとなりました。

これでも単体のstyled-componentsなどに比べて重たいのは間違い無いのですが、開発体験の良さがあるのでこれ以上ワガママは言いません。
(これ以上お手軽にbundleサイズ下げる方法を思いつきませんでした。)

オリジナルのthemeの型定義を生成する

先ほど重たいという理由でデフォルトのthemeを外しましたが、外しただけだと一つ困ったことが起こります。
themeとして存在しないはずのkeyが型定義に残っているためスタイルを当てる時の候補に出てきます。

存在しないkeyのsuggest

存在しないkeyを使用してしまうとスタイルが反映されないので@chakra-ui/cliを使用してカスタムthemeの型定義を作ってデフォルトの型定義を上書きしましょう。
https://chakra-ui.com/docs/styled-system/advanced-theming#theme-typings

packageを追加

$ npm install -D @chakra-ui/cli

themeのファイルを指定して実行

npx chakra-cli tokens <path/to/your/theme.(js|ts)>

とすると、node_modules内のChakra UIの型定義がカスタムthemeで上書きされます。

どのような型定義ができているかを見たい場合は--out theming.types.ts とのようにオプションを足して実行すると型定義ファイルを好きな場所に生成できます。

あとはnpm install時に、この型生成が自動的に行われるようpostinstallでscriptを指定しましょう。

"scripts": {
  "gen:theme-typings": "chakra-cli tokens <path/to/your/theme.(js|ts)>",
  "postinstall": "npm run gen:theme-typings"
},

extendThemeは使わない

公式ドキュメントに載っているextendThemeを使ってしまうと、既存のthemeが入り込んでしまうため、themeを1から作ります。

デフォルトのthemeの値を消しただけです笑

// theme.ts
import { ChakraTheme } from "@chakra-ui/react";

/**
 * typography関連を管理したい場合はここに記述する
 * @see https://github.com/chakra-ui/chakra-ui/blob/main/packages/theme/src/foundations/typography.ts
 */
const typography: Pick<
  ChakraTheme,
  "fonts" | "fontSizes" | "fontWeights" | "letterSpacings" | "lineHeights"
> = {
  fonts: {},
  fontSizes: {},
  fontWeights: {},
  letterSpacings: {},
  lineHeights: {},
};

/**
 * marginやcolorを管理したい場合はここに記述する
 * @see https://github.com/chakra-ui/chakra-ui/tree/main/packages/theme/foundations
 */
const foundations: Pick<
  ChakraTheme,
  | "borders"
  | "breakpoints"
  | "colors"
  | "radii"
  | "shadows"
  | "sizes"
  | "space"
  | "transition"
  | "zIndices"
> &
  typeof typography = {
  borders: {},
  breakpoints: {},
  colors: {},
  radii: {},
  shadows: {},
  sizes: {},
  space: {},
  transition: {
    property: {},
    easing: {},
    duration: {},
  },
  zIndices: {},
  ...typography,
};

/**
 * global stylesを変更したい場合はここに記述する
 * themeのkeyを使うことができます
 *
 * @see https://chakra-ui.com/docs/styled-system/global-styles
 */
const styles: ChakraTheme["styles"] = {
  global: {
    body: {},
    "*, *::before, &::after": {},
  },
};

/**
 * themeの設定はここに記述する
 * @see https://chakra-ui.com/docs/styled-system/color-mode#updating-the-theme-config
 */
const config: ChakraTheme["config"] = {
  useSystemColorMode: false,
  initialColorMode: "light",
  cssVarPrefix: "chakra",
};

export const theme: ChakraTheme = {
  ...foundations,
  direction: "ltr",
  components: {},
  styles,
  config,
};

あとは必要に応じてthemeで使いたいkey, valueを増やしていけば問題ないです。

  space: {
    0: "0",
    4: "4px",
    8: "8px",
    12: "12px",
    16: "16px",
    24: "24px",
    32: "32px",
    48: "48px",
    64: "64px",
  },

出来上がったもの

当初の半分以下の約30kBのbundleサイズでChakra UIを使うことが出来るようになりました。

Page                                       Size     First Load JS
┌ ○ /                                      11.1 kB         120 kB
├   /_app                                  0 B             109 kB
└ ○ /404                                   194 B           109 kB
+ First Load JS shared by all              109 kB
  ├ chunks/framework-7dc8a65f4a0cda33.js   45.2 kB
  ├ chunks/main-aff9f5cd95c9bd16.js        30.8 kB
  ├ chunks/pages/_app-f8685f270fc3a879.js  31.6 kB
  └ chunks/webpack-9b0e45c24ba97727.js     1.07 kB

まとめ

冒頭と同じですが

  • @chakra-ui/reactではなく@chakra-ui/providerChakraProviderを使う
  • themeextendThemeを使わずcomponentsのthemeを外す
  • @chakra-ui/cliで必要な分だけの型生成を行う

実装例
https://github.com/wado63/chakra-ui-light-use-example

Discussion