- 投稿日:2019-04-30T23:40:51+09:00
react-qr-reader を利用したQRコードリーダーの作成
はじめに
ReactでQRコードリーダを作ります。
create-react-app と react-qr-reader を使って簡単に作ります。QRコードリーダの作成
最初にcreate-react-appを使って、React環境を作成します。
今回は、qrcode-readerというディレクトリに作成します。npx create-react-app qrcode-reader次に、react-qr-readerをインストールします。
react-qr-readerのページに書かれているインストール方法で、
以下のコマンドを実行します。cd qrcode-reader npm install --save react-qr-reader最後に、react-qr-readerのサンプルコードを参考にsrc/App.jsを書き換えます。
import React, { Component } from 'react' import QrReader from 'react-qr-reader' class App extends Component { state = { result: 'No result' } handleScan = data => { if (data) { this.setState({ result: data }) } } handleError = err => { console.error(err) } render() { return ( <div> <QrReader delay={300} onError={this.handleError} onScan={this.handleScan} style={{ width: '100%' }} /> <p>{this.state.result}</p> </div> ) } } export default App;QRコードリーダの確認
作成したコードが動作するか確認します。
以下のコマンドでローカルサーバを起動します。npm startこの後ブラウザで、http://localhost:3000/ を表示します。
カメラでQRコードを読むと、画面の下にQRコードの文字列を表示します。スマホから確認
QRコードリーダがスマホでも動くか確認します。
ローカルサーバを起動した開発マシンと同一ネットワークに
存在するスマホのブラウザで確認します。
npm startした時に画面上に以下のような文字が出ていると思います。
IPアドレスは、環境により異なるので、画面上に出ているURLを
ブラウザに入力します。これで作成したコードがスマホで実行できるか確認できます。On Your Network: http://192.168.0.1:3000/上記の方法では、iPhone(iOS12.2)では、うまく表示できませんでした。
最初は、開発コードのバグかと思いましたが、iOS12.2のsafariでは、
httpsではないとカメラにアクセスできないようです。HTTPSでローカルサーバを起動
ローカルサーバにhttpsでアクセスできるように、以下のコマンドでローカルサーバを
起動します。このコマンドは、Linuxとmac用です。他のOSの場合は、参考文献に書いてある、create-react-app公式ページ(Using HTTPS in Development)を確認ください。HTTPS=true npm startHTTPSでローカルサーバを起動
以下のコマンドで公開用ビルドを作成します。
npm run buildビルド結果は、buildディレクトリに生成されます。
このディレクトリをWEBサーバに設置することで公開できます。環境
- react-qr-reader 2.2.1
- create-react-app 3.0.0
参考文献
- 投稿日:2019-04-30T18:18:31+09:00
typelessでReduxチュートリアルのTodoListを作ってみた
↓の記事でtypelessというツールキットを知ったので、typelessを使って、ReduxチュートリアルのTodoListを作ってみました。
ソースコード
メモ
redux-devtools-extension
// src/index.tsx import React from 'react'; import ReactDOM from 'react-dom'; import { RootEpic, RootReducer, TypelessProvider } from 'typeless'; import { createStore } from 'redux'; import { composeWithDevTools } from 'redux-devtools-extension'; import { App } from './components/App'; const rootEpic = new RootEpic(); const rootReducer = new RootReducer(); const store = (() => { if (process.env.NODE_ENV === 'production') { return createStore(rootReducer.getReducer()); } return createStore(rootReducer.getReducer(), composeWithDevTools()); })(); ReactDOM.render( <TypelessProvider rootEpic={rootEpic} rootReducer={rootReducer} store={store}> <App /> </TypelessProvider>, document.getElementById('app') );redux-devtools-extensionを使えるようにします。
stateが初期化されるタイミング
素のReduxだと
@@INITのタイミングで初期状態がstateの中に保存されています↓typelessでは
useModuleが呼ばれたタイミングで@@typeless/addedがdispatchされて初期状態が保存されます↓
VisibleTodoListの中ではtodosとvisibilityFilterの両方の状態を使うので、
typelessのクイックスタートにあるようにuseModuleしてコンポーネントを返す、というのができませんでした。なのでuseModuleとコンポーネントを別々に呼び出しています。(ここうまく書く方法がわからない…)// components/App.tsx import React from 'react'; import { useTodoListModule, AddTodoComponent, VisibleTodoListComponent } from '../features/todoList/module'; import { useFooterModule, FooterComponent } from '../features/footer/module'; export function App() { useTodoListModule(); // <- useModuleのみ useFooterModule(); // <- useModuleのみ return ( <> <AddTodoComponent /> <VisibleTodoListComponent /> <FooterComponent /> </> ); }
- 投稿日:2019-04-30T17:55:45+09:00
TypeScript x create-react-app x re-ducks雑感
概要
create-react-app/TypeScript/re-ducksを用いてアプリケーションを作成した雑感
※ 主にre-ducksのことについて書くコードはこれ(未完)
https://github.com/MasahikoJinno/re-ducks-study技術スタック
- TypeScript
- JavaScriptに型のパワーをもたらすやつ(スーパーセット)
- コードが大規模になったときに堅牢かつスピーディーに開発できると思う
- vscodeを使えばめっちゃ補完が効くようになる
- React
- JavaScriptのViewライブラリ
- 公式ドキュメント
- Redux
- JavaScriptの状態管理フレームワーク
- 公式ドキュメント
- create-react-app
- re-ducks
- Reduxで秩序あるコードを書くためのプラクティス
- ディレクトリ構成とファイルごとの責務分離を定めたデザインパターン(だと思ってる)
この名前めんどくさくないっすか?もうちょっとReduxの発音と変えてほしい。- 参考
TypeScriptについて
乗るしかねぇッッ!!
このビッグウェーブにッッ!!色々めんどくさいところもあるけど、VSCodeによる補完は素晴らしい。
基本プロとして複数人でコード書くならTypeScriptにしたほうがいいと思う。使い捨てだったり、(未来永劫)自分一人で書くコードだったり、短命(なのが確実)なアプリだったらJavaScriptでもいいかな・・・
create-react-appについて
webpackとかbabelとかあんまり考えずにReactのアプリ作れるのでとても便利だと思う。
そのかわりwebpackの設定とか書き換えられないけどそのへんはお察しください。
というかSSRをしないという前提なら、create-react-appのwebpack書き換えたいって思った時点でちょっとアーキテクチャが正しいのか考え直したほうがいい。基本的にセオリーからズレると思うので。トリッキーは辛いぞ。クライアントサイドレンダリングのだけのSPAを作るならcreate-react-app一択かなぁと思ってます。
思考停止してモダンな環境作れるので。re-ducksについて
今まで、以下のようなディレクトリ構成でReduxを用いていた。
https://github.com/MasahikoJinno/study/tree/master/js/react_todoapp- components: Functional Componentを配置 - containers: Container Componentを配置 - actions: ReduxのAction Creatorsを配置 - reducers: ReduxのReducerを配置同じ名前のファイルが大量にできるし、Stateの世界とViewの世界がごっちゃになってる感があってあまりしっくり来ていなかった。
re-ducksというものを知り、そのデザインパターンがどのようなものなのか知るためにサンプルコードを書いてみた。
俺の思うre-ducksでの責務分離
re-ducksは以下のようなディレクトリ構成になる
+ ducks └+ todos └ actions.ts └ index.ts └ operations.ts └ reducers.ts └ selectors.ts └ types.ts各ファイルの責務分離は以下のようになっている。(と思う)
※ index.tsはただのモジュールのエントリポイントだから無視。
※ 詳しくは冒頭のリポジトリを見てほしい。actions.ts
- Action Creatorを配置する
- Action Creatorは単純なオブジェクト(ActionTypeとPayload)を返すだけにする
- Dispatchとかしない
- 値の計算もしない
- async/awaitとかも書かない
operations.ts
- Actionを発火するためのラッパー関数を配置する
- Actionを複数かい同時に叩きたい場合やThunkを使う場合はここに書く
- Actionにわたす引数の作成等もこのoperationsで行う
- Actionを発火させたい場合は、必ずこのoperations経由で行う
- Actionをカプセル化する
- ViewはどのActionを発火するとか知らないほうがいい
reducers.ts
- ここは他のパターンでもあんまり変わらないと思う
- Reduxのreducerを配置する
- Stateの定義とActionTypeに応じたState変更処理を記述する
- 書いてて思ったけどファイル名複数形じゃないほうがいいね
selectors.ts
- Viewが使いたい値はStateの値とイコールとは限らない
- Viewが使いたい形にStateを加工する関数を配置する
- 単純なデータ構造であればStateをそのまま利用しても良いが、ViewがStateを知りすぎるのは良くないのでViewが使いやすい形に加工して渡してやるのがよい
- 配列のフィルタリングとかはここでやる(Viewにロジックを書かない。ただ渡されたものを表示するだけにする。)
types.ts
- ActionTypesの定義とかはここでしている
- actions.tsで定義すればよくね?って意見もあるけどそれでもいいと思う(好みの問題)
- TSじゃなかったら不要かなぁ
Reduxの作用は
actions.tsとreducers.tsに記述して、副作用をoperations.tsとselectors.tsに記述するイメージだと思う。
operations.tsとselectors.tsの違いで最初混乱したけど今は以下のように理解している。
- operations.ts
- State変更時の(ビジネス)ロジックを記載
- selectors.ts
- State取得時の(ビジネス)ロジックを記載
全体的な所感
アプリケーション全体では大まかに以下のようなディレクトリ構成にしている
+ state └+ ducks + views └+ components └+ containers └+ pages └ App.tsx index.tsx大まかに
stateとviewsで状態管理の世界と表示の世界で分離。
stateには前述のducksディレクトリが入る。
viewsは純粋なComponent(ほぼFunctional Componentsになる)のcomponentsと状態管理の世界と表示の世界を繋ぐcontainersといわゆるContainer Componentsを格納するpagesに分離した。
componentsとpagesは正直適当。Atomicデザインとかに習えばatom,molecules,organisms,templates,pagesとかになるのかな・・・
ただ、containersとその他は分けたほうがいいと思う。(containersにJSX書かないほうがいい)んで、何が嬉しいの?
究極は「責務が明確になる」であると思う。
re-ducksのファイル分割では、各々の責務が明確になっており、actionsやreducerは非常に単純なコード(ピュアな状態)を維持できる。
ユニットテストも容易になると思われる。その分operationsとselectorsに副作用が追いやられているが、これはこれでどこのテストをしっかり書かなきゃだめかわかりやすくなると思う。
re-ducksとは直接関係ないけど、今回コードを書いていてViewとStateの依存をなくすことの重要さを再認識した。
Stateのデータ構造がViewのに依存する形で作られていたり、ViewがStateの構造を完全に理解して実装されていたりっていうのはあるあるだと思うがこの状態は良くない。ViewはViewとして必要なものだけを、StateはStateとして必要なものだけを実装すべきだと思うし、そうでなければ作業分担当もできない。
ViewとStateの互いの依存をなくすために、
containerの実装が重要になってくる。
「containerがViewとState両方に依存して、複雑さを一手に引き受けることでViewとStateの依存関係が初めて解消される」ということにようやく気づくことができた。
selectorsとoperationsはcontainerが肥大化するのを避けるために生まれた概念なのかなってちょっと思った。まとめ
- ViewとStateはお互い依存しないようにしよう
- 責務分離をしっかりと行おう
- そのための手段がre-ducks
以上、最後までご覧いただきありがとうございました。
- 投稿日:2019-04-30T16:52:54+09:00
自分の環境設定を書いておく (webpack.config.js)
JavaScript(ES6)
- 要
@babel/register&.babelrcwebpack.config.babel.jsimport path from 'path'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import TerserWebpackPlugin from 'terser-webpack-plugin'; import MiniCSSExtractPlugin from 'mini-css-extract-plugin'; import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin'; const isDev = process.env.NODE_ENV === 'development'; export default { mode: isDev ? 'development' : 'production', entry: './src/main.js', output: { path: path.resolve(__dirname, 'build'), filename: 'bundle.js', }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', }, { test: /\.css$/, use: [ { loader: MiniCSSExtractPlugin.loader, options: { hmr: isDev, reloadAll: true, }, }, { loader: 'css-loader', options: { sourceMap: isDev, }, }, ], }, { test: /\.(tiff|gif|jpe?g|png|svg|ttf|eot|wof|woff2?)$/, loader: 'file-loader', options: { name: 'images/[name].[ext]', }, }, ], }, resolve: { extensions: ['.js'], }, optimization: isDev ? { minimize: false } : { minimizer: [new TerserWebpackPlugin(), new OptimizeCSSAssetsPlugin({})], }, plugins: [ new MiniCSSExtractPlugin({}), new HtmlWebpackPlugin({ template: './src/index.html', favicon: './src/favicon.ico', }), ], devtool: isDev ? 'source-map' : false, devServer: { contentBase: path.resolve(__dirname, 'build'), port: 8000, }, };JavaScript + React
webpack.config.jsconst path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const TerserWebpackPlugin = require('terser-webpack-plugin'); const MiniCSSExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const isDev = process.env.NODE_ENV === 'development'; module.exports = { mode: isDev ? 'development' : 'production', entry: './src/index.jsx', resolve: { extensions: ['.jsx', '.js'], }, output: { path: path.resolve(__dirname, 'build'), filename: 'bundle.js', }, module: { rules: [ { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: ['@babel/preset-env', '@babel/preset-react'], plugins: ['@babel/plugin-proposal-class-properties'], }, }, { test: /\.css$/, use: [ { loader: MiniCSSExtractPlugin.loader, options: { hmr: isDev, reloadAll: true, }, }, { loader: 'css-loader', options: { sourceMap: isDev, }, }, ], }, { test: /\.(tiff|gif|jpe?g|png|svg|eot|wof|woff2?|ttf)$/, loader: 'file-loader', options: { name: 'images/[name].[ext]', }, }, ], }, plugins: [ new MiniCSSExtractPlugin({}), new HtmlWebpackPlugin({ template: './src/index.html', favicon: './src/favicon.ico', }), ], optimization: isDev ? { minimize: false } : { minimizer: [new TerserWebpackPlugin(), new OptimizeCSSAssetsPlugin({})], }, devtool: isDev ? 'source-map' : false, devServer: { contentBase: path.resolve(__dirname, 'build'), port: 3030, }, };JavaScript + Electron
webpack.config.jsconst path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const TerserWebpackPlugin = require('terser-webpack-plugin'); const MiniCSSExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const isDev = process.env.NODE_ENV === 'development'; const main = { mode: isDev ? 'development' : 'production', target: 'electron-main', entry: './src/main.js', resolve: { extensions: ['.js', '.json'], }, output: { path: path.resolve(__dirname, 'build'), filename: 'main.js', }, module: { rules: [ { test: /\.js$/, loader: 'babel-loader', options: { presets: ['@babel/preset-env'], }, }, ], }, }; const app = { mode: isDev ? 'development' : 'production', target: 'electron-renderer', entry: './src/app.js', output: { path: path.resolve(__dirname, 'build'), filename: 'app.js', }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: ['@babel/preset-env'], }, }, { test: /\.css$/, use: [ { loader: MiniCSSExtractPlugin.loader, options: { hmr: isDev, reloadAll: true, }, }, { loader: 'css-loader', options: { sourceMap: isDev, }, }, ], }, { test: /\.(tiff|gif|jpe?g|png|svg|eot|wof|woff2?|ttf)$/, loader: 'file-loader', options: { name: 'images/[name].[ext]', }, }, ], }, plugins: [ new MiniCSSExtractPlugin({}), new HtmlWebpackPlugin({ template: './src/index.html', }), ], optimization: isDev ? { minimize: false } : { minimizer: [new TerserWebpackPlugin(), new OptimizeCSSAssetsPlugin({})], }, devtool: isDev ? 'source-map' : false, }; module.exports = [main, app];TypeScript
webpack.config.jsconst path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const TerserWebpackPlugin = require('terser-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const isDev = process.env.NODE_ENV === 'development'; module.exports = { mode: isDev ? 'development' : 'production', entry: './src/index.tsx', output: { path: path.resolve(__dirname, 'build'), filename: 'bundle.js', }, resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'], }, module: { rules: [ { test: /\.tsx?$/, exclude: /node_modules/, loader: 'ts-loader', }, { test: /\.css$/, use: [ { loader: MiniCssExtractPlugin.loader, options: { hmr: isDev, reloadAll: true, }, }, 'css-loader', ], }, { test: /\.(tiff|gif|jpe?g|png|svg|ttf|eot|wof|woff|woff2)$/, loader: 'file-loader', options: { name: 'images/[name].[ext]', }, }, ], }, optimization: isDev ? { minimize: false } : { minimizer: [new TerserWebpackPlugin(), new OptimizeCSSAssetsPlugin({})], }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', favicon: './src/favicon.ico', }), new MiniCssExtractPlugin({}), ], devtool: isDev ? 'source-map' : false, devServer: { contentBase: path.resolve(__dirname, 'build'), port: 3000, }, stats: 'minimal', performance: { hints: false, }, };公式ドキュメント
- Webpack
- Babel
- ts-loader (github)
- css-loader (github)
- file-loader (github)
- html-webpack-plugin (github)
- mini-css-extract-plugin (github)
- optimize-css-assets-webpack-plugin (github)
- 投稿日:2019-04-30T07:53:52+09:00
Chrome拡張機能で、GitHub上でSwaggerをプレビューできるツールを作った
概要
GitHub 上で、Swagger の yaml | yml | json を Swagger-UI に変換できる Chrome 拡張機能を作った。
その紹介。「Swagger とは?」という話は、下記等を参照のこと。
Demo
インストール
Chrome Web Store からインストールできる。
https://chrome.google.com/webstore/detail/swagger-viewer/nfmkaonpdmaglhjjlggfhlndofdldfag特徴
簡単
- 1クリックで変換できる
依存なし
- この拡張機能のみで動作する
- Web の Swagger Editor を開いたり、ドキュメント生成サーバーを起動する必要なし
セキュア(たぶん)
- 外部ネットワークへの送受信はないため、業務でも使用できる
- ※自己責任でお願いします
- たぶん
- Chrome の Network タブを眺めたが、通信する様子はなかった
- Swagger の描画のために使用しているライブラリ「swagger-ui」のコードも軽く読んだが、ネットワーク送受信に関するコードはなかった(自信なし)
- やべーやつだったらご連絡いただけると幸いです
開発裏話(?)
動けばいいんだよ2割、美しく書きたい8割、くらいの気持ちで作った
趣味のコードは盆栽
名言だと思った「趣味で書くソースコードは綺麗汚い問わない」との意見が散見されますが、僕は趣味で綺麗なソースコードを追求する人間です。盆栽に近い。
— ミノ駆動 (@MinoDriven) 2019年4月25日src
https://github.com/arx-8/swagger-viewer
技術要素
似た技術スタックで Chrome 拡張機能を作りたい人の一助になれば(と盆栽自慢に)少し書く
- WebExtension Toolbox (Chrome 拡張機能ジェネレーター)
- TypeScript
- Jest
- jsdom
- React
- ESLint
- Clean Architecture
以下盆栽ポイント
- 最新フロントエンド技術 (TypeScript, typescript-eslint, prettier, etc.) を導入した
- 壊れやすいDOM操作に対してUnitTestを書いた
- 生DOMは、Jest + jsdom
- Reactは、Jest Snapshot test + react-test-renderer
- WebExtension Toolbox で作った雛形には、TypeScriptはもちろんESLintもテストも入ってないので、自分で追加が必要
- Clean Architecture
- (ゆるく)パッケージ構成をこれでやるとだいたいうまく整理できる気がする
- Clean Architecture * React のベストプラクティスがほしい・・・
TypeScript導入
下記issueを参考に、
https://github.com/webextension-toolbox/generator-web-extension/issues/11
ts-loaderで導入
https://github.com/arx-8/swagger-viewer/commit/fcf385363729f6fb0a7e39018a5fff2ec5881145後に、Jest導入時にうまく動作しなかったため、babelに変更
https://github.com/arx-8/swagger-viewer/commit/a5dbbe19054189cb03cc88d825ee9c21d2671987幸いwebextension-toolboxがbuildに使ってるbabelが7系だっため、TypeScriptのトランスパイルができた
https://github.com/arx-8/swagger-viewer/blob/master/swagger-viewer/package-lock.json#L9871Jest導入
前述の通り、ts-loaderではうまく動かず、babelに変えた
https://github.com/arx-8/swagger-viewer/pull/9/files#diff-47c7df0a0b4c5f24af1531f28d7a4d57Reactでの css import がエラーになったため、これをやった
https://jestjs.io/docs/ja/webpack#静的アセットの管理ESLint (+ Prettier)
https://github.com/arx-8/swagger-viewer/pull/11/files#diff-514b7220e7bfcb11a68634cba4db8961
- .eslintrcのextendsが激盛り・・・
- 個人的には、ガチガチのairbnbをプロジェクトに応じて緩めるのが好き
no-restricted-globals, no-restricted-properties で禁止機能を定義している
- Map は嫌い
- innerText -> textContent
- DOM の textContent と innerText について - Please Sleep
- 「innerHtmlがあるからinnerTextやろ」とやると死ぬ
- document -> getDocument()
- 理由は後述
ESLint + TypeScript + VSCode
VSCode拡張機能のESLintはちゃんとtsファイルにも対応していた
しばらく気付かず悶々としていたが、やはりMSは神https://github.com/Microsoft/vscode-eslint/issues/609#issuecomment-460554105
- ただデフォルト有効にはならなそうな雰囲気なので、自分で設定が必要
実際の設定はこんな感じ
- https://github.com/arx-8/swagger-viewer/blob/aaae948cf150848f44e7a110d3487ce9de328b23/swagger-viewer/.vscode/settings.json
- https://github.com/arx-8/swagger-viewer/blob/aaae948cf150848f44e7a110d3487ce9de328b23/swagger-viewer/.eslintrc.js#L15-L22
今のところ特にバグにもハマってないので、typescript-eslint は十分プロダクトに投入できるレベルなのでは
Jest + jsdom
だいたいこんな手順で、documentオブジェクトに任意の値を展開してテストしている
- 1. getDocument関数をJestでMock化
- 2. ページ丸ごと文字列としてテストデータとし、それをjsdomに与えてdocumentオブジェクトを生成
- 3. getDocumentの返値を「2.」にする
- 4. 任意のdocumentオブジェクトに対して、操作が期待通りの状態を作れるかテストする
テスト結果を手で定義するのが面倒なものは、toMatchSnapshotで雑にassert
no-restricted-globals で document -> getDocument() としてる理由はこれ
- JestでMockできるのは、外部ファイルからimportしたリソースのみらしい
- なので、documentオブジェクトにアクセスする場合は、必ずgetDocument(モック化できるfunction)を経由させている
Jest + react-test-renderer
enzyme より react-test-renderer の方がいいぞ、という記事を読んだのでそうした
コード例
- https://github.com/arx-8/swagger-viewer/blob/280caa8e155a4e45d68b74c83b532650d531b56b/swagger-viewer/app-src/contentscript/presentation/App/index.test.tsx
- 出力内容を1行ずつ見てはいられないが、「これで間違いはない」という状態で一発Snapshot取っておく
- UI系のライブラリも丸ごとSnapshot取っておくと、バージョンアップの時に動作の保証だけでなく、「どこが変更されたか」がdiffでわかって便利
DOM丸ごと生成したりライブラリのrender結果を丸ごとSnapshot取ったり力技なUTだが、「規模が小さいし実行してほっときゃ終わるだろ」ということで富豪チックに書いた
未解決の課題
ReactTestUtils.Simulate.click ができない
やり方を知ってる方いたら、ご教授願いたい・・・
公式には記述があるのだが、ver違いなのかReactTestUtils.Simulateがundefinedだった
https://reactjs.org/docs/test-utils.html#simulate「Reactコンポーネントが、ある箇所をクリックされた結果どうなってるか」のSnapshot testができていないのが心残り
まとめ
- Chrome拡張機能、型・テストともガッチガチに作れて楽しかったのでオススメ!!
- 投稿日:2019-04-30T07:53:52+09:00
GitHub上でSwaggerをプレビューできるChrome拡張機能を作った
概要
GitHub 上で、Swagger の yaml | yml | json を Swagger-UI に変換できる Chrome 拡張機能を作った。
その紹介。「Swagger とは?」という話は、下記等を参照のこと。
Demo
インストール
Chrome Web Store からインストールできる。
swagger-viewer - Chrome ウェブストア特徴
簡単
- 1クリックで変換できる
依存なし
- この拡張機能のみで動作する
- Web の Swagger Editor を開いたり、ドキュメント生成サーバーを起動する必要なし
セキュア(たぶん)
- 外部ネットワークへの送受信はないため、業務でも使用できる
- ※自己責任でお願いします
- たぶん
- Chrome の Network タブを眺めたが、通信する様子はなかった
- Swagger の描画のために使用しているライブラリ「swagger-ui」のコードも軽く読んだが、ネットワーク送受信に関するコードはなかった(自信なし)
- やべーやつだったらご連絡いただけると幸いです
開発裏話(?)
動けばいいんだよ2割、美しく書きたい8割、くらいの気持ちで作った
趣味のコードは盆栽
名言だと思った「趣味で書くソースコードは綺麗汚い問わない」との意見が散見されますが、僕は趣味で綺麗なソースコードを追求する人間です。盆栽に近い。
— ミノ駆動 (@MinoDriven) 2019年4月25日src
https://github.com/arx-8/swagger-viewer
技術要素
似た技術スタックで Chrome 拡張機能を作りたい人の一助になれば(と盆栽自慢に)少し書く
- WebExtension Toolbox (Chrome 拡張機能ジェネレーター)
- TypeScript
- Jest
- jsdom
- React
- ESLint
- Clean Architecture
以下盆栽ポイント
- 最新フロントエンド技術 (TypeScript, typescript-eslint, prettier, etc.) を導入した
- 壊れやすいDOM操作に対してUnitTestを書いた
- 生DOMは、Jest + jsdom
- Reactは、Jest Snapshot test + react-test-renderer
- WebExtension Toolbox で作った雛形には、TypeScriptはもちろんESLintもテストも入ってないので、自分で追加が必要
- Clean Architecture
- (ゆるく)パッケージ構成をこれでやるとだいたいうまく整理できる気がする
- Clean Architecture * React のベストプラクティスがほしい・・・
TypeScript導入
下記issueを参考に、
https://github.com/webextension-toolbox/generator-web-extension/issues/11
ts-loaderで導入
https://github.com/arx-8/swagger-viewer/commit/fcf385363729f6fb0a7e39018a5fff2ec5881145後に、Jest導入時にうまく動作しなかったため、babelに変更
https://github.com/arx-8/swagger-viewer/commit/a5dbbe19054189cb03cc88d825ee9c21d2671987幸いwebextension-toolboxがbuildに使ってるbabelが7系だっため、TypeScriptのトランスパイルができた
https://github.com/arx-8/swagger-viewer/blob/master/swagger-viewer/package-lock.json#L9871Jest導入
前述の通り、ts-loaderではうまく動かず、babelに変えた
https://github.com/arx-8/swagger-viewer/pull/9/files#diff-47c7df0a0b4c5f24af1531f28d7a4d57Reactでの css import がエラーになったため、これをやった
https://jestjs.io/docs/ja/webpack#静的アセットの管理ESLint (+ Prettier)
https://github.com/arx-8/swagger-viewer/pull/11/files#diff-514b7220e7bfcb11a68634cba4db8961
- .eslintrcのextendsが激盛り・・・
- 個人的には、ガチガチのairbnbをプロジェクトに応じて緩めるのが好き
no-restricted-globals, no-restricted-properties で禁止機能を定義している
- Map は嫌い
- innerText -> textContent
- DOM の textContent と innerText について - Please Sleep
- 「innerHtmlがあるからinnerTextやろ」とやると死ぬ
- document -> getDocument()
- 理由は後述
ESLint + TypeScript + VSCode
VSCode拡張機能のESLintはちゃんとtsファイルにも対応していた
しばらく気付かず悶々としていたが、やはりMSは神https://github.com/Microsoft/vscode-eslint/issues/609#issuecomment-460554105
- ただデフォルト有効にはならなそうな雰囲気なので、自分で設定が必要
実際の設定はこんな感じ
- https://github.com/arx-8/swagger-viewer/blob/aaae948cf150848f44e7a110d3487ce9de328b23/swagger-viewer/.vscode/settings.json
- https://github.com/arx-8/swagger-viewer/blob/aaae948cf150848f44e7a110d3487ce9de328b23/swagger-viewer/.eslintrc.js#L15-L22
今のところ特にバグにもハマってないので、typescript-eslint は十分プロダクトに投入できるレベルなのでは
Jest + jsdom
だいたいこんな手順で、documentオブジェクトに任意の値を展開してテストしている
- 1. getDocument関数をJestでMock化
- 2. ページ丸ごと文字列としてテストデータとし、それをjsdomに与えてdocumentオブジェクトを生成
- 3. getDocumentの返値を「2.」にする
- 4. 任意のdocumentオブジェクトに対して、操作が期待通りの状態を作れるかテストする
テスト結果を手で定義するのが面倒なものは、toMatchSnapshotで雑にassert
no-restricted-globals で document -> getDocument() としてる理由はこれ
- JestでMockできるのは、外部ファイルからimportしたリソースのみらしい
- なので、documentオブジェクトにアクセスする場合は、必ずgetDocument(モック化できるfunction)を経由させている
Jest + react-test-renderer
enzyme より react-test-renderer の方がいいぞ、という記事を読んだのでそうした
コード例
- https://github.com/arx-8/swagger-viewer/blob/280caa8e155a4e45d68b74c83b532650d531b56b/swagger-viewer/app-src/contentscript/presentation/App/index.test.tsx
- 出力内容を1行ずつ見てはいられないが、「これで間違いはない」という状態で一発Snapshot取っておく
- UI系のライブラリも丸ごとSnapshot取っておくと、バージョンアップの時に動作の保証だけでなく、「どこが変更されたか」がdiffでわかって便利
DOM丸ごと生成したりライブラリのrender結果を丸ごとSnapshot取ったり力技なUTだが、「規模が小さいし実行してほっときゃ終わるだろ」ということで富豪チックに書いた
これでGitHubのhtmlが変更されたとしても、UT回して検知・修正ができるであろうと期待未解決の課題
ReactTestUtils.Simulate.click ができない
やり方を知ってる方いたら、ご教授願いたい・・・
公式には記述があるのだが、ver違いなのかReactTestUtils.Simulateがundefinedだった
https://reactjs.org/docs/test-utils.html#simulate「Reactコンポーネントが、ある箇所をクリックされた結果どうなってるか」のSnapshot testができていないのが心残り
まとめ
- Chrome拡張機能、型・テストともガッチガチに作れて楽しかったのでオススメ!!
- 投稿日:2019-04-30T01:54:13+09:00
create-react-appを利用してReactアプリをGitHub Pagesで公開
はじめに
Facebookが作成しているReactを試してみたかったので、GitHub PagesでReactアプリを公開する方法です。現時点のReactの公式ページとGitHubの公式ページに記載されている方法で動いたので、この方法を書いていきます。
Reactアプリの環境構築
Reactアプリの環境構築は、コマンド1つで作成できるcreate-react-appを使います。このツールは、Reactと同様にFacebookが公開しています。使い方は、簡単。インストールに必要な環境は、npxのバージョンが5.2以上。この環境があれば、公式ページに書かれている手順でReactが使えるようになります。
Reactアプリの環境を作るのは、以下のコマンドを実行します。npx create-react-app my-appmy-appは、ディレクトリ名なので、自分の好きな名前に変更できます。この記事では、my-appとします。Reactのページをブラウザで表示するには、以下のコマンドを実行して、ブラウザで http://localhost:3000/ を開きます。my-app/src/App.js を編集すれば、表示内容を修正することができるので、試してみてください。
cd my-app npm startReactアプリのデプロイ
GitHub Pagesに設置するURLをpackage.json ファイルのhomepageの項目に設定します。初期のpackage.jsonファイルには、homepageの項目はないので、例えば、以下のように設定を追加します。
"homepage" : "http://myname.github.io/myapp",設定後、以下のコマンドでbuildを行います。
ビルド結果は、my-app/build ディレクトリに生成されます。
このディレクトリをGitHubに設置すれば公開することができます。npm run buildGitHub Pagesで公開
- GitHubでusername.github.ioのレポジトリを作成
usernameの部分は、自分のGitHubユーザ名に変更します。このレポジトリは、GitHub Pagesとしてwebサーバとして公開されます。- GitHubのレポジトリ(username.github.io)にbuildディレクトリを設置
ディレクトリ名は、myappにします。git pushしてGitHubに反映させます。- http://myname.github.io/myapp にアクセスして確認
環境
- create-react-app 3.0.0
- npx 6.7.0
- node 11.10.1
参考文献



