WEBエンジニア勉強会 #10 (東京都, 渋谷)の発表資料と資料中の参考リンク、紹介ライブラリの導入手順をまとめた。
イベント名 | WEBエンジニア勉強会 #10 (東京都, 渋谷) |
---|---|
URL | https://web-engineer-meetup.connpass.com/event/104523/ |
会場 | 株式会社ミクシィ 東京都渋谷区東1-2-20 住友不動産渋谷ファーストタワー7F |
日時 | 2018/11/09(金) 19:30 〜 22:00 |
ハッシュタグ | #WEBエンジニア勉強会10 |
リポジトリ・バージョン情報
本記事はスライドで紹介している各ライブラリの導入方法と概要をまとめている。各々は以下のリポジトリをcloneすることですぐ試すことができる。
一部ずつ確認したい場合はコミットログを参照のこと。
なお、本記事は以下のバージョンの実行環境で作成されている。
$ npm version
{ npm: '5.6.0',
ares: '1.13.0',
cldr: '33.0',
http_parser: '2.8.0',
icu: '61.1',
modules: '59',
napi: '3',
nghttp2: '1.32.0',
node: '9.11.2',
openssl: '1.0.2o',
tz: '2018c',
unicode: '10.0',
uv: '1.19.2',
v8: '6.2.414.46-node.23',
zlib: '1.2.11' }
$ yarn -v
1.9.4
ベースとなるプロジェクトはcreate-react-app
によって作成されている。
$ npm install -g create-react-app
$ create-react-app --version
2.1.1
ESLint + Prettier
- ESLint
- Prettier
まず静的解析・フォーマッタの準備をする。ESLint
はcreate-react-app
のv2.1.1
がv5.6.0
しか認めないようなのでバージョンを固定している。
(本当はESLint
はcrreate-react-app
にプリインストールされているのだが、明示的にインストールしないと違うバージョンがインストールされてハマる)
$ yarn add -D eslint@5.6.0 eslint-config-react-app eslint-plugin-import eslint-plugin-react eslint-config-airbnb eslint-plugin-jsx-a11y
$ yarn add -D prettier prettier-eslint-cli eslint-config-prettier eslint-plugin-prettier
次に.eslintrc.js
という名前で設定ファイルを作成する(細かい設定内容の説明は割愛する)。
module.exports = {
parser: "babel-eslint",
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:prettier/recommended",
"prettier/react"
],
env: {
browser: true
},
plugins: ["react", "prettier", "jsx-a11y"],
rules: {
"prettier/prettier": [
"error",
{
singleQuote: true,
printWidth: 100
}
],
yoda: 0,
"no-unused-vars": 1,
"react/prefer-stateless-function": 0,
"react/jsx-filename-extension": [1, { extensions: [".js", ".jsx"] }],
"jsx-a11y/href-no-hash": "off",
"jsx-a11y/anchor-is-valid": ["warn", { aspects: ["invalidHref"] }]
},
globals: {
$: false,
describe: true,
it: true,
test: true,
expect: true,
process: true
}
};
また、自動生成ファイルはLintから外れるように、以下の内容で.eslintignore
という名前の設定ファイルも追加する。
node_modules
**/*.min.js
src/registerServiceWorker.js
src/setupTests.js
これでバージョン差異などがなければ動くはずだ。例えば、こんな感じでsrc/App.js
からセミコロンを一つ削除してみてからもう一度yarn lint
コマンドを実行してみる。
diff --git a/src/App.js b/src/App.js
index 7e261ca..8850ce6 100644
--- a/src/App.js
+++ b/src/App.js
@@ -21,7 +21,7 @@ class App extends Component {
</a>
</header>
</div>
- );
+ )
}
以下のようにPrettierのエラーが出ることがわかる。
yarn lint
yarn run v1.9.4
$ eslint src/**/*.js
Warning: React version not specified in eslint-plugin-react settings. See https://github.com/yannickcr/eslint-plugin-react#configuration.
/Users/budougumi0617/go/src/github.com/budougumi0617/wem10-react-sample/src/App.js
24:6 error Insert `;` prettier/prettier
✖ 1 problem (1 error, 0 warnings)
1 error and 0 warnings potentially fixable with the `--fix` option.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
あとは各エディタ・IDEで上記の設定を確認するように設定すればエディタ側でエラー内容を確認したり、オートフォーマットができる。
検索サービスで<エディタの名前> eslint prettier
と検索した通りに設定すれば大丈夫だろう。以下の画像はVS Codeで結果を表示する例だ。
Jest + Enzyme
- Option 1: Shallow Rendering
- Jest
- Enzyme
次にテスト用のライブラリを追加する。
create-react-app
を使っている場合、Jest
はプリセットに含まれている。
なので、Enzyme
に関連するライブラリをインストールするだけで実行可能だ。
$ yarn add -D enzyme enzyme-adapter-react-16 react-test-renderer
src
ディレクトリにsetupTests.js
という名前で初期化ファイルを追加しておく。
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
Props
を変化させるテストを試してみたいので、src/App.js
の内容を以下に書き換える。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to {this.props.text}</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
App.propTypes = {
text: PropTypes.string
};
App.defaultProps = {
text: 'React'
};
export default App;
src/__tests__
ディレクトリを作りsrc/App.test.js
をその下に移動する。
その後、src/__tests__/App.test.js
の内容を以下に書き換える。
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';
const emptyProps = {
text: 'test'
};
describe('App', () => {
describe('App-title', () => {
const cases = [
['simple test1', { text: 'Jest' }, 'Welcome to Jest'],
['simple test2', { text: 'enzyme' }, 'Welcome to enzyme']
];
describe.each(cases)('change Props', (title, override, expected) => {
it(title, () => {
const customProps = Object.assign({}, emptyProps, override);
const wrapper = shallow(<App {...customProps} />);
expect(wrapper.instance().props.text).toBe(customProps.text);
expect(wrapper.find('.App-title').text()).toBe(expected);
});
});
});
});
次にsrc/setupTests.js
を以下の内容で作成する。
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
ここまで行なってyarn test
を実行し、以下のような実行結果が得られればテストを書く準備が完了している。
Usegeの通り、Jest
は特に何も指定しないとホットリロードモードでプロセスが終わらないので、終わるときはq
を入力する
PASS src/__tests__/App.test.js
App
App-title
change Props
✓ simple test1 (13ms)
✓ simple test2 (2ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 5.871s
Ran all test suites related to changed files.
Watch Usage
› Press a to run all tests.
› Press f to run only failed tests.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press q to quit watch mode.
› Press Enter to trigger a test run.
スナップショットテスト
src/__tests__
ディレクトリを作り、src/__tests__/Snapshot.test.js
を以下の内容で作成する。
import React from 'react';
import App from '../App';
import renderer from 'react-test-renderer';
it('renders correctly', () => {
const tree = renderer.create(<App text="Snapshot" />).toJSON();
expect(tree).toMatchSnapshot();
});
この状態でyarn test
を行なうと、スナップショットテストが実行され、src/__tests__/__snapshots__/Snapshot.test.js.snap
というファイルが生成される。
ここに現在のApp
コンポーネントをレンダリングした結果が保存されて、次回以降のテストでリグレッションテストが行われる。
もし、意図的にレンダリング結果が変わる変更をした場合はこのスナップショットファイルを更新する必要がある。
そのため、以下のコマンドをpackage.json
に追加しておく。
diff --git a/package.json b/package.json
index fc2033d..e760ea4 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
+ "updateSnapshot": "react-scripts test --updateSnapshot",
"eject": "react-scripts eject",
"lint": "eslint src/**/*.js",
"eslint-check": "eslint --print-config .eslintrc.js | eslint-config-prettier-check"
Storybook
$ yarn add -D @storybook/react @storybook/addons @storybook/addon-actions @storybook/addon-links
$ yarn add -D @storybook/addon-notes @storybook/addon-knobs @storybook/addon-events
$ yarn add -D @storybook/addon-options @storybook/addon-backgrounds @storybook/addon-storyshots babel-core babel-loader
package.json
にstorybook起動用のコマンドを追加する。
diff --git a/package.json b/package.json
index e760ea4..0b3918b 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,8 @@
"updateSnapshot": "react-scripts test --updateSnapshot",
"eject": "react-scripts eject",
"lint": "eslint src/**/*.js",
- "eslint-check": "eslint --print-config .eslintrc.js | eslint-config-prettier-check"
+ "eslint-check": "eslint --print-config .eslintrc.js | eslint-config-prettier-check",
+ "storybook": "start-storybook -p 9001 -c .storybook"
},
"eslintConfig": {
"extends": "react-app"
.storybook
ディレクトリを作成し、.storybook/config.js
にファイルのロード設定をする。
import { configure } from "@storybook/react";
const req = require.context("../stories", true, /.stories.js$/); // <- import all the stories at once
function loadStories() {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);
.storybook/addons.js
を作成してアドオンを読み込む設定をする。
import "@storybook/addon-actions/register";
import "@storybook/addon-links/register";
import "@storybook/addon-events/register";
import "@storybook/addon-notes/register";
import "@storybook/addon-options/register";
import "@storybook/addon-knobs/register";
import "@storybook/addon-backgrounds/register";
stories
ディレクトリを作成し、動作確認用にstories/index.stories.js
を作成する。
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { Button } from '@storybook/react/demo';
import { withKnobs, text, boolean, number } from '@storybook/addon-knobs';
storiesOf('Button', module)
.addDecorator(withKnobs)
.add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>)
.add('with some emoji', () => (
<Button onClick={action('clicked')}>
<span role="img" aria-label="so cool">
😀 😎 👍 💯
</span>
</Button>
));
// second stories
const stories = storiesOf('Storybook Knobs', module);
// Add the `withKnobs` decorator to add knobs support to your stories.
// You can also configure `withKnobs` as a global decorator.
stories.addDecorator(withKnobs);
// Knobs for React props
stories.add('with a button', () => (
<button disabled={boolean('Disabled', false)}>{text('Label', 'Hello Storybook')}</button>
));
// Knobs as dynamic variables.
stories.add('as dynamic variables', () => {
const name = text('Name', 'Arunoda Susiripala');
const age = number('Age', 89);
const content = `I am ${name} and I'm ${age} years old.`;
return <div>{content}</div>;
});
yarn storybook
を実行してlocalhost:9001
にStorybookが表示されれば成功だ。
以下のようにブラウザで各コンポーネントを操作できる。
Netlifyでデプロイする
まずここまでのソースコードをGitHubに公開しておく。
create-react-app
を使ったプロジェクトならばGit用の設定が済んでいるので以下のコマンドで全てコミットする。
$ git add --all
$ git commit -v -a
GitHubにリポジトリを作ったら、そのリポジトリのURLをリモートリポジトリとして登録し、変更をプッシュする。
$ git remote add git@github.com:${USER_NAME}/${REPO_NAME}.git
$ git pull
# rebaseしてリモートのinit commitとマージしておく
$ git rebase origin/master
$ git push origin master
- Netlify
GitHub連携などで、サインアップしたあとに以下のURLをクリックする(もしくはログイン後のページで「New site from Git」ボタンをクリックする)。
- Create a new site
あとは作成したリポジトリを選ぶ。「3. Build options, and deploy!」ページの設定はそのまま「Deploy site」ボタンをクリックする
そのままビルドを待っていればサイトが公開される。
- Demo site
公開されるURLはランダム値になるため、上記のURLは以下のページからURLを指定の文字列に変更している。
以降はなにもしなくてもGitHubのmasterブランチにプッシュするたびに自動でビルドされて更新される。
様々な設定ができるが、例えばビルド時に通知が欲しい場合は以下に登録すればよい。自分の場合はSlackの通知などを設定している。
- Deploy notifications
終わりに
「コミットログきれいなサンプルリポジトリ作っておこう!」と思って改めてプロジェクトを作成したのだが、2,3ヶ月前にcreate-react-app
したときとだいぶ中身が変わっていた気がする。もし動かなかったときはバージョンを確認してもらいたい。