React + TypeScript + Vite で babel-plugin-react-intl-auto を使ってみる
はじめに
React で開発する場合、公式で説明されている Create React App を使うのが一般的です。ただし、Create React App は簡単に使える反面、カスタマイズが難しいという課題があります。たとえば以下のような問題があります。
- Create React App が Jest に依存しているため、バージョンを変更できない
- Create React App の内部設定を書き換えられない (eject するか react-app-rewired や craco などのツールが必要)
- ビルドが遅い
このような課題を解決するため、Create React App ではなく Vite へ移行する流れが進んでいます。初級者が使う場合は Create React App も便利ですが、中級者以上でカスタマイズしたい場合は Vite への移行も選択肢となります。
React で i18n を行う場合(または単に文字列をリソース管理したい場合)、react-intl を利用します。react-intl をより使いやすくするための babel-plugin-react-intl-auto というプラグインがあります。詳細は作者の記事を参照してください。
ここでは、これを Vite で利用できるようにする方法を紹介します。
サンプルコード
実行環境の構築
まず Vite でプロジェクトを作成します。後述しますが、Storybook が現時点では React 18 に対応していないため、React 17 の最新版である vite@2.9.0 を使用します。
npm create vite@2.9.0 my-application -- --template react-ts
react-intl を追加します。
npm install react-intl
babel-plugin-react-intl-auto を追加します。合わせて extract-react-intl-messages もインストールすると便利です。
npm install babel-plugin-react-intl-auto extract-react-intl-messages --save-dev
TypeScript が babel-plugin-react-intl-auto を認識できるように tsconfig.json を修正します。
"include": [
"src",
+ "node_modules/babel-plugin-react-intl-auto/**/*.d.ts"
],
Vite が babel-plugin-react-intl-auto を認識できるように vite.config.json を修正します。
export default defineConfig({
plugins: [
react(
+ {
+ babel: {
+ plugins: [
+ 'react-intl-auto'
+ ]
+ }
+ }
)]
})
extract-react-intl-messages を実行できるように babel.config.js を作成します。
module.exports = {
plugins: [
'react-intl-auto'
]
};
src フォルダーに messages.ts を作成します。
import { defineMessages } from 'react-intl';
const messages = defineMessages({
HelloWorld: 'こんにちは世界'
});
export default messages;
src/translations フォルダーに ja.json を生成します。
npx extract-messages -l ja -o src/translations --flat src/messages.ts
src/translations フォルダーの ja.json を修正します。
{
"src.HelloWorld": "こんにちは世界"
}
src フォルダーに translations.ts を作成します。
import ja from './translations/ja.json';
const translations: { [key: string]: Record<string, string> } = {
ja
};
export default translations;
src フォルダーの App.tsx を修正します。
- import { useState } from 'react'
+ import React, { useState } from 'react'
import logo from './logo.svg'
import './App.css'
+ import { FormattedMessage, IntlProvider } from 'react-intl'
+ import translations from './translations'
+ import messages from './messages'
function App() {
const [count, setCount] = useState(0)
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>Hello Vite + React!</p>
<p>
<button type="button" onClick={() => setCount((count) => count + 1)}>
count is: {count}
</button>
</p>
<p>
Edit <code>App.tsx</code> and save to test HMR updates.
</p>
<p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
{' | '}
<a
className="App-link"
href="https://vitejs.dev/guide/features.html"
target="_blank"
rel="noopener noreferrer"
>
Vite Docs
</a>
</p>
+ <p>
+ <IntlProvider locale='ja' messages={translations.ja}>
+ <FormattedMessage {...messages.HelloWorld} />
+ </IntlProvider>
+ </p>
</header>
</div>
)
}
export default App
npm run dev
を実行し、http://localhost:3000
にブラウザーでアクセスします。こんにちは世界 と表示されることを確認します。
テスト環境の構築
テストは Jest と Storybook で行います。
Jest
先述の通り、Create React App とは異なり Vite には Jest が含まれていないため、インストールから始めます。TypeScript で Jest を利用する場合、ts-jest を使うことが多いです。ただし、ts-jest では Babel プラグインの読み込みがうまくいかなかったため、babel-jest を利用します。また、Babel の関連プリセットも追加します。
npm install jest jest-environment-jsdom @types/jest @testing-library/react@12 @testing-library/jest-dom --save-dev
npm install babel-jest @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript --save-dev
jest.config.js を作成します。
module.exports = {
testEnvironment: 'jsdom',
testMatch: [
'**/*.test.ts',
'**/*.test.tsx'
],
moduleNameMapper: {
"\\.(css|svg)$": '../jest.mock.js'
}
};
jest.mock.js を作成します。CSS と SVG をモックするだけなので中身は空です。
module.exports = {};
babel.config.js を修正します。Jest の公式サイトにも説明がある通り、ターゲットとして node を指定する必要があります。
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: {
node: "current"
}
}
],
"@babel/preset-react",
"@babel/preset-typescript"
],
plugins: [
'react-intl-auto'
]
};
src フォルダーに App.test.tsx を追加します。
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
describe('App', () => {
it('create shapshot', () => {
render(<App />);
expect(screen.queryAllByText(/^.*$/)[0]).toMatchSnapshot();
});
});
npx jest
を実行します。スナップショットが作成されることを確認します。
Storybook
Storybook には Vite 向けのビルダーがあり、簡単に構築できます。
npx sb init --builder @storybook/builder-vite
Babel プラグインを有効にするには .storybooz フォルダーの main.js に viteFinal を設定します。
viteFinal: (config) => {
const react = require("@vitejs/plugin-react");
config.plugins = [
...config.plugins.filter((plugin) => {
return !(
Array.isArray(plugin) && plugin[0].name === "vite:react-babel"
);
}),
react({
exclude: [/\.stories\.(t|j)sx?$/, /node_modules/],
babel: {
plugins: [
'react-intl-auto'
]
},
}),
];
return config;
}
src フォルダーに App.stories.tsx を作成します。
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import App from './App';
export default {
title: 'App',
component: App,
} as ComponentMeta<typeof App>;
const Template: ComponentStory<typeof App> = (args) => <App />;
export const Primary = Template.bind({});
Primary.args = {};
npm run storybook
を実行すると こんにちは世界 と表示されます。
おわりに
Storybook については @storybook/builder-vite のメンテナーである Ian さんに教えていただきました。ありがとうございました。
Discussion