- 投稿日:2020-07-25T21:52:44+09:00
DockerでTypeScript+Reactな環境構築(create-react-app)
目標
- Dockerとdocker-composeを用いてcreate-react-appし、Reactアプリケーションの開発を始められる
- かつ、TypeScriptでReactを動かす
前提
- Dockerが使用可能である
- docker-composeが使用可能である
手順
1. 作業ディレクトリの作成
作業するディレクトリを作成しましょう。
プロジェクト名はここではsample-app
とします。% mkdir sample-app % cd sample-app
2. Dockernize
Dockerfileとdocker-compose.ymlを作成し、中身を追加します。
% touch Dockerfile % touch docker-compose.yml
nodeのバージョンはこちらから新しめのものを選択すればいいと思います。
プロジェクトディレクトリ名が"sample-app"なので、コンテナ名・react-app名は"frontend"とします。
DockerfileFROM node:14.4.0-alpine3.10 ENV LANG=C.UTF-8 ENV TZ=Asia/Tokyo WORKDIR /usr/src/sample-appdocker-compose.ymlversion: '3' services: frontend: build: . environment: - NODE_ENV=development volumes: - ./:/usr/src/sample-app command: sh -c 'cd frontend && yarn start' ports: - '3000:3000' tty: true
volumes
:ホスト:コンテナ
間でファイル・ディレクトリを同期(= マウント)する
- これにより、ホスト側のエディター等で行った編集がコンテナ側にも反映されたり、
docker-compose run コマンド
でコンテナ側に生成されたファイル・ディレクトリがホスト側にも同様に生成されるようになるsh -c 'cd frontend && yarn start'
:docker-compose up
したとき、react-appディレクトリに移動し、devサーバーを起動するports
: 公開用のポートを、'ホスト:コンテナ'
の書式で指定している。
- ブラウザでローカルホストを開くときはホスト側に指定したポートを参照する(e.g. この場合、ブラウザからは
localhost:3000
で開発環境のアプリケーションにアクセスできる)tty
: 値をtrueにすることで、docker-compose up
でコンテナを起動させた際にコンテナがすぐに終了してしまうのを防ぐ3. ビルド
% docker-compose build % docker-compose run --rm frontend sh -c 'npx create-react-app frontend --template typescript'
--rm
: コマンド実行後にこのコンテナ(frontend)を削除する--template typescript
: .jsxファイルの代わりに.tsxファイルが生成され、ReactアプリケーションがTypeScriptで動くようになる正常にビルドが完了した場合、以下のようなディレクトリ構成になります。
TSXファイル等はsrc
以下に配置されています。sample-app ├── Dockerfile ├── docker-compose.yml └── frontend ├── node_modules ├── public ├── src ├── .gitignore ├── README.md ├── package.json ├── tsconfig.json └── yarn.lock
4. アプリケーション起動の確認
下記コマンドでdevサーバーが起動します。
% docker-compose up
http://localhost:3000/
にアクセスし、App.tsx
の内容が表示されていれば成功です。
該当ファイルをエディターで編集するとすぐに反映されることも確認できるはずです。あとは自由に開発を進めていきましょう!
アプリケーションの停止は下記コマンドでできます。
% docker-compose down
参考
- 投稿日:2020-07-25T17:53:09+09:00
【React】基礎編 ⑶
新しく学んだことをアウトプットしています。
React.Fragment
JSXは複数の要素を持つことができないため、
<div>タグで複数の要素を格納するのが一般的でした。ですが、HTMLで描画した時に、
本来は<div>タグが必要ない所にまで要素が付与されてしまうので、意外と困る時がありますよね。そんな時に、使用するのが、React.Fragmentなのです。
React.Fragmentは<div>タグと同じ役割を担っており、
HTMLでは要素としてカウントされないので、使い勝手が良いです。App.jsximport React, { Component } from 'react'; class App extends Component { render() { return( // divの替り <React.Fragment> <User name={"Taro"} age={8}/> </React.Fragment> ); } } export default App;関数コンポーネント
コンポーネントには、
クラスコンポーネントと関数コンポーネントの2つがあります。Reactでは、VirtualDomというDomが存在していて、その中でどのDomが変更になったのか管理していて、その変更点のみを実際のDomに反映していく機構があります。
それぞれのDomにkeyを与えてあげて、必要最低限なDomの範囲をReactで管理しています。App.jsx// クラスコンポーネント import React, { Component } from 'react'; class App extends Component { const profiles = [ { name: "Taro", age: 8 }, { name: "Hanako", age: 5 }, ] render() { return( <React.Fragment> { profiles.map((profile, index) => { return( // keyをindexで与えてあげる <User name={profile.name} age={profile.age} key={index} /> ); }) } </React.Fragment> ); } } // 関数コンポーネント const User = (props) => { // 引数にpropsを持たせる return( <div> <p> I am {props.name} and {props.age} years old!</p> </div> ) } export default App;defaultProps
ReactのpropsにはdefaultPropsという機構があります。
propsを受け取るComponentにdefaultPropsを設定してあげる。以下のように設定してあげると、
Noname さんの age がデフォルトで 1 と出力されます。App.jsximport React, { Component } from 'react'; class App extends Component { const profiles = [ { name: "Taro", age: 8 }, { name: "Hanako", age: 5 }, { name: "Noname" } ] render() { return( <React.Fragment> { profiles.map((profile, index) => { return( <User name={profile.name} age={profile.age} key={index} /> ); }) } </React.Fragment> ); } } const User = (props) => { return( <div> <p> I am {props.name} and {props.age} years old!</p> </div> ) } // propsを受け取るComponentでdefaultPropsを設定 User.defaultProps = { age: 1 } export default App;propTypes
先ほどのdefaultPorpsと同様に、
ReactのpropsにはpropTypesという機構があります。
propsを受け取るComponentにpropsTypesを設定してあげる。以下のように設定してあげると、
name や age に型を設定することができ、 validation をかけることができます。
型を設定しておくことで、 name に数値が入れられたり、age に文字列が入るのを防止してくれます。
また、 isRquired とすることで、 age が空の場合はエラーが発生するようになります。App.jsximoprt React from 'react'; // prop-Typesをimport imoprt PropTypes from 'prop-types'; // 省略 // User.propTypes = { name: PropTypes.string, age: PropTypes.number.isRequired, }以上
- 投稿日:2020-07-25T16:55:08+09:00
React deploy to github static pages
githubに静的ページをdeployする方法
install the gh-pages
npm i gh-pages
package.jsonを開き、scriptsに以下のdeployの内容を追記
"scripts":{ ..., "deploy": " gh-pages -d build", "predeploy": "npm run build" } "homepage": "https://{githubのuser-name}.github.io/{project名}"※pre-がついていたら、該当するコマンドが動く先にpre-コマンドが動く、後コマンドが動く
※project名は git remote -v で検索する。
※該当するhomepageがpublishedされる先projectをbuildするため、以下のコマンドを叩く
npm run build
build後以下のようにbuildフォルダーが作成されることを確認する
buildされたものを配布する(deploy)
npm run deploy
明示されたhomepageに入ったら、配布されたことが確認できる
- 投稿日:2020-07-25T16:34:12+09:00
gitコマンド初心者覚書
git初心者、すぐに忘れるので覚書
- githubで + New repositoryで、パブリックで nodeを選び、 できたリポジトリをローカルにクローンする。
$ yarn global add create-react-app $ git checkout -b hello-world $ create-react-app ファイル名 $ git clone https://github.com/----/----.git//githubに新しいリポジトリを作り、ローカルにgit clone する $ cd 作りたいディレクトリ //移動 $ git status // git add されているけどまだ git commit されていないファイルの一覧 編集・変更・削除されているが、まだ git add されていないファイルの一覧 Git管理されていない、かつ .gitignore で管理除外対象にもされていないものの一覧 $ git add .gitignore //管理外にする $ git add //変更内容をインデックスに追加してコミット対象にする $ git diff $ git checkout -b hello-world //ブランチを作る $ git commit //コミットする
- 投稿日:2020-07-25T16:14:23+09:00
自粛期間中に制作した成果物まとめ
はじめに
新型コロナウイルスの影響で4月から休業を余儀なくされたので
個人制作物がそこそこ溜まってきました。ここではその溜まった成果物をまとめておきます。
成果物まとめ
# 001 AdobeXD WEBデザインテンプレート(随時更新中)
AdobeXDで、Webとアプリデザインのテンプレートを制作しまとめてあるものになります。
モックアップを制作する際にご利用ください。
こちらはこれからも随時更新していく予定です。[URL]
- xd_webdesign_sample
- Demo# 002 React製 WhiteBoardアプリ
create-react-appベースで開発したWhiteBoard(付箋投稿)アプリです。
バックエンドはFirebase(リアルタイムデータベース)を使用。[コア機能]
- 付箋リアルタイム投稿[URL]
- react-board
- Demo# 003 React製 SNS風アプリ
create-react-appベースで開発したSNS風アプリです。
バックエンドはFirebase(cloud Firestore)を使用。[コア機能]
- Firebaseログイン(メール、Twitter、Google)
- お問い合わせフォーム(cloud Firestore使用)
- チャット機能(cloud Firestore使用)
- ニュースAPI情報取得・表示[URL]
- engineer-chat
- Demo# 004 React製 Firestorage画像アップロード
Firebaseの一機能Firestorage(画像を格納できる場所)に画像アップロード機能を実装しました。
※cloud Firestoreにpathをアップする機能は実装していません(こちらは今後の目標)[コア機能]
- Firestorage画像アップロード[URL]
- react-image-upload# 005 Next.jsチュートリアルからアーキテクチャ設計
ReactフレームワークであるNEXT.jsのチュートリアルを一巡してから、自分なりにアーキテクチャを設計しました。
また独自のAPIも組み込んだコンポーネントも追加しています。[URL]
- hello-nextその他
その他、VueやNuxt、ReactとLaravelのサンプルなど制作しています。
- 投稿日:2020-07-25T15:23:34+09:00
Reactアクセシビリティ
Reactの記事を参考に初心者がなんとか覚書を書いています。
ってそういう意味だったのかー、と思いました。
- Webアクセシビリティ a11yとも呼ばれる
- Web Content Accessibility Guidelines (WCAG) Overview アクセシビリティを備えたウェブサイトを構築するためのガイドラインを提供
- WAI-ARIA React においてほとんどの DOM プロパティと属性がキャメルケースである一方で、これらの属性は純粋な HTML と同じようにハイフンケース(ケバブケースやリスプケースなどとも言われる)である必要があります。
- セマンティックな HTML
- 正しい子要素の出力
Fragment.jsclass Columns extends React.Component { render() { return ( <React.Fragment> <td>Hello</td> <td>World</td> </React.Fragment> ); } }- key 付きフラグメント 明示的に <React.Fragment> と宣言したフラグメントでは key を持つことができる
- 投稿日:2020-07-25T14:29:17+09:00
個人サイトをjQueryからReactに書き換え
uedayou.netで個人的に作成したWebアプリやら技術系同人誌を紹介するサイトを公開しています。
サイトというか、ほとんど1ページしかないのですが、だいぶ古いコードで、アプリが増えてきて更新し難くなってきたので、作り直すことにしました。従来の構成
当時、あまりサイト更新に時間をかけたくなく、1ページにまとまって見れるリンク集に毛が生えた程度のものを作りたかったので、スタイルはBootstrap、PC、タブレット、スマートフォン、どの環境でもそれなりに見れるようにするためMasonryとjQueryを使って、作品紹介の要素がきれいに並び変わるようにしていました。
使用モジュール jQuery Masonry Bootstrap 問題点
以下のような要素を作品分、1つのHTMLファイル内に記述する形なので、数が少ないときはよかったのですが、数が多くなるにつれて、コードの可読性が悪くなり、作品の順番を入れ替えるのも大変になってきました。
<div class="app-section col-lg-4 col-sm-6"> <h4>鉄道駅LOD</h4> <div class="well"> <div class="image"> <img src="app_images/jrslod-image.jpg" class="img-responsive"> </div> <div class="description"> <p> 「<a href="https://uedayou.net/jrslod/" target="_blank">鉄道駅LOD</a>」は日本の鉄道に関するオープンデータを収集して提供するサイトです。 </p> ... <a class="btn btn-primary" href="https://uedayou.net/jrslod/" target="_blank">サイトを開く</a> <a class="btn btn-primary" href="https://qiita.com/uedayou/items/b5131b5ca930fe0bef69" target="_blank">解説記事</a> </div> </div> </div>新しい構成
昨今だと、Gatsby、VuePress、Hexoなど静的サイトジェネレータを使うのが一般的かもしれませんが、上記のような構成だと静的サイトジェネレータ使うにはちょっと重いかなと思いました。
なので、Reactで既存のページを再現して、作品紹介の部分のみ、編集しやすくするようにしました。
スタイルはBootstrap から Material-UI、MasonryはReact用にreact-masonry-cssというのがあったのでこちらに変更しました。
また、作品紹介部分はMarkdownで書けると楽なので react-markdown を使うことにしました。
使用モジュール React react-masonry-css Material-UI react-markdown インストール
React は CLI を使ってセットアップしました。
npx create-react-app uedayou.net cd uedayou.net
以下のモジュールをインストールしました。
npm install react-masonry-css npm install @material-ui/core npm install react-markdown作品紹介コンポーネント
作品紹介部分はほぼ共通化されているので、作品紹介用の共用コンポーネントを作成しました。
Product.jsximport React from 'react' import ReactMarkdown from 'react-markdown/with-html'; import { makeStyles } from '@material-ui/core/styles'; import Card from '@material-ui/core/Card'; import CardHeader from '@material-ui/core/CardHeader'; import CardMedia from '@material-ui/core/CardMedia'; import CardActions from '@material-ui/core/CardActions'; import CardContent from '@material-ui/core/CardContent'; import Button from '@material-ui/core/Button'; import Typography from '@material-ui/core/Typography'; const useStyles = makeStyles((theme) => ({ media: { height: 0, paddingTop: '56.25%', backgroundPositionY: 0, }, })); function Product(props) { const classes = useStyles(); return ( <Card> <CardHeader title={props.title} /> <CardMedia className={classes.media} image={props.image} title={props.title} /> <CardContent> <Typography variant="body2" component="div"> <ReactMarkdown source={props.description || ""} escapeHtml={false} /> </Typography> </CardContent> <CardActions> {(props.links && props.links.length>0) && props.links.map(v=>( <Button variant="outlined" color="primary" href={v.link} key={v.link} target="_blank"> {v.label} </Button> ))} </CardActions> </Card> ) } export default Product;これで、たとえば
JrsLod.jsximport React from 'react' import Product from './Product'; function JrsLod() { return ( <Product title="鉄道駅LOD" image="app_images/jrslod-image.jpg" description={` [鉄道駅LOD](https://uedayou.net/jrslod/)は日本の鉄道に関するオープンデータを収集して提供するサイトです。 鉄道会社、鉄道路線、鉄道駅のデータを閲覧、ダウンロードできます。鉄道会社のデータには、全路線のポリラインデータ、路線データには路線のポリラインデータ、駅データには、駅のポイントデータをダウンロードできます。 JSONデータでダウンロードでき、CORSにも対応しています。Webアプリから直接利用することができます。 [鉄道駅LOD](https://uedayou.net/jrslod/)のデータを使ったWebアプリ [鉄道駅LOD GeoJSONダウンローダー](https://uedayou.net/jrslod-geojson-downloader/) の [ソースコード](https://github.com/uedayou/jrslod-geojson-downloader) を公開しています。 「鉄道駅LOD」のデータを使ったアプリを作るときに参考にしてみてください。 `} links={[ { label:"サイトを開く", link:"https://uedayou.net/jrslod/" }, { label:"解説記事", link:"https://qiita.com/uedayou/items/b5131b5ca930fe0bef69" }, ]} /> ) } export default JrsLod;のように必要な部分だけをコンポーネントにかけるようになりました。
description部分は Markdown で書けるようにしています。
改善の余地はあると思いますが、個人でやってるサイトであれば必要十分かと思います。ページへの登録
上記の作品紹介コンポーネントは、以下のように登録しています。
import React from 'react' import Masonry from 'react-masonry-css' import Container from '@material-ui/core/Container'; import { makeStyles } from '@material-ui/core/styles'; import Rosenoh from './products/Rosenoh'; import JrsLod from './products/JrsLod'; import LoajBook from './products/LoajBook'; ... const useStyles = makeStyles((theme) => ({ myMasonryGrid: { display: "flex", marginLeft: "-30px", width: "auto", }, myMasonryGridColumn: { paddingLeft: "30px", "& .MuiCard-root": { marginBottom: "30px" } } })); function Contents() { const classes = useStyles(); const breakpointColumnsObj = { default: 3, 1100: 2, 700: 1 }; return ( <Container maxWidth="lg"> <Masonry breakpointCols={breakpointColumnsObj} className={classes.myMasonryGrid} columnClassName={classes.myMasonryGridColumn}> <Rosenoh /> <JrsLod /> <LoajBook /> ... </Masonry> </Container> ) } export default Contents;
<Masonry>...</Masonry>
の間に作品紹介コンポーネントを記述します。
これで以下のように表示されます。コンポーネントの順番を変えれば、ページでの表示順も変わりますし、削除も簡単です。
これで従来の構成よりもだいぶ編集が楽になりそうです。
- 投稿日:2020-07-25T14:22:04+09:00
TypeScriptでstyled-componentsを使う時propsでstyleの制御ができなくて困った時の解決法
概要
TypeScriptでstyled-componentsを使いたくて公式ドキュメントのコードを写経していたら、
"Adapting based on props"の節の以下のコードが通らなくて困った。const Button = styled.button` /* Adapt the colors based on primary prop */ background: ${props => props.primary ? "palevioletred" : "white"}; color: ${props => props.primary ? "white" : "palevioletred"}; font-size: 1em; margin: 1em; padding: 0.25em 1em; border: 2px solid palevioletred; border-radius: 3px; `;原因
原因はpropsの部分に以下のエラーが出ていた。
> any > プロパティ 'primary' は型 'ThemedStyledProps<Pick<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "form" | ... 264 more ... | "value"> & { ...; }, any>' に存在しません。ts(2339)解決法
interfaceを以下の様に定義してあげるとコンパイルが通った。
import styled, { StyledComponent } from 'styled-components'; interface ButtonProps { primary: boolean; } export const Button = styled.button<ButtonProps>` /* Adapt the colors based on primary prop */ background: ${({primary}) => (primary ? 'palevioletred' : 'white')}; color: ${({primary}) => (primary ? 'white' : 'palevioletred')}; font-size: 1em; margin: 1em; padding: 0.25em 1em; border: 2px solid palevioletred; border-radius: 3px; `;ちなみにこの
Button
コンポーネントを使う側のコードは以下の様になる。import React from 'react'; import Button from '../../atoms/Button'; export const App: React.FC = () => { return ( <> <Button primary={false}> Button </Button> </> ); };
- 投稿日:2020-07-25T09:57:02+09:00
React(Next)/Go/GraphQL/Fargateで個人開発したサービス「読み方アンケート」をリリースするまで
個人開発である程度しっかりしたものを作ってリリースまでするってなかなか大変だと思います。
そこには技術的な壁がある人もいれば、なかなか本業が忙しくて時間がとれない人、途中まで開発して「こんなん誰が使うねん...」モードになってしまい頓挫する人。
そんないくつかの壁をなんとか乗り越え、
この度晴れて個人開発サービスである読み方アンケートをリリースできたのでまとめたいと思います。個人開発サービスリリース?
— じゃけぇ.tsx@読み方アンケート開発 (@bb_ja_k) July 18, 2020
恥ずかしい読み間違いをこの世から無くすための投票型読み方情報集約サイトです!
React(Next)/Go/AWS(Fargate)/GraphQLで作成
「◯◯ 読み方」で検索上位に出るサイトになります?
SNS等アカウントの宣伝できるので是非?#読み方アンケート https://t.co/NFgemOFMM6
〜こんな人に読んで欲しい〜
・ 個人開発のモチベーションをあげたい人
・ 個人でWebサービスをリリースするまでにするべきことを知りたい人
・ 以下の技術等に興味のある人
・ React(Next.js)
・ Go
・ GraphQL
・ AWS Fargate
・ AWS CodePipeline
・ GitHub Actions
・ TypeScript
・ Atomic Design
・ Recoil
・ Vercel書くこと(index)
サービス概要
サービス名: 読み方アンケート
URL: https://yomikata-enquete.com/
以下のように投票形式で読み方のデータを蓄積することで、世間一般的な読み方を知ることができる読み方情報蓄積サイトです。
ITは特にそうですが、専門用語の読み方って人によって違ったり、「え、そんな読み方すんの!?」みたいなのもあってややこしすぎますよね...
Djangoを「でぃーじゃんご」って読んでて陰で笑われてたあの頃...
そんな恥ずかしい読み間違いとサヨナラしてもらうために作りました
積極的に使ってくれるユーザーには恩恵があるようにしたいという思いがあったので、1つのミソとしては上で貼った画像のように読み方を聞いた人のプロフィールは単語ページの上部に大きく表示されます。
読み方を調べようとページに訪れた人の目に止まるので、検索流入によるアカウントの宣伝権利を半永久的に獲得できるというわけです。
また、以下のように投票した数や、その人のおかげで集まった投票数もカウントしているのでこの数字が大きい人のアカウントも目立つようにするつもりです(リリース予定)
「シェアして集めた投票数」はログインした状態で各単語ページのシェアボタンからシェアすると、そのリンク経由でページを訪れた人が投票するとカウントアップされていく仕組みです
目指すところとしては
■ このサイトで調べればある程度世間一般的な読み方がすぐ分かる
■ 「◯◯ 読み方」で検索したときに検索結果上位にでるサイトにすることという感じです。
是非使ってみてくださいm(..)m
サービス構成
技術的にどのような構成で作られているかを紹介します。
サービス構成概要
- 開発環境
- Docker
- フロントエンド
- React(Next)、TypeScript
- バックエンド
- Go
- データベース
- RDS(PostgreSQL)
- CI
- GitHub Actions
- CD
- CodePipeline
- 画像保存
- S3
- バックエンドデプロイ先
- ECS(Fargate)
- フロントエンドデプロイ先
- Vercel
フロントエンド
Reactで作成しています。
また、今回はSEOが重要だったためSSR(サーバーサイドレンダリング)が可能なフレームワークであるNext.jsを使用しています。
Nextは目を見張るスピードで進化していて、かなり使いやすいので使ったことない人は是非使ってみてください。
ルーティングの設定が不要ですし、最近(2020年5月)のリリース(ver9.4)で実装されたFast Refleshはかなり開発効率あがります。
コードを変更したときのホットリロードがかなり速く、しかもstateを保持したままリロードされます。例えばこれまでフォームのテキストボックスとかに値を入力した状態でコード変更すると頑張って入力したのが全部消えていたのが、それらの情報は保持されたまま修正箇所のみ変更されます。はい、最高です。
他にも便利な機能目白押しですb
コンポーネントの設計はAtomic Designをベースに行っています。
適切な粒度でコンポーネントを分割することで、開発効率や保守性が高くなります。詳しくは後述します。
また、APIのやりとりはGraphQLで行っています。TypeScriptとGraphQLの組み合わせはハッキリ言ってフロント開発の世界が変わります。RESTには戻れなくなります←
これもコードレベルの話は後述します。
バックエンド
バックエンドにはGoを使用しています。
普段は仕事でフロントエンドの開発をメインにしており、ずっとGoを使って自分で0からバックエンドの構築をしてみたいと思っていたので色々試せて面白かったです。
GraphQLを使用するので型付きの言語が相性が良く、マイクロサービスでサクッとAPIサーバーを立てるのにGoは適しているので今回はうまくハマりました。
DBとのやりとりの部分はGoのORM(オーアールマッパー)ライブラリであるgormを使用しました。
インフラ
マイクロサービス構築のノウハウが得たいというのもあり、フロントとバックは別々のサーバーにデプロイしようということは決めていました。
AWSをちゃんと自分で触ったことがなかったため、バックエンドはAWSを使って勉強を兼ねることにし、フロントはVercel(旧Zeit Now)にデプロイしています。
Vercelはフロントのデプロイ先としてめちゃくちゃオススメです。詳しくは後述します。
AWSでは主に以下のサービスを利用しています。
・Fargate(ECS)
・RDS(PostgreSQL)
・S3
・ECR
・ALB
・CodePipelineAWS周りは事前の知識が皆無だったこともありかなり苦労しましたが、今回の開発を通してだいぶレベルアップできたかなと思います。個人開発の醍醐味ですね。
あとは、GitHubにpushした際にテストを実行したりするのにGitHub Actionsを使用しました。途中まではCircleCIを使ってましたが、やはりGitHub内で完結するのは魅力的です。
なぜリリースまで辿り着けたか
ちょっと技術的な話から離れて、個人開発をリリースまで持っていくためのTips的なものを紹介します。
モチベーションを明確にする
これは個人開発だけでなく趣味でチーム開発をする時とかでも大事なんですが、
この開発におけるwhyを明確にする
ということが大切です。
「開発におけるwhy = モチベーション」と思ってますが、大体は以下のどっちがかなと思います。
1.アイディアを形にしたい
・過程より結果重視
・使ってもらうことが最優先
・作る時ワクワク、これ使われないなーと思うと一気にやる気↓↓2.開発を通して技術を学びたい
・結果より過程重視
・学習ありきの開発
・最悪使われなくても良いやチーム開発の場合、上記のようなメンバーのwhyが揃ってないパターンは大体頓挫します。
どちらが良い悪いではなく、自分は何故個人開発をやっているのかを理解して常々選択の軸にすることでブレずにリリースまで持っていける可能性が上がります。
因みに自分は今回の場合、2が70%くらいの比重を占めています。
感覚としてこれ以上2の比重が高くなってしまうとモチベーションを保って開発を続け、リリース日を迎えることがむずかしいです。
タスクを見える化する
仕事だけでなく個人開発でも見える化はめっちゃ大事です。
見える化してないと夜パソコンの前に座ってもどうしてもやる気出てこなかったり、「どこ実装しようかな〜」って考えながら現状でできてるアプリを動かして目についたとこ実装していくみたいな無駄の多い開発になります。
そして「あ、こんな機能ありやな〜」て移動中とか思いついたアイディアが忘れ去られたりします...
具体的にはTrelloに代表されるようなタスク管理ツールを使います。自分の場合はClickUpを使用しています。
ClickUpは無料で、プライベートな空間で使用できますし、タスクレーンのカスタムもできたり、Slack通知も簡単に設定できるので仕事でも重宝しています。
以下のようにレーンを分けたりラベルを付けて優先順位やタスクタイプを見える化します。(テンション上がる)(一部都合上隠しています)
「その先」を妄想する
たぶん個人開発者あるあるだと思います←
サービスをリリースした未来を妄想します。
- サービスが人気になって「◯◯作った人ですよね!」って認識される未来
- そこで学んだ技術を生かして仕事で活躍できる未来
- そのサービスがきっかけでバイネームで仕事を依頼される未来
- 大当たりして個人開発で食べていける未来
冗談のように聞こえますが、個人開発をずっと続けれている人達はこの妄想力が段違いなんじゃないかと思ってます←
必須スキルかもしれません。
Webサービスをリリースするまでにすること
では個人でWebサービスを開発、リリースするまでどんなことをしなければいけないのか紹介します。
これから開発する方は参考にして頂ければと思います。
(抜け漏れある気がするのでコメントで補足してもらえると助かりますm(..)m)
※ * がついている項目は必須
システム構成を考える*
まずは当たり前ですが構成を考えます。
- フロント、バックに何を使うか
- DBは使うのか、使う場合はRDBにするのかNoSQLにするのか等
- デプロイ先はどこにするのか
ワイヤーフレームを作成する
これは必須ではありませんが、サイトの設計書とも呼ばれるワイヤーフレームを考えた方が開発はスムーズに進みます。
手書きでざっくりでも良いですし、得意な人はXDやFigmaを使って作成しても良いでしょう。
因みに自分は今回は何にも作らなかったです←
なんか個人開発くらい場当たり的に画面作ってみたかったのです...
DB設計をする*
データベースを使用する場合はDB設計は必須です。
これは場当たり的にはやってはいけません
DB設計はどうしても数をこなさないと上手くできないと思いますが、考えることを諦めてはいけません。
実装したい機能からどんなテーブルが必要なのか、将来機能追加したときに破綻しないか等々考慮してテーブルや項目を洗い出していきましょう。
開発する*
ここまでで開発は進めれる状態になったのでつらつらと開発しましょう。
頑張りましょう。
頑張りましょう。。。
デプロイ先を考える*
世に公開するためにはどこかのサーバーに作成したアプリをデプロイしなければいけません。
使用している技術やCI/CDとの相性、ランニングコスト等比較して早い段階で決めておきましょう。
CI/CD環境の整備をする
継続して安定的なサービスを提供し続けるためには今やCI/CD環境は必須と言えます。
GitHub Actions、CircleCI、CodePipeline等々選択し、実装していきましょう。
404や500ページのことも考える
意外と盲点なのが存在しないページが開かれた場合の404ページと、サーバーエラーが発生した場合の500ページです。
一般的なサービスは大体このあたりも綺麗に作成されています。
因みにNextの場合、
/pages/
以下に404.tsx
、_error.tsx
のようにファイルを作成しておくだけでカスタムできるので最高です。参考までに読み方アンケートは以下のようにしています。
(500ページにTwitterのタイムラインを載せるTipsはnoteから学びました。サーバートラブルがあってもユーザーに状況を伝えることができます)
meta,titleタグを整備する*
Webサービスの場合、meta,titleタグの設定は必須です。
検索エンジンやブラウザに伝える情報を記述します。
これらはHTMLのheadタグの中に記述するもので、titleタグはページのタイトル情報でブラウザのタブに表示されるものがそうです。
metaタグは色々な種類があり、SNSにURLをシェアした際の画像や文章を設定するOGPの設定や、ページの説明文などを記述していきます。
faviconやtouch-iconを作成する*
faviconは以下のようなブラウザのタブ等に表示される画像です。間違ってもデフォルトのままリリースしないようにしましょう。
apple-touch-iconはスマホやiPadでホーム画面に追加された時の画像を設定します。
faviconに比べ優先度は低いですが、設定しておくにこしたことはありません。(PWA化する場合は必ず設定しておきましょう)
以下のように180×180サイズを設定しておけば大丈夫(のはず)です。
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />各ブラウザ、端末での動作を確認する*
一通りシステムが形になってきたら各ブラウザや、スマホ等でレイアウト崩れが起きていないか、機能が正常に動くかを確認しましょう。
PCでスマホのレイアウトを確認する場合、各ブラウザのDeveloper Tool等を使うと思いますが、実機で見たらレイアウトが異なることがあるのでちゃんと実機で見てみることが大事です。
IEは、、、個人開発であれば無視しましょう←
外形監視を導入する
これは最初からやる必要はないと思いますが、個人開発でやってみておくと良い経験になるし仕事に生かせると思います。
Sentryに代表される外形監視ツールは、ユーザーが操作している際フロントエンドで何かエラーが起きたときにSlack等に通知をしてくれるものです。
フロントエンドのエラーはユーザーの使い勝手に直結するので、知らない間にエラーが起きていてユーザーが離れていくということを避けられます。
余裕があれば導入してみましょう。(個人開発レベルであれば無料で使えます)
ドメインを取得する*
https://yomikata-enquete.com/
のyomikata-enquete.com
の部分です。ドメインの取得は必須です。年間で数百円とかなので惜しまずに買いましょう。
色んなサービス経由で買えますが、私はお名前ドットコムで買っています。
どこでも対して差はないので大丈夫です。
Google Analyticsへの登録
Google Analyticsは必須ではないですが、絶対設定しておいたほうが良いです。
ユーザーが今何人サイトのどのページを見ているかリアルタイムで見れたり、日や月単位で何人に訪問されたのか、直帰率はどのくらいか等々分析することができます。
リリース情報を公開したあとなんかはずっとリアルタイムの数字見てワクワクできます。そりゃあもうビール3杯くらいそれでいけます。
Google Search Consoleへの登録
Google Analyticsがユーザーの分析なら、Search Consoleは検索エンジンの分析です。
どんな検索ワードでサイトが訪問されたのか、googleにindex(検索結果にでるように認識されていること)されているか、されていない場合はリクエストしたり等々
検索流入に対する対策をたてるのに使用できます。GAと並んでとても重要です。どちらも無料で使用できるので導入しておきましょう。
単に開発するだけでなく、上記のことを乗り越えて初めてリリースを迎えることができます。頑張りましょう!b
フロントエンド
ではここからは実装レベルでの細かいTips等を紹介します。
ざっくりとの紹介になるので、気になる部分がある人は質問等で聞いてもらえたらと思いますm(..)m
フロントエンドでは以下のライブラリを使用しています(一部)
■ React、Next.js、TypeScript
- https://github.com/facebook/react
- https://github.com/vercel/next.js
- https://github.com/microsoft/TypeScript
このセットはかなりDX(Developer Experience)が向上します。
VueとReactを比較した記事も書いていてけっこう読んで頂いているので参考にしてみてください。
あらためてReactとVueを比較してみる〔2020年最新版〕
■ eslint、prettier
コード自動整形や静的解析のために使用。
ESLintとPrettierを連携してVSCodeで保存時に自動で整形されるための設定は以下で紹介しています。(GitHubのリポジトリもあります)
良い感じのReact開発をすぐに始めれるテンプレートプロジェクトを作ったので共有します
■ GraphQL
https://graphql.org/
GraphQLはFacebookが開発したRESTに代わる新しいAPIの規格です。
schema(スキーマ)と呼ばれるAPIの設定ファイルを基盤にAPIを作成していくことからスキーマ駆動開発と呼ばれる開発フローが可能になります。https://www.apollographql.com/
ApolloはGraphQLを使うための様々な便利な機能を提供しているライブラリで、クライアントもサーバーもサポートしています。キャッシュができたり、reactの場合、hooksライクにAPI実行できたりするので非常に便利です。以下のgraphql-codegen
と組み合わせることでschema作成→API呼び出しが超簡単になります。https://github.com/dotansimha/graphql-code-generator
schema情報から、API実行するための口や型情報を自動生成してくれるためのライブラリです。
以下に簡単なサンプルを紹介します。1.schemaを定義
以下はIDから1つの単語を取得するクエリ定義の例です。# queryはデータ取得。その他mutationやsubscription schema { query: Query } # データ取得はQueryに定義する、引数や返却型を定義 type Query { word(wordID: Int!): Word! } # 型情報 type Word { wordId: Int! title: String! }
2.クエリを定義するschemaを基に取得するためのクエリを定義します。
RESTのようにデータを全て受け取るのではなく、ここでフロントが必要なデータのみ指定できるのもGraphQLの強みです。word.graphqlquery word($wordID: Int!) { word(wordID: $wordID) { wordId title } }
3.自動生成する
以下のコマンドを実行するとschema情報を基にファイルが自動生成されます。(別途設定ファイルの記載は必要。package.jsonのscriptに定義すると楽)graphql-codegen --config codegen.yml
4.生成されたファイル以下のようなファイルが自動生成されます。
type Word
の型情報や、useWordQuery
などのAPI実行用の口ができていることが確認できます。graphqlClientApi.tsximport gql from 'graphql-tag'; import * as ApolloReactCommon from '@apollo/react-common'; import * as ApolloReactHooks from '@apollo/react-hooks'; export type Maybe<T> = T | null; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { ID: string; String: string; Boolean: boolean; Int: number; Float: number; }; /** データ取得はQueryに定義する、引数や返却型を定義 */ export type Query = { __typename?: 'Query'; word: Word; }; /** データ取得はQueryに定義する、引数や返却型を定義 */ export type QueryWordArgs = { wordID: Scalars['Int']; }; /** 型情報 */ export type Word = { __typename?: 'Word'; wordId: Scalars['Int']; title: Scalars['String']; }; export type WordQueryVariables = { wordID: Scalars['Int']; }; export type WordQuery = { __typename?: 'Query' } & { word: { __typename?: 'Word' } & Pick<Word, 'wordId' | 'title'> }; export const WordDocument = gql` query word($wordID: Int!) { word(wordID: $wordID) { wordId title } } `; /** * __useWordQuery__ * * To run a query within a React component, call `useWordQuery` and pass it any options that fit your needs. * When your component renders, `useWordQuery` returns an object from Apollo Client that contains loading, error, and data properties * you can use to render your UI. * * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; * * @example * const { data, loading, error } = useWordQuery({ * variables: { * wordID: // value for 'wordID' * }, * }); */ export function useWordQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<WordQuery, WordQueryVariables>) { return ApolloReactHooks.useQuery<WordQuery, WordQueryVariables>(WordDocument, baseOptions); } export function useWordLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<WordQuery, WordQueryVariables>) { return ApolloReactHooks.useLazyQuery<WordQuery, WordQueryVariables>(WordDocument, baseOptions); } export type WordQueryHookResult = ReturnType<typeof useWordQuery>; export type WordLazyQueryHookResult = ReturnType<typeof useWordLazyQuery>; export type WordQueryResult = ApolloReactCommon.QueryResult<WordQuery, WordQueryVariables>;
5.APIを実行する上記で作成したAPIから取得したデータを表示したい場合、以下のようにするだけです!
型情報を別途定義する必要もなければ、error情報やloadingの情報も含まれています。
これがめっっっっっちゃ楽です。
■ Atomic Design
Atomic DesignはアメリカのWebデザイナーBrad Frost氏が考案・提唱したデザインシステムです。
細かい説明は色々なサイトで紹介されているので省きますが、自分がフロント構築する場合はAtomic Designベースでやります。
「ベースで」というのが大事で、自分の中でコンポーネント分割はあくまで概念であって、厳密なルールを強制するものではないと思っています。
なので、ベースはありつつもプロジェクト毎にメンバーで話し合って適宜カスタムしていって良いと思います。
コンポーネント分割というと「再利用」というメリットがフォーカスされがちですが、TypeScriptと組み合わせることで実装に制約を注入できるというのも大きいです。例えば以下のようにloadingというpropsを必須のpropsとして定義しておくことでボタンを配置するときに押下時のloadingを設定しないとエラーになるので実装者にルールを意識してもらえます。
MainButton.tsximport React, { ReactNode } from 'react'; import { Button } from 'semantic-ui-react'; type Props = { children: ReactNode; loading: boolean; onClick: () => void; }; export const MainButton = (props: Props) => { const { children, loading, onClick } = props; return ( <Button loading={loading} disabled={loading} onClick={onClick}> {children} </Button> ); };■ Semantic UI React
CSSライブラリとしてSemantic UI Reactを使用しています。
BootstrapやMaterial UIだと人と被るしな〜と思っている人にSemantic UI Reactはオススメです。
サンプルの例が丁寧ですし、色の選択も豊富です。もちろんレスポンジブやGrid系の仕組みも提供されていますし、読み方アンケートのTOPページで実装しているようなアニメーションも簡単に実装できます。
■ styled-components
ReactではCSSのあてかたに様々なパターンがあり、宗教戦争がおきがちです←
自分はstyled-components(以下SC)を好んで使います。
CSS-in-JSの中でもSCを使用している理由はscssライクに使えるのでもし方針の変更があってもscss→SC、SC→scssといった変換が容易というのと、hoverなどの擬似要素やメディアクエリも同様に使えるところが大きいです。
以下は読み方アンケートで使用している、リンクテキスト用のコンポーネントの例です。
LinkText.tsximport React, { ReactNode } from 'react'; import styled from 'styled-components'; import { COLOR } from 'util/const'; type Props = { children: ReactNode; onClick: () => void; }; export const LinkText = (props: Props) => { const { onClick } = props; return <SLink onClick={onClick}>{props.children}</SLink>; }; const SLink = styled.p` color: ${COLOR.LINK}; cursor: pointer; margin: 0; &:hover { text-decoration: underline; color: ${COLOR.LINK_HOVER}; } `;scssの書き方がそのまま使用できるのでhover時のスタイル変更もjs内で簡単に適用できます。
問題としてあげられるのが、SCを使うと外部ライブラリから読み込んで使用しているコンポーネントか、SCのコンポーネントかパっと見で分かりづらいので、自分は
S
プレフィックスを付けるルールをよく使います。■ react-spinners
- https://github.com/davidhu2000/react-spinners ローディングのインジケーターに使用しています。
ローディングをちょっと工夫したい人は是非。
読み方アンケートだと以下のローディングがそうです。
■ Recoil
- https://github.com/facebookexperimental/Recoil
状態管理としてRecoilを使ってみました。少し前に公開されて話題になったやつです。上記のローディングを表示する箇所に使ってみています。
以下のような状態用atomを定義して
loadingState.tsimport { atom } from 'recoil'; export const loadingState = atom({ key: 'loadingState', default: false, });trueの場合、黒い背景(Dimmer)上にローディングアイコンが表示されるようにコンポーネントをセットしておきます。
あとは下記のように任意の場所でstateを設定することでグローバルな状態管理が可能になります。
import React from 'react'; import { Button } from 'semantic-ui-react'; import { useSetRecoilState } from 'recoil'; import { loadingState } from 'store/recoil/loadingState'; export const UserPublish = () => { const setRecoilLoading = useSetRecoilState(loadingState); const onClickButton = () => { setRecoilLoading(true); }; return <Button onClick={onClickButton}>ボタン</Button>; };まだ複雑な状態管理は試してないですが、かなり可能性を感じます。
■ react-alert
- https://github.com/schiehll/react-alert
メッセージ表示のライブラリとして使用しています。hooksライクに使えるので重宝しています。
なんでこんなにstarが少ないんだろう..
バックエンド
■ Go
- https://github.com/golang/go
今更説明は不要かと思います。今回の規模のサービスでは特に困ることはありませんでした。ビルドが早いのは良いですね。
■ gorm
- https://github.com/go-gorm/gorm
ORMライブラリとしてgormを使用しています。goのORMでは1番シェアが大きいのではないかと思います。DBとのやりとりがシンプルになりますし、素のSQLも簡単に実行できるので助かりました。
■ GraphQL
- https://github.com/99designs/gqlgen
GoでGraphQLを扱うためのライブラリとしてgqlgenを使用しています。 フロントエンドと同様、shemaファイルを基に型情報やresolverの情報を自動生成できるので重宝します。また、GraphQL Playgroundが含まれているので、バックエンドだけ実装した後GUI上で簡単にAPIを実行した結果を確認できるのでわざわざAPI実行のために別のツールを導入する必要がなくなります。
■ jwt-go
- https://github.com/dgrijalva/jwt-go
認証にJWTを使用しているので使用しています。一般的なJWT系のライブラリと同じ感じで使えます。
■ go-chi
- https://github.com/go-chi/chi
ルーティングに使用。■ CORS
- https://github.com/rs/cors
CORSの設定に使用。何もしないと別ドメインからのリクエストはエラーになるのでマイクロサービスの場合は設定必須。インフラ-環境周り
読み方アンケートで設定しているインフラや環境周りを紹介します。
■ Docker
開発環境はDockerで構築しています。
nginxを含めてリバースプロキシすることで
http://localhost:3000/
ではなく、http://localhost/
でアクセスできるようにしています。(色んなプロジェクトで3000使うので不便なんですよね...)以下は読み方アンケートのdocker-composeファイルです。
docker-compose.ymlversion: '3' services: db: image: postgres:11-alpine volumes: - db:/var/lib/postgresql/data - ./docker/init:/docker-entrypoint-initdb.d environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres ports: - "5434:5432" container_name: yomikata-postgres nginx: image: nginx:stable container_name: nginx ports: - '80:80' volumes: - ./conf.d:/etc/nginx/conf.d links: - front front: build: context: . dockerfile: ./docker/front/Dockerfile container_name: yomikata-front hostname: front volumes: - .:/yomikata-enquete:cached - node_modules:/yomikata-enquete/node_modules:cached ports: - '3001:3000' api: build: context: . dockerfile: ./docker/api/Dockerfile environment: DB_INFO: host=db port=5432 user=postgres dbname=postgres password=postgres sslmode=disable ports: - "8081:8081" container_name: yomikata-api tty: true volumes: - .:/usr/src/app command: go run ./server/cmd/server.go depends_on: - db volumes: db: driver: local node_modules: driver: local■ AWS Fargate
バックエンドはFargateで構築しました。
初めてだったのでまぁまぁ大変でした...
手順もまとめているのですが、ここに書くと長くなるので別途記事にしようと思います。
■ AWS CodePipeline
CDのためにCodePipelineを使用しています。
GitHubの
production
ブランチへのpushに反応して動き、ビルドやデプロイを自動でしてくれます。また、Slackへの通知も簡単に設定できます。
読み方アンケートでは以下のように開始と終了の通知をSlackに送るようにしています。
GitHub Actions
各ブランチをGitHubにプッシュした際にテストを実行するためにGitHub Actionsを使用しています。
/.github/workflows/
にymlで設定ファイルを記述します。読み方アンケートでフロントのテストを実行する場合は以下のようなファイルを定義しています。
front_test.ymlname: front_test on: push jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup NodeJs uses: actions/setup-node@v1 with: node-version: '10.x' - name: Cache dependencies uses: actions/cache@v1 with: path: ~/node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-node- - name: Install Dependencies run: yarn install --frozen-lockfile - name: Run test run: yarn testちなみにGitHub Actionsの勉強で買った以下の本は読みやすくて良かったです。
GitHub Actions 実践入門
Vercel
フロントのデプロイ先にはVercel(旧Zeit Now)を使用しています。
GUI上でGitHubのリポジトリとの連携設定を簡単にできて、ブランチをpushする度に各ブランチに対応したPreview用のURLを生成してくれるのでチーム開発では力を発揮します。
(わざわざブランチをpullしてきてローカルで実行して確認しなくて良い)Next.jsを開発している会社と同じなのでNextのデプロイもめちゃくちゃ簡単ですし、ReactやVueのデプロイもほぼ0configで可能です。
SSL化もデフォルトでされており、別途証明書を発行する手間もありませんし、お名前ドットコムなど外部で取得したドメインを設定するのも簡単にできます。
気になるお値段ですが、
なんと個人利用の場合無制限で無料です
いや、太っ腹かよ( ゚д゚)
ぜひ使ってみてください。
さいごに
個人開発、大変ではありますがやはり実際のプロダクトを作ることで得られる経験というのは本を読んだりUdemyで勉強するのとは段違いだと思います。
仕事と違って誰にも合わせる必要なく自分の思うがままに進めれるのも良いですしね←
ちなみに次は React × AWS Amplify で何か作ってみるつもりです。
読み方アンケートや使用している技術に関して質問があればこの記事にコメントしていただくか、私のTwitterにDMでもかまわないのでお待ちしています。
個人開発がんばりたい人、個人開発の先駆者の方、フォローお待ちしております。お互い頑張りましょうb