diff --git a/index.html b/index.html index 35b62c4d..f2c3667d 100644 --- a/index.html +++ b/index.html @@ -8,8 +8,11 @@ memegle - gif search engine for you diff --git a/package.json b/package.json index a7908f70..b64fca7d 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,13 @@ "build:prod": "webpack --mode=production --node-env=production", "watch": "webpack --watch", "serve": "webpack serve --mode=development", + "serve:prod": "webpack serve --mode=production --node-env=production", "prettier": "prettier --write .", "deploy": "npm run build:prod && npx gh-pages -d dist" }, "keywords": [], "author": "woowacourse", - "homepage": "https://{username}.github.io/perf-basecamp", + "homepage": "https://d0dam.github.io/perf-basecamp", "license": "MIT", "dependencies": { "@giphy/js-fetch-api": "^4.1.1", @@ -33,13 +34,14 @@ "@types/react": "^18.0.17", "@types/react-dom": "^18.0.6", "@types/react-router-dom": "^5.3.3", - "@typescript-eslint/eslint-plugin": "^5.35.1", + "@typescript-eslint/eslint-plugin": "^5.0.0", "@webpack-cli/generators": "^2.2.0", "babel-loader": "^8.2.2", "copy-webpack-plugin": "^9.0.1", "css-loader": "^6.2.0", + "css-minimizer-webpack-plugin": "^5.0.1", "dotenv-webpack": "^7.0.3", - "eslint": "^7.32.0", + "eslint": "^8.0.1", "eslint-config-prettier": "^8.5.0", "eslint-config-standard-with-typescript": "^22.0.0", "eslint-plugin-hooks": "^0.4.3", @@ -50,21 +52,28 @@ "file-loader": "^6.2.0", "html-loader": "^2.1.2", "html-webpack-plugin": "^5.3.2", + "mini-css-extract-plugin": "^2.7.6", "prettier": "^2.3.2", - "style-loader": "^3.2.1", "ts-loader": "^9.3.1", "typescript": "^4.8.2", - "webpack": "^5.50.0", + "webpack": "^5.88.2", + "webpack-bundle-analyzer": "^4.9.1", "webpack-cli": "^4.7.2", "webpack-dev-server": "^3.11.2" }, "babel": { "presets": [ "@babel/preset-env", - "@babel/preset-react" + "@babel/preset-react", + { + "modules": false + } ], "plugins": [ "@babel/plugin-transform-runtime" ] - } + }, + "sideEffects": [ + "*.css" + ] } diff --git a/src/App.tsx b/src/App.tsx index 978d59d2..4c357d93 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,8 @@ +import { lazy } from 'react'; import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; -import Home from './pages/Home/Home'; -import Search from './pages/Search/Search'; +const Home = lazy(() => import(/* webpackChunkName: "home" */ './pages/Home/Home')); +const Search = lazy(() => import(/* webpackChunkName: "search" */ './pages/Search/Search')); import NavBar from './components/NavBar/NavBar'; import Footer from './components/Footer/Footer'; @@ -10,7 +11,7 @@ import './App.css'; const App = () => { return ( - + } /> diff --git a/src/assets/images/find.gif b/src/assets/images/find.gif deleted file mode 100644 index a82c1611..00000000 Binary files a/src/assets/images/find.gif and /dev/null differ diff --git a/src/assets/images/find.mp4 b/src/assets/images/find.mp4 new file mode 100644 index 00000000..2bbde8af Binary files /dev/null and b/src/assets/images/find.mp4 differ diff --git a/src/assets/images/free.gif b/src/assets/images/free.gif deleted file mode 100644 index a99029a7..00000000 Binary files a/src/assets/images/free.gif and /dev/null differ diff --git a/src/assets/images/free.mp4 b/src/assets/images/free.mp4 new file mode 100644 index 00000000..d53dbe87 Binary files /dev/null and b/src/assets/images/free.mp4 differ diff --git a/src/assets/images/hero.png b/src/assets/images/hero.png deleted file mode 100644 index ac7efc4e..00000000 Binary files a/src/assets/images/hero.png and /dev/null differ diff --git a/src/assets/images/hero.webp b/src/assets/images/hero.webp new file mode 100644 index 00000000..1fbe45fd Binary files /dev/null and b/src/assets/images/hero.webp differ diff --git a/src/assets/images/trending.gif b/src/assets/images/trending.gif deleted file mode 100644 index 6dcf7ad1..00000000 Binary files a/src/assets/images/trending.gif and /dev/null differ diff --git a/src/assets/images/trending.mp4 b/src/assets/images/trending.mp4 new file mode 100644 index 00000000..1c3eb86a Binary files /dev/null and b/src/assets/images/trending.mp4 differ diff --git a/src/data/gifCache.ts b/src/data/gifCache.ts new file mode 100644 index 00000000..941bb6e2 --- /dev/null +++ b/src/data/gifCache.ts @@ -0,0 +1,3 @@ +import type { GifImageModel } from '../models/image/gifImage'; + +export let gifCache: GifImageModel[] = []; diff --git a/src/index.tsx b/src/index.tsx index dfd83003..59c7d74e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,10 @@ import { createRoot } from 'react-dom/client'; import App from './App'; +import { Suspense } from 'react'; const root = createRoot(document.getElementById('app')!); -root.render(); +root.render( + 로딩중입니다.}> + + +); diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index 73a9eb49..d00d5818 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -2,10 +2,10 @@ import { useRef } from 'react'; import { Link } from 'react-router-dom'; import classNames from 'classnames/bind'; -import heroImage from '../../assets/images/hero.png'; -import trendingGif from '../../assets/images/trending.gif'; -import findGif from '../../assets/images/find.gif'; -import freeGif from '../../assets/images/free.gif'; +import heroImage from '../../assets/images/hero.webp'; +import trendingGif from '../../assets/images/trending.mp4'; +import findGif from '../../assets/images/find.mp4'; +import freeGif from '../../assets/images/free.mp4'; import FeatureItem from './components/FeatureItem/FeatureItem'; import CustomCursor from './components/CustomCursor/CustomCursor'; @@ -19,7 +19,8 @@ const Home = () => { return ( <>
- hero image + hero +

Memegle

gif search engine for you

diff --git a/src/pages/Home/components/CustomCursor/CustomCursor.tsx b/src/pages/Home/components/CustomCursor/CustomCursor.tsx index 4c6ad7b3..fd86a261 100644 --- a/src/pages/Home/components/CustomCursor/CustomCursor.tsx +++ b/src/pages/Home/components/CustomCursor/CustomCursor.tsx @@ -14,8 +14,7 @@ const CustomCursor = ({ text = '' }: CustomCursorProps) => { useEffect(() => { if (cursorRef.current) { - cursorRef.current.style.top = `${mousePosition.pageY}px`; - cursorRef.current.style.left = `${mousePosition.pageX}px`; + cursorRef.current.style.transform = `translate(${mousePosition.pageX}px, ${mousePosition.pageY}px)`; } }, [mousePosition]); diff --git a/src/pages/Home/components/FeatureItem/FeatureItem.tsx b/src/pages/Home/components/FeatureItem/FeatureItem.tsx index c5e93eed..59f30986 100644 --- a/src/pages/Home/components/FeatureItem/FeatureItem.tsx +++ b/src/pages/Home/components/FeatureItem/FeatureItem.tsx @@ -8,7 +8,7 @@ type FeatureItemProps = { const FeatureItem = ({ title, imageSrc }: FeatureItemProps) => { return (
- +
diff --git a/src/pages/Search/components/GifItem/GifItem.module.css b/src/pages/Search/components/GifItem/GifItem.module.css index 1eea2746..59df7b08 100644 --- a/src/pages/Search/components/GifItem/GifItem.module.css +++ b/src/pages/Search/components/GifItem/GifItem.module.css @@ -14,7 +14,7 @@ } .gifItem:hover { - top: -0.75rem; + transform: translateY(-0.75rem); } .gifImage { diff --git a/src/pages/Search/components/GifItem/GifItem.tsx b/src/pages/Search/components/GifItem/GifItem.tsx index 5fb41408..63607166 100644 --- a/src/pages/Search/components/GifItem/GifItem.tsx +++ b/src/pages/Search/components/GifItem/GifItem.tsx @@ -1,5 +1,5 @@ +import { memo } from 'react'; import { GifImageModel } from '../../../../models/image/gifImage'; - import styles from './GifItem.module.css'; type GifItemProps = Omit; @@ -16,4 +16,4 @@ const GifItem = ({ imageUrl = '', title = '' }: GifItemProps) => { ); }; -export default GifItem; +export default memo(GifItem); diff --git a/src/pages/Search/components/HelpPanel/HelpPanel.module.css b/src/pages/Search/components/HelpPanel/HelpPanel.module.css index f44a0aaf..5adfb7f1 100644 --- a/src/pages/Search/components/HelpPanel/HelpPanel.module.css +++ b/src/pages/Search/components/HelpPanel/HelpPanel.module.css @@ -24,7 +24,7 @@ } .selectedItemContainer.showSheet { - right: 0; + transform: translateX(-320px); opacity: 1; } diff --git a/src/pages/Search/hooks/useGifSearch.tsx b/src/pages/Search/hooks/useGifSearch.tsx index 1aaa4a30..f1a2f832 100644 --- a/src/pages/Search/hooks/useGifSearch.tsx +++ b/src/pages/Search/hooks/useGifSearch.tsx @@ -2,6 +2,7 @@ import { ChangeEvent, useEffect, useState } from 'react'; import { gifAPIService } from '../../../apis/gifAPIService'; import { GifImageModel } from '../../../models/image/gifImage'; +import { gifCache } from '../../../data/gifCache'; const DEFAULT_PAGE_INDEX = 0; @@ -12,7 +13,7 @@ export const SEARCH_STATUS = { NO_RESULT: 'NO_RESULT' } as const; -export type SearchStatus = typeof SEARCH_STATUS[keyof typeof SEARCH_STATUS]; +export type SearchStatus = (typeof SEARCH_STATUS)[keyof typeof SEARCH_STATUS]; const useGifSearch = () => { const [status, setStatus] = useState(SEARCH_STATUS.BEFORE_SEARCH); @@ -57,11 +58,18 @@ const useGifSearch = () => { useEffect(() => { const fetch = async () => { if (status === SEARCH_STATUS.BEFORE_SEARCH) { - const gifs: GifImageModel[] = await gifAPIService.getTrending(); - - setGifList(gifs); + if (gifCache.length === 0) { + const gifs: GifImageModel[] = await gifAPIService.getTrending(); + + gifCache.push(...gifs); + setGifList(gifs); + } + if (gifCache.length !== 0) { + setGifList(gifCache); + } } }; + fetch(); return () => setStatus(SEARCH_STATUS.LOADING); diff --git a/src/types/images.d.ts b/src/types/images.d.ts index 848831fb..fdd78781 100644 --- a/src/types/images.d.ts +++ b/src/types/images.d.ts @@ -2,3 +2,5 @@ declare module '*.png'; declare module '*.jpg'; declare module '*.gif'; declare module '*.svg'; +declare module '*.webp'; +declare module '*.mp4'; diff --git a/webpack.config.js b/webpack.config.js index 1f5f1b41..8bbf8576 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,13 +1,16 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const Dotenv = require('dotenv-webpack'); const CopyWebpackPlugin = require('copy-webpack-plugin'); +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { entry: './src/index.tsx', resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'] }, output: { - filename: 'bundle.js', + filename: 'bundle.[chunkhash].js', path: path.join(__dirname, '/dist'), clean: true }, @@ -18,16 +21,24 @@ module.exports = { }, devtool: 'source-map', plugins: [ + new BundleAnalyzerPlugin(), new HtmlWebpackPlugin({ template: './index.html' }), new CopyWebpackPlugin({ patterns: [{ from: './public', to: './public' }] }), - new Dotenv() + new Dotenv(), + new MiniCssExtractPlugin({ + filename: '[name].css' + }) ], module: { rules: [ + { + test: /\.css$/i, + use: [MiniCssExtractPlugin.loader, 'css-loader'] + }, { test: /\.(js|jsx|ts|tsx)$/i, exclude: /node_modules/, @@ -36,11 +47,7 @@ module.exports = { } }, { - test: /\.css$/i, - use: ['style-loader', 'css-loader'] - }, - { - test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i, + test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif|mp4|webm|webp)$/i, loader: 'file-loader', options: { name: 'static/[name].[ext]' @@ -49,6 +56,7 @@ module.exports = { ] }, optimization: { - minimize: false + splitChunks: { chunks: 'all' }, + minimizer: ['...', new CssMinimizerPlugin()] } };