From efe5fd62e76435e14648a6c1e180f4fb4f4cff9e Mon Sep 17 00:00:00 2001 From: chysis Date: Tue, 15 Oct 2024 01:33:01 +0900 Subject: [PATCH 01/10] =?UTF-8?q?fix:=20=EB=BC=88=EB=8C=80=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nodemon.json | 2 +- package-lock.json | 127 +++++----------------------- package.json | 9 +- public/styles/main.css | 6 +- src/client/components/Footer.jsx | 3 +- src/client/components/Header.jsx | 6 +- src/client/components/MovieItem.jsx | 3 +- src/server/main.js | 27 +++--- src/server/routes/index.js | 14 +-- views/index.html | 13 +-- webpack.client.config.js | 22 +++-- webpack.server.config.js | 18 +++- 12 files changed, 92 insertions(+), 158 deletions(-) diff --git a/nodemon.json b/nodemon.json index 7031f70..f161fa3 100644 --- a/nodemon.json +++ b/nodemon.json @@ -2,5 +2,5 @@ "watch": ["src/server", "src/client"], "ext": "js jsx json", "ignore": ["dist", "node_modules"], - "exec": "npm run build:client && npm run build:server && node dist/server/server.js" + "exec": "npm run build:client && npm run build:server && node dist/server.js" } diff --git a/package-lock.json b/package-lock.json index c6ccac4..7574eaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,11 +20,12 @@ "copy-webpack-plugin": "^12.0.2", "css-loader": "^7.1.2", "dotenv": "^16.4.5", - "file-loader": "^6.2.0", "html-webpack-plugin": "^5.6.0", + "ignore-loader": "^0.1.2", "nodemon": "^3.1.7", "style-loader": "^4.0.0", - "webpack-cli": "^5.1.4" + "webpack-cli": "^5.1.4", + "webpack-node-externals": "^3.0.0" } }, "node_modules/@ampproject/remapping": { @@ -2468,15 +2469,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3065,15 +3057,6 @@ "integrity": "sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw==", "dev": true }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -3319,7 +3302,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/fast-uri": { "version": "3.0.1", @@ -3345,75 +3329,6 @@ "reusify": "^1.0.4" } }, - "node_modules/file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "dev": true, - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/file-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/file-loader/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/file-loader/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/file-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3885,6 +3800,12 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, + "node_modules/ignore-loader": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ignore-loader/-/ignore-loader-0.1.2.tgz", + "integrity": "sha512-yOJQEKrNwoYqrWLS4DcnzM7SEQhRKis5mB+LdKKh4cPmGYlLPR0ozRzHV5jmEk2IxptqJNQA5Cc0gw8Fj12bXA==", + "dev": true + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -4086,6 +4007,7 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "peer": true, "bin": { "json5": "lib/cli.js" }, @@ -4112,20 +4034,6 @@ "node": ">=6.11.5" } }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -4696,6 +4604,7 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -5575,6 +5484,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -5736,6 +5646,15 @@ "node": ">=10.0.0" } }, + "node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", diff --git a/package.json b/package.json index c56162b..8f2813f 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "description": "", "main": "index.js", "scripts": { - "build:client": "webpack --config webpack.client.config.js", + "build:client": "rm -rf dist && webpack --config webpack.client.config.js", "build:server": "webpack --config webpack.server.config.js", - "start": "concurrently \"npm run build:client -- --watch\" \"npm run dev:server\"", + "start": "npm run build:client && npm run build:server && node dist/server.js", "dev": "nodemon", "test": "echo \"Error: no test specified\" && exit 1" }, @@ -25,10 +25,11 @@ "copy-webpack-plugin": "^12.0.2", "css-loader": "^7.1.2", "dotenv": "^16.4.5", - "file-loader": "^6.2.0", "html-webpack-plugin": "^5.6.0", + "ignore-loader": "^0.1.2", "nodemon": "^3.1.7", "style-loader": "^4.0.0", - "webpack-cli": "^5.1.4" + "webpack-cli": "^5.1.4", + "webpack-node-externals": "^3.0.0" } } diff --git a/public/styles/main.css b/public/styles/main.css index bb2e9a0..00a5a83 100644 --- a/public/styles/main.css +++ b/public/styles/main.css @@ -1,4 +1,4 @@ -@import "./colors.css"; +@import './colors.css'; * { box-sizing: border-box; @@ -43,12 +43,12 @@ button.primary { border-radius: 4px; } -#wrap { +#root { min-width: 1440px; background-color: var(--color-bluegray-100); } -#wrap h2 { +#root h2 { font-size: 1.4rem; font-weight: bold; margin-bottom: 32px; diff --git a/src/client/components/Footer.jsx b/src/client/components/Footer.jsx index f76da16..72a0130 100644 --- a/src/client/components/Footer.jsx +++ b/src/client/components/Footer.jsx @@ -1,11 +1,12 @@ import React from 'react'; +import LogoImage from '@images/logo.png'; const Footer = () => { return ( ); diff --git a/src/client/components/Header.jsx b/src/client/components/Header.jsx index 595d18f..4b64a81 100644 --- a/src/client/components/Header.jsx +++ b/src/client/components/Header.jsx @@ -1,4 +1,6 @@ import React from 'react'; +import starEmptyImage from '@images/star_empty.png'; +import LogoImage from '@images/logo.png'; import { TMDB_BANNER_URL } from '../../server/constants/movies'; const Header = ({ bestMovie }) => { @@ -12,11 +14,11 @@ const Header = ({ bestMovie }) => {

- MovieList + MovieList

- + {bestMovieRate}
{bestMovieTitle}
diff --git a/src/client/components/MovieItem.jsx b/src/client/components/MovieItem.jsx index 6389cf9..74368ef 100644 --- a/src/client/components/MovieItem.jsx +++ b/src/client/components/MovieItem.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import starEmptyImage from '@images/star_empty.png'; import { TMDB_THUMBNAIL_URL } from '../../server/constants/movies.js'; const MovieItem = ({ movie }) => { @@ -11,7 +12,7 @@ const MovieItem = ({ movie }) => { {movie.title

- 평점 + 평점 {movieRating}

{movie.title} diff --git a/src/server/main.js b/src/server/main.js index 77e1d8d..31043fd 100644 --- a/src/server/main.js +++ b/src/server/main.js @@ -1,21 +1,28 @@ -import "./config.js"; -import express from "express"; -import path from "path"; -import { fileURLToPath } from "url"; +import './config.js'; +import express from 'express'; +import path from 'path'; -import movieRouter from "./routes/index.js"; +import movieRouter from './routes/index.js'; const app = express(); const PORT = 3000; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); +// 정적 파일 제공 +app.use('/static', express.static(path.join(__dirname))); +// 존재하지 않는 정적 파일에 대한 404 처리 +app.use('/static', (req, res) => { + res.status(404).send('Resource not found'); +}); -app.use("/assets", express.static(path.join(__dirname, "../../public"))); +// 메인 페이지 라우트 (React 앱 렌더링) +app.get('/', movieRouter); -app.use("/", movieRouter); +// 그 외 모든 경로에 대한 404 처리 +app.use((req, res) => { + res.status(404).send('Page not found'); +}); -// Start server +// 서버 시작 app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); }); diff --git a/src/server/routes/index.js b/src/server/routes/index.js index 5b57d97..4bc8f49 100644 --- a/src/server/routes/index.js +++ b/src/server/routes/index.js @@ -1,22 +1,18 @@ import { Router } from 'express'; import fs from 'fs'; import path from 'path'; -import { fileURLToPath } from 'url'; import React from 'react'; import { renderToString } from 'react-dom/server'; import App from '../../client/App'; import fetchNowPlayingMovies from '../apis/movies.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - const router = Router(); -router.get('/', async (_, res) => { +router.use('/', async (_, res) => { const nowPlayingMovies = await fetchNowPlayingMovies(); - const templatePath = path.join(__dirname, '../../../views', 'index.html'); + const templatePath = path.resolve(__dirname, 'index.html'); const renderedApp = renderToString(); const template = fs.readFileSync(templatePath, 'utf-8'); @@ -29,11 +25,7 @@ router.get('/', async (_, res) => { `; - const renderedHTML = template - .replace('', initData) - .replace('', renderedApp); - - res.send(renderedHTML); + res.send(template.replace('
', `
${renderedApp}
${initData}`)); }); export default router; diff --git a/views/index.html b/views/index.html index 4716c0d..2ae2039 100644 --- a/views/index.html +++ b/views/index.html @@ -3,19 +3,10 @@ - - - - - - 영화 리뷰 + -
- -
- +
- diff --git a/webpack.client.config.js b/webpack.client.config.js index ec9f0b1..bcdf010 100644 --- a/webpack.client.config.js +++ b/webpack.client.config.js @@ -3,13 +3,13 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); module.exports = { - module: 'development', + mode: 'development', entry: './src/client/main.js', output: { - path: path.resolve('dist/client'), + path: path.resolve('dist'), filename: 'bundle.js', clean: true, - publicPath: '/', + publicPath: '/static/', }, module: { rules: [ @@ -28,22 +28,32 @@ module.exports = { use: ['style-loader', 'css-loader'], }, { - test: /\.(png)$/, - use: ['file-loader'], + test: /\.(png|jpe?g|gif|svg)$/i, + type: 'asset/resource', + generator: { + filename: 'images/[name][ext]', + }, }, ], }, plugins: [ new HtmlWebpackPlugin({ template: './views/index.html', + filename: 'index.html', + inject: 'body', }), new CopyPlugin({ patterns: [ - { from: 'public/images', to: 'images' }, // public 폴더의 이미지를 dist로 복사 + { from: 'public/images', to: 'images' }, + { from: 'public/styles', to: 'styles' }, ], }), ], resolve: { + alias: { + '@images': path.resolve(__dirname, 'public/images'), + '@styles': path.resolve(__dirname, 'public/styles'), + }, extensions: ['.js', '.jsx'], }, }; diff --git a/webpack.server.config.js b/webpack.server.config.js index f48927b..8fe934d 100644 --- a/webpack.server.config.js +++ b/webpack.server.config.js @@ -1,11 +1,17 @@ const path = require('path'); +const nodeExternals = require('webpack-node-externals'); module.exports = { mode: 'development', target: 'node', entry: path.resolve(__dirname, 'src/server/main.js'), + externals: [nodeExternals()], resolve: { extensions: ['.js', '.jsx'], + alias: { + '@images': path.resolve(__dirname, 'public/images'), + '@styles': path.resolve(__dirname, 'public/styles'), + }, }, module: { rules: [ @@ -21,16 +27,20 @@ module.exports = { }, { test: /\.css$/, - use: ['style-loader', 'css-loader'], + use: ['ignore-loader'], // CSS 파일을 무시합니다. }, { - test: /\.(png)$/, - use: ['file-loader'], + test: /\.(png|jpg|jpeg|gif|svg)$/i, + type: 'asset/resource', + generator: { + filename: 'images/[name][ext]', + }, }, ], }, output: { filename: 'server.js', - path: path.resolve(__dirname, 'dist/server'), + path: path.resolve(__dirname, 'dist'), + publicPath: '/static/', }, }; From 528acf9b0c9fb33e09fa56d66302b033c43cd732 Mon Sep 17 00:00:00 2001 From: chysis Date: Thu, 17 Oct 2024 22:01:27 +0900 Subject: [PATCH 02/10] =?UTF-8?q?fix:=20process=20is=20not=20defined=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webpack.client.config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webpack.client.config.js b/webpack.client.config.js index bcdf010..b3cbe30 100644 --- a/webpack.client.config.js +++ b/webpack.client.config.js @@ -1,6 +1,7 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); module.exports = { mode: 'development', @@ -48,6 +49,9 @@ module.exports = { { from: 'public/styles', to: 'styles' }, ], }), + new webpack.DefinePlugin({ + 'process.env': JSON.stringify(process.env), + }), ], resolve: { alias: { From 2f28c163005aa64c08965e576939280c63201327 Mon Sep 17 00:00:00 2001 From: chysis Date: Fri, 18 Oct 2024 00:08:36 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=20=EB=9D=BC=EC=9A=B0=ED=84=B0=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 41 ++++++++++++++++++++++++++++++++++++++++- package.json | 3 ++- src/client/App.jsx | 29 ++++++++++++++++++++--------- src/client/main.js | 2 +- 4 files changed, 63 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7574eaf..a9176b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "express": "^4.21.0", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-router-dom": "^6.27.0" }, "devDependencies": { "@babel/preset-env": "^7.25.4", @@ -2020,6 +2021,14 @@ "node": ">= 8" } }, + "node_modules/@remix-run/router": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", + "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@sindresorhus/merge-streams": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", @@ -4697,6 +4706,36 @@ "react": "^18.3.1" } }, + "node_modules/react-router": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", + "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", + "dependencies": { + "@remix-run/router": "1.20.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", + "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", + "dependencies": { + "@remix-run/router": "1.20.0", + "react-router": "6.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", diff --git a/package.json b/package.json index 8f2813f..3f34aa0 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "dependencies": { "express": "^4.21.0", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-router-dom": "^6.27.0" }, "devDependencies": { "@babel/preset-env": "^7.25.4", diff --git a/src/client/App.jsx b/src/client/App.jsx index a77a64e..eef9be3 100644 --- a/src/client/App.jsx +++ b/src/client/App.jsx @@ -1,15 +1,26 @@ import React from 'react'; -import Header from './components/Header'; -import MovieList from './components/MovieList'; -import Footer from './components/Footer'; +import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import MoviePage from './pages/MoviePage.jsx'; +import MovieDetail from './pages/MovieDetail.jsx'; + +function App({ initialData }) { + const router = createBrowserRouter([ + { + path: '/', + element: , + children: [ + { + path: '/detail/:id', + element: , + }, + ], + }, + ]); -function App({ movies }) { return ( - <> -
- -
- + + + ); } diff --git a/src/client/main.js b/src/client/main.js index d2940e5..878cbcf 100644 --- a/src/client/main.js +++ b/src/client/main.js @@ -4,4 +4,4 @@ import App from './App'; const initialData = window.__INITIAL_DATA__; -hydrateRoot(document.getElementById('root'), ); +hydrateRoot(document.getElementById('root'), ); From 716102d1a7754970b20f739d558c61aedbcd0900 Mon Sep 17 00:00:00 2001 From: chysis Date: Fri, 18 Oct 2024 00:16:25 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=EC=98=81=ED=99=94=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A0=95=EB=B3=B4=20=EC=9A=94=EC=B2=AD=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=9E=91=EC=84=B1=20=EB=B0=8F=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=EB=A1=9C=20=EC=9D=B4=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/apis/movies.js | 29 +++++++++++++++++++++++++++++ src/server/apis/movies.js | 18 ------------------ 2 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 src/client/apis/movies.js delete mode 100644 src/server/apis/movies.js diff --git a/src/client/apis/movies.js b/src/client/apis/movies.js new file mode 100644 index 0000000..46eaaab --- /dev/null +++ b/src/client/apis/movies.js @@ -0,0 +1,29 @@ +import { BASE_URL, FETCH_OPTIONS, TMDB_MOVIE_LISTS } from '../../server/constants/movies.js'; + +// 영화 리스트 +export const fetchMovies = async (url) => { + try { + const response = await fetch(url, FETCH_OPTIONS); + const data = await response.json(); + + return data.results; + } catch (error) { + console.error('Failed to fetch movies!'); + } +}; + +export const fetchNowPlayingMovies = async () => { + return await fetchMovies(TMDB_MOVIE_LISTS.nowPlaying); +}; + +// 영화 상세 정보 +export const fetchMovieDetail = async (movieId) => { + try { + const response = await fetch(BASE_URL + movieId, FETCH_OPTIONS); + const data = await response.json(); + + return data; + } catch (error) { + console.error('Failed to fetch movie detail'); + } +}; diff --git a/src/server/apis/movies.js b/src/server/apis/movies.js deleted file mode 100644 index c4bc4d9..0000000 --- a/src/server/apis/movies.js +++ /dev/null @@ -1,18 +0,0 @@ -import { FETCH_OPTIONS, TMDB_MOVIE_LISTS } from '../constants/movies.js'; - -const fetchMovies = async (url) => { - try { - const response = await fetch(url, FETCH_OPTIONS); - const data = await response.json(); - - return data.results; - } catch (error) { - console.error('Failed to fetch movies!'); - } -}; - -const fetchNowPlayingMovies = async () => { - return await fetchMovies(TMDB_MOVIE_LISTS.nowPlaying); -}; - -export default fetchNowPlayingMovies; From a31d844beada575bff14baa21b623b46a8c4ffa3 Mon Sep 17 00:00:00 2001 From: chysis Date: Fri, 18 Oct 2024 00:51:47 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=EC=98=81=ED=99=94=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=99=80=20=EC=83=81=EC=84=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EB=A5=BC=20=EB=B6=84=EB=A6=AC=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/pages/MovieDetail.jsx | 41 ++++++++++++++++++++++++++++++++ src/client/pages/MoviePage.jsx | 16 +++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/client/pages/MovieDetail.jsx create mode 100644 src/client/pages/MoviePage.jsx diff --git a/src/client/pages/MovieDetail.jsx b/src/client/pages/MovieDetail.jsx new file mode 100644 index 0000000..6f10420 --- /dev/null +++ b/src/client/pages/MovieDetail.jsx @@ -0,0 +1,41 @@ +import React from 'react'; +import CloseButtonImage from '@images/modal_button_close.png'; +import StarEmptyImage from '@images/star_empty.png'; +import { useNavigate } from 'react-router-dom'; + +const MovieDetail = ({ detail }) => { + const navigate = useNavigate(); + + const bannerUrl = TMDB_ORIGINAL_URL + detail.poster_path; + const releaseYear = detail.release_date.split('-')[0]; + const genres = detail.genres.map((genre) => genre.name).join(', '); + + return ( +
navigate('/')}> +
+ +
+
+ +
+
+

${detail.title}

+

+ ${releaseYear} · ${genres} +

+

+ + ${detail.vote_average} +

+
+

${detail.overview}

+
+
+
+
+ ); +}; + +export default MovieDetail; diff --git a/src/client/pages/MoviePage.jsx b/src/client/pages/MoviePage.jsx new file mode 100644 index 0000000..e8c52e3 --- /dev/null +++ b/src/client/pages/MoviePage.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import Header from '../components/Header.jsx'; +import MovieList from '../components/MovieList.jsx'; +import Footer from '../components/Footer.jsx'; + +const MoviePage = ({ movies }) => { + return ( + <> +
+ +
+ + ); +}; + +export default MoviePage; From a43d91b63abda413821f087b7acac1f493a48588 Mon Sep 17 00:00:00 2001 From: chysis Date: Fri, 18 Oct 2024 00:52:35 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=ED=81=B4=EB=A6=AD=20navigate=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/components/Header.jsx | 11 ++++++++--- src/client/components/MovieItem.jsx | 5 ++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/client/components/Header.jsx b/src/client/components/Header.jsx index 4b64a81..738575f 100644 --- a/src/client/components/Header.jsx +++ b/src/client/components/Header.jsx @@ -1,9 +1,12 @@ import React from 'react'; -import starEmptyImage from '@images/star_empty.png'; +import StarEmptyImage from '@images/star_empty.png'; import LogoImage from '@images/logo.png'; import { TMDB_BANNER_URL } from '../../server/constants/movies'; +import { useNavigate } from 'react-router-dom'; const Header = ({ bestMovie }) => { + const navigate = useNavigate(); + const backgroundContainer = TMDB_BANNER_URL + '/' + bestMovie.backdrop_path; const bestMovieRate = bestMovie.vote_average.toFixed(1); const bestMovieTitle = bestMovie.title; @@ -18,11 +21,13 @@ const Header = ({ bestMovie }) => {
- + {bestMovieRate}
{bestMovieTitle}
- +
diff --git a/src/client/components/MovieItem.jsx b/src/client/components/MovieItem.jsx index 74368ef..4badc9f 100644 --- a/src/client/components/MovieItem.jsx +++ b/src/client/components/MovieItem.jsx @@ -1,14 +1,17 @@ import React from 'react'; import starEmptyImage from '@images/star_empty.png'; import { TMDB_THUMBNAIL_URL } from '../../server/constants/movies.js'; +import { useNavigate } from 'react-router-dom'; const MovieItem = ({ movie }) => { + const navigate = useNavigate(); + const thumbnailFullUrl = TMDB_THUMBNAIL_URL + '/' + movie.poster_path; const movieRating = movie.vote_average.toFixed(1); return (
  • -
    +
    navigate(`/detail/${movie.id}`)}> {movie.title

    From 8fd097c9f75db8525c1d0ca338f8a979cd68c3a7 Mon Sep 17 00:00:00 2001 From: chysis Date: Sun, 20 Oct 2024 00:08:07 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20=EC=84=9C=EB=B2=84=20=EB=9D=BC?= =?UTF-8?q?=EC=9A=B0=ED=84=B0=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/main.js | 2 +- src/server/routes/index.js | 49 +++++++++++++++++++++++++++++++++----- views/index.html | 2 +- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/server/main.js b/src/server/main.js index 31043fd..1d45a02 100644 --- a/src/server/main.js +++ b/src/server/main.js @@ -15,7 +15,7 @@ app.use('/static', (req, res) => { }); // 메인 페이지 라우트 (React 앱 렌더링) -app.get('/', movieRouter); +app.use('/', movieRouter); // 그 외 모든 경로에 대한 404 처리 app.use((req, res) => { diff --git a/src/server/routes/index.js b/src/server/routes/index.js index 4bc8f49..12b14e9 100644 --- a/src/server/routes/index.js +++ b/src/server/routes/index.js @@ -3,20 +3,28 @@ import fs from 'fs'; import path from 'path'; import React from 'react'; -import { renderToString } from 'react-dom/server'; -import App from '../../client/App'; -import fetchNowPlayingMovies from '../apis/movies.js'; +import * as ReactDOMServer from 'react-dom/server'; +import { StaticRouter } from 'react-router-dom/server'; +import { fetchMovieDetail, fetchNowPlayingMovies } from './../../client/apis/movies'; +import MovieDetail from '../../client/pages/MovieDetail.jsx'; +import MoviePage from '../../client/pages/MoviePage.jsx'; const router = Router(); -router.use('/', async (_, res) => { +router.get('/', async (req, res) => { const nowPlayingMovies = await fetchNowPlayingMovies(); const templatePath = path.resolve(__dirname, 'index.html'); - const renderedApp = renderToString(); - const template = fs.readFileSync(templatePath, 'utf-8'); + const initialData = { movies: nowPlayingMovies }; + + const renderedApp = ReactDOMServer.renderToString( + + + + ); + const initData = /*html*/ ` + `; + + res.send(template.replace('

    ', `
    ${renderedApp}
    ${initData}`)); +}); + export default router; diff --git a/views/index.html b/views/index.html index 2ae2039..a4bf92d 100644 --- a/views/index.html +++ b/views/index.html @@ -4,7 +4,7 @@ 영화 리뷰 - +
    From 916ef827b9a0747e417a1c551b7492b763c11149 Mon Sep 17 00:00:00 2001 From: chysis Date: Sun, 20 Oct 2024 00:08:58 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20url=20=EC=A7=81=EC=A0=91=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=20=EB=B0=8F=20=EB=AA=A8=EB=8B=AC=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/App.jsx | 39 ++++++++++++++-------------- src/client/pages/MovieDetail.jsx | 44 +++++++++++++++++++++++--------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/client/App.jsx b/src/client/App.jsx index eef9be3..4e18c93 100644 --- a/src/client/App.jsx +++ b/src/client/App.jsx @@ -1,27 +1,28 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import MoviePage from './pages/MoviePage.jsx'; import MovieDetail from './pages/MovieDetail.jsx'; -function App({ initialData }) { - const router = createBrowserRouter([ - { - path: '/', - element: , - children: [ - { - path: '/detail/:id', - element: , - }, - ], - }, - ]); +const initialData = window.__INITIAL_DATA__; - return ( - - - - ); +const router = createBrowserRouter([ + { + path: '/', + element: , + }, + { + path: '/detail/:id', + element: ( + <> + + + + ), + }, +]); + +function App() { + return ; } export default App; diff --git a/src/client/pages/MovieDetail.jsx b/src/client/pages/MovieDetail.jsx index 6f10420..bec600b 100644 --- a/src/client/pages/MovieDetail.jsx +++ b/src/client/pages/MovieDetail.jsx @@ -1,36 +1,56 @@ -import React from 'react'; +import React, { useEffect, useLayoutEffect, useState } from 'react'; import CloseButtonImage from '@images/modal_button_close.png'; import StarEmptyImage from '@images/star_empty.png'; import { useNavigate } from 'react-router-dom'; +import { TMDB_ORIGINAL_URL } from '../../server/constants/movies.js'; +import { fetchMovieDetail } from '../apis/movies.js'; const MovieDetail = ({ detail }) => { + const [detailData, setDetailData] = useState(detail || {}); const navigate = useNavigate(); - const bannerUrl = TMDB_ORIGINAL_URL + detail.poster_path; - const releaseYear = detail.release_date.split('-')[0]; - const genres = detail.genres.map((genre) => genre.name).join(', '); + const movieId = typeof window !== 'undefined' && window.location.pathname.split('/detail/')[1]; + + const fetchDetail = async () => { + const movieDetail = await fetchMovieDetail(movieId); + setDetailData(movieDetail); + }; + + useLayoutEffect(() => { + if (!detail || movieId !== detail.id) { + fetchDetail(); + } + }, []); + + const bannerUrl = TMDB_ORIGINAL_URL + detailData.poster_path || ''; + const releaseYear = detailData.release_date?.split('-')[0] || ''; + const genres = detailData.genres?.map((genre) => genre.name).join(', ') || ''; + + const handleModalClose = () => { + navigate('/'); + }; return ( -
    navigate('/')}> -
    - -
    +
    -

    ${detail.title}

    +

    {detailData.title}

    - ${releaseYear} · ${genres} + {releaseYear} · {genres}

    - ${detail.vote_average} + {detailData.vote_average}


    -

    ${detail.overview}

    +

    {detailData.overview}

    From 0304e532b74cd1194bd3e0d9f98597276f3cfc48 Mon Sep 17 00:00:00 2001 From: chysis Date: Sun, 20 Oct 2024 00:09:11 +0900 Subject: [PATCH 09/10] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/apis/movies.js | 2 +- webpack.client.config.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/apis/movies.js b/src/client/apis/movies.js index 46eaaab..08116a0 100644 --- a/src/client/apis/movies.js +++ b/src/client/apis/movies.js @@ -19,7 +19,7 @@ export const fetchNowPlayingMovies = async () => { // 영화 상세 정보 export const fetchMovieDetail = async (movieId) => { try { - const response = await fetch(BASE_URL + movieId, FETCH_OPTIONS); + const response = await fetch(`${BASE_URL}/${movieId}`, FETCH_OPTIONS); const data = await response.json(); return data; diff --git a/webpack.client.config.js b/webpack.client.config.js index b3cbe30..34a119b 100644 --- a/webpack.client.config.js +++ b/webpack.client.config.js @@ -2,6 +2,9 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); const webpack = require('webpack'); +const dotenv = require('dotenv'); + +dotenv.config(); module.exports = { mode: 'development', From 45d47543db9f63ea5baccc99613ad1766fb5b67d Mon Sep 17 00:00:00 2001 From: chysis Date: Sun, 20 Oct 2024 20:52:20 +0900 Subject: [PATCH 10/10] =?UTF-8?q?chore:=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=EC=97=90=EC=84=9C=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=A4=91=EC=9D=BC=20=EB=95=8C=20=EB=A1=9C=EB=94=A9=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/pages/MovieDetail.jsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/client/pages/MovieDetail.jsx b/src/client/pages/MovieDetail.jsx index bec600b..21ea527 100644 --- a/src/client/pages/MovieDetail.jsx +++ b/src/client/pages/MovieDetail.jsx @@ -6,22 +6,25 @@ import { TMDB_ORIGINAL_URL } from '../../server/constants/movies.js'; import { fetchMovieDetail } from '../apis/movies.js'; const MovieDetail = ({ detail }) => { - const [detailData, setDetailData] = useState(detail || {}); + const [detailData, setDetailData] = useState(detail); const navigate = useNavigate(); - const movieId = typeof window !== 'undefined' && window.location.pathname.split('/detail/')[1]; - const fetchDetail = async () => { + const movieId = window.location.pathname.split('/detail/')[1]; const movieDetail = await fetchMovieDetail(movieId); setDetailData(movieDetail); }; - useLayoutEffect(() => { - if (!detail || movieId !== detail.id) { + useEffect(() => { + if (!detailData) { fetchDetail(); } }, []); + if (!detailData) { + return
    Loading ...
    ; + } + const bannerUrl = TMDB_ORIGINAL_URL + detailData.poster_path || ''; const releaseYear = detailData.release_date?.split('-')[0] || ''; const genres = detailData.genres?.map((genre) => genre.name).join(', ') || '';