URL
テスト用アカウント
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 設定
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,
},
});
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" }),
],
});
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設定
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.**
.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
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設定
postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
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}
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>
main.css
@tailwind base;
@tailwind components;
@tailwind utilities;
App.tsx
import React from "react";
const App = () => {
return <p>Hello World</p>;
};
export default App;
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 以下を追加
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"
],
}