App PORT製作のまとめ

## URL
https://stevesan-appport.herokuapp.com/
## テスト用アカウント
email: test@test.com
password: Qwe123123
※自由に投稿していただいて構いません
# 概要
他の人が製作したアプリの閲覧、
またはユーザー登録することで自分の製作したアプリを投稿できます。
# 製作理由
私がポートフォリオを作成しようと考えた時、
他の人たちはどのようなサービスを製作したのか知りたかったのですが、
まとまったサイトがなく不便であったため製作しようと考えました。
# 利用方法
- All App
  縦スクロールで、それぞれのユーザーが投稿したアプリを閲覧でき、
  横スクロールではそのユーザーが投稿した他のアプリを閲覧できます。
- Login
  ユーザーの新規登録、またはログインができます。
### **ログイン後**
- My App
  自分が投稿したアプリを閲覧できます。
  このページでアプリ情報の編集と削除ができます。
- Add New App
  画像、タイトル、説明、URL を入力し、新しくアプリを投稿できます。
# 使用技術:**フロントエンド**
### 言語
- HTML&CSS
- Javascript(Typescript)
### フレームワーク&ライブラリ
- React
- Redux
- React Router
- tailwindCSS
### モジュールバンドラー
- webpack
# 使用技術:**バックエンド**
### 言語
- Javascript(Typescript)
### フレームワーク&ライブラリ
- Node.js(Express)
### **データベース**
- MongoDB(ユーザー情報、アプリ情報の保存)
### **ストレージ**
- AWS S3(画像の保存)
### **セキュリティ対策**
- helmet(HTTP ヘッダー関係)
- bcrypt(パスワードをハッシュ化)
- passport-local / passport-jwt(ユーザー認証)
# 実装予定
- Google 認証
- 登録メールアドレスの URL 認証
# 製作までの道のり

### Webpackで環境構築(React+TypeScript+Babel+ESLint+Prettier+TailwindCSS)
前回webpackでCreateReactAppを使用しないReact環境の構築をしたが、
アプリを制作する上で新たに追加・変更した点があるため再度環境構築しながらもう一度まとめる。

#### npm 初期設定
```
mkdir webpack_React
cd webpack_React
npm init
```
#### webpack関係 インストール
```
npm i -D webpack webpack-cli webpack-merge webpack-dev-server
npm i -D html-loader css-loader postcss-loader babel-loader @svgr/webpack
npm i -D html-webpack-plugin css-minimizer-webpack-plugin mini-css-extract-plugin terser-webpack-plugin eslint-webpack-plugin dotenv-webpack
```
#### babel関係 インストール
```
npm i core-js
npm i -D @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
```
#### eslint関係 インストール
```
npm i -D eslint eslint-plugin-react eslint-plugin-react-hooks eslint-config-prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser
```
#### prettier インストール
```
npm i -D prettier
```
#### typescript インストール
```
npm i @types/react @types/react-dom @types/tailwindcss
npm i -D typescript
```
#### tailwindcss インストール
```
npm i -D postcss autoprefixer tailwindcss
```
#### react インストール
```
npm i react react-dom
```
#### ファイル作成
```
touch {webpack.common.js,webpack.dev.js,webpack.prod.js}
touch babel.config.json
touch {.eslintignore,.eslintrc.js}
touch {.prettierignore,.prettierrc}
touch {postcss.config.js,tailwind.config.js}
touch {.env.dev,.env.production}
```
#### webpack 設定
```javascript:webpack.common.js
const path = require("path");
const ESLintPlugin = require("eslint-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = ({ outputFile, assetFile }) => ({
	entry: path.resolve(__dirname, "src/index.tsx"),
	output: {
		path: path.resolve(__dirname, "build"),
		publicPath: "/",
		filename: `js/${outputFile}.js`,
		chunkFilename: `js/${outputFile}.js`,
		clean: true,
	},
	plugins: [
		new ESLintPlugin({
			extensions: [".ts", ".tsx", ".js"],
			exclude: "node_modules",
		}),
		new MiniCssExtractPlugin({
			filename: `css/${outputFile}.css`,
		}),
	],
	module: {
		rules: [
			{
				test: /\.(ts|tsx)$/i,
				loader: "babel-loader",
				exclude: /node_modules/,
			},
			{
				test: /\.(eot|ttf|woff|woff2|png|jpg|gif|jpeg)$/i,
				type: "asset/resource",
				generator: {
					filename: `asset/${assetFile}[ext]`,
				},
			},
			{
				test: /\.html$/, //HtmlWebpackPluginがないと動作しない
				use: ["html-loader"],
			},
			{
				test: /\.css$/,
				use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
			},
			{
				test: /\.svg$/i,
				type: "asset",
				resourceQuery: /url/, // *.svg?url
			},
			{
				test: /\.svg$/i,
				issuer: /\.([jt]sx|js|ts)?$/,
				resourceQuery: { not: [/url/] }, // exclude react component if *.svg?url
				use: ["@svgr/webpack"],
			},
		],
	},
	resolve: {
		modules: [path.resolve(__dirname, "node_modules")],
		extensions: [".tsx", ".ts", ".js"],
	},
	optimization: {
		splitChunks: {
			cacheGroups: {
				vendors: {
					name: "vendors",
					test: /[\\/]node_modules[\\/]/,
					chunks: "all",
					reuseExistingChunk: true,
				},
			},
		},
	},
	stats: {
		children: true,
	},
});
```
```javascript:webpack.dev.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const Dotenv = require("dotenv-webpack");
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");

const outputFile = "[name]";
const assetFile = "[name]";

module.exports = merge(common({ outputFile, assetFile }), {
	mode: "development",
	devtool: "inline-source-map",
	devServer: {
		open: true,
		static: {
			directory: path.join(__dirname, "build"),
			watch: {
				ignored: "node_modules",
			},
		},
		compress: true,
		port: 3000,
		historyApiFallback: true /* /home/hoge に直接リンクすると表示されないためfallbackで対策 */,
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: "./src/index.html",
		}),
		new Dotenv({ path: "./.env" }),
	],
});
```
```javascript:webpack.prod.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const Dotenv = require("dotenv-webpack");
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");

const outputFile = "[name].[chunkhash]";
const assetFile = "[name].[contenthash]";

module.exports = merge(common({ outputFile, assetFile }), {
	mode: "production",
	plugins: [
		new HtmlWebpackPlugin({
			template: "./src/index.html",
			minify: {
				collapseWhitespace: true,
				keepClosingSlash: true,
				removeComments: true,
				removeRedundantAttributes: true,
				removeScriptTypeAttributes: true,
				removeStyleLinkTypeAttributes: true,
				useShortDoctype: true,
			},
		}),
		new Dotenv({ path: "./.env.production" }),
	],
	optimization: {
		minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
	},
});
```
#### babel設定
```json:babel.config.json
{
	"presets": [
		[
			"@babel/preset-env",
			{
				"useBuiltIns": "usage",
				"corejs": 3
			}
		],
		[
			"@babel/preset-react",
			{
				/*options*/
			}
		],
		[
			"@babel/preset-typescript",
			{
				/*options*/
			}
		]
	]
}
```
#### eslint設定
```.eslintignore
**/node_modules/
**/build/**
webpack.**
```
```javascript:.eslintrc.js
module.exports = {
	env: {
		browser: true,
		es2021: true,
		node: true,
	},
	extends: [
		"eslint:recommended",
		"plugin:react/recommended",
		"plugin:@typescript-eslint/recommended",
		"prettier",
	],
	parser: "@typescript-eslint/parser",
	parserOptions: {
		ecmaFeatures: {
			jsx: true,
		},
		ecmaVersion: "latest",
		sourceType: "module",
	},
	plugins: ["react", "react-hooks", "@typescript-eslint"],
	rules: {
		indent: ["error", "tab", { SwitchCase: 1 }],
		"linebreak-style": ["error", "unix"],
		quotes: ["error", "double"],
		semi: ["error", "always"],
	},
	settings: {
		react: {
			version: "detect",
		},
	},
};
```
#### prettier設定
```.prettierignore
**/node_modules/
**/build/**
```
```.prettierrc
{
	"tabWidth": 2,
	"useTabs": true,
	"semi": true
}
```
#### typescript設定
```
npx tsc --init
```
```json:tsconfig.json
{
	"compilerOptions": {
		"target": "ESNext",
		"jsx": "react",
		"module": "commonjs",
		"moduleResolution": "node",
		"sourceMap": true,
		"noEmit": true,
		"esModuleInterop": true,
		"forceConsistentCasingInFileNames": true,
		"strict": true,
		"skipLibCheck": true
	},
	"include": ["src/**/*.ts", "src/**/*.tsx"]
}
```
#### tailwindcss設定
```javascript:postcss.config.js
module.exports = {
	plugins: {
		tailwindcss: {},
		autoprefixer: {},
	},
};
```
```javascript:tailwind.config.js
module.exports = {
	content: ["./src/**/*.{html,tsx,ts,js}"],
	// theme: {extend: {}},
	plugins: [],
};
```
#### html/css/reactのテンプレート作成
```
mkdir src
cd src
touch {index.html,main.css,index.tsx,App.tsx,custom.d.ts}
```
```html:index.html
<!DOCTYPE html>
<html lang="ja">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<div id="root"></div>
	</body>
</html>
```
```css:main.css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
```typescript:App.tsx
import React from "react";

const App = () => {
	return <p>Hello World</p>;
};

export default App;
```
```typescript:index.tsx
import React from "react";
import ReactDOM from "react-dom/client";

import App from "./App";
import "./main.css";

const container: HTMLElement | null = document.getElementById("root");
const root = ReactDOM.createRoot(container as HTMLElement);

root.render(<App />);
```
```custom.d.ts
declare module "*.jpg";
declare module "*.png";
declare module "*.jpeg";
declare module "*.gif";
declare module "*.svg";
declare module "*.svg?url";
```
#### packege.json　以下を追加
```json:packege.json
{
	"scripts": {
		"start": "webpack-dev-server --config ./webpack.dev.js",
		"dev": " webpack --config ./webpack.dev.js",
		"build": "webpack --config ./webpack.prod.js",
		"lint": "eslint src && prettier --write src",
		"lint:fix": "eslint --fix src && prettier --write src"
	},
	"browserslist": [
		"defaults",
		"not IE 11"
	],
}
```
# ※随時更新