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 プラグインを有効にするには .storybook/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