diff --git a/admin.html b/admin.html new file mode 100644 index 00000000..ea275476 --- /dev/null +++ b/admin.html @@ -0,0 +1,14 @@ + + + + + + + + Awesome Orange - Admin + + +
+ + + diff --git a/build.js b/build.js new file mode 100644 index 00000000..57d04ea4 --- /dev/null +++ b/build.js @@ -0,0 +1,97 @@ +import { build } from "vite"; +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import process from "node:process"; +import { readFile, writeFile, rm, mkdir } from "node:fs/promises"; +import config from "./vite.config.js"; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const mode = process.argv[2] ?? "main"; +const toAbsolute = (p) => resolve(__dirname, p); + +const buildConfig = { + main: { + clientEntry: "index.html", + sourceDir: "mainPage", + ssgEntry: "main-server.jsx" + }, + admin: { + clientEntry: "admin.html", + sourceDir: "adminPage", + ssgEntry: "main-server.jsx", + url: [ + "index", + "events", + "events/create", + "login", + "events/[id]", + "comments", + "comments/[id]" + ] + }, +} + +async function processBuild(mode) { + await Promise.all([buildClient(mode), buildSSG(mode)]); + await injectSSGToHtml(mode); +} + +async function buildClient(mode) { + await build({ + ...config, + build: { + rollupOptions: { + input: { + entry: buildConfig[mode].clientEntry + }, + output: { + dir: `dist/${mode}` + } + } + } + }); + await rm(toAbsolute(`dist/${mode}/mockServiceWorker.js`)); +} + +function buildSSG(mode) { + return build({ + ...config, + build: { + ssr: true, + rollupOptions: { + input: { + entry: `src/${buildConfig[mode].sourceDir}/${buildConfig[mode].ssgEntry}` + }, + output: { + dir: `dist-ssg/${mode}` + } + } + } + }); +} + +async function injectSSGToHtml(mode) { + console.log("--ssg result--"); + const {default: render} = await import(`./dist-ssg/${mode}/entry.js`); + const template = await readFile(`dist/${mode}/${buildConfig[mode].clientEntry}`, "utf-8"); + + const urlEntryPoint = buildConfig[mode].url ?? ["index"]; + + const promises = urlEntryPoint.map( async (path)=>{ + const absolutePath = toAbsolute(`dist/${mode}/${path}.html`); + try { + const html = template.replace("", render(path)); + + const dir = dirname(absolutePath); + await mkdir(dir, { recursive: true }); + await writeFile(absolutePath, html); + console.log(`pre-rendered : ${path}`); + } catch { + console.log(`pre-rendered failed : ${path}`); + } + } ); + await Promise.allSettled(promises); + console.log("--successfully build completed!--"); +} + +processBuild(mode); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e73423db..656e1867 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { - "name": "vite-project", - "version": "0.0.0", + "name": "awesome-orange-project", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "vite-project", - "version": "0.0.0", + "name": "awesome-orange-project", + "version": "0.5.0", "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", "react-lottie-player": "^2.1.0", + "react-router-dom": "^6.26.0", "swiper": "^11.1.9", "zustand": "^4.5.4" }, @@ -1280,6 +1281,15 @@ "node": ">=14" } }, + "node_modules/@remix-run/router": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.0.tgz", + "integrity": "sha512-zDICCLKEwbVYTS6TjYaWtHXxkdoUvD/QXvyVZjGCsWz5vyH7aFeONlPffPdW+Y/t6KT0MgXb2Mfjun9YpWN1dA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/pluginutils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", @@ -5634,6 +5644,38 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.0.tgz", + "integrity": "sha512-wVQq0/iFYd3iZ9H2l3N3k4PL8EEHcb0XlU2Na8nEwmiXgIUElEH6gaJDtUQxJ+JFzmIXaQjfdpcGWaM6IoQGxg==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.19.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.0.tgz", + "integrity": "sha512-RRGUIiDtLrkX3uYcFiCIxKFWMcWQGMojpYZfcstc63A1+sSnVgILGIm9gNUA6na3Fm1QuPGSBQH2EMbAZOnMsQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.19.0", + "react-router": "6.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index 53ef909c..7ffc8ff7 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,23 @@ { - "name": "vite-project", + "name": "awesome-orange-project", "private": true, - "version": "0.0.0", + "version": "0.5.0", "type": "module", "scripts": { "dev": "vite --host", - "build": "vite build && npm run build:server && node prerender.js && rm dist/mockServiceWorker.js", - "build:client": "vite build", - "build:server": "vite build --ssr src/main-server.jsx --outDir dist-ssg", + "build": "node build.js main", + "build-admin": "node build.js admin", "format": "prettier --write 'src/**/*.{js,jsx,ts,tsx,json,css}'", "lint": "eslint . --ext js,jsx --max-warnings 0", "lint-fix": "eslint . --ext js,jsx --fix", - "preview": "vite preview" + "preview": "vite preview --outDir dist/main", + "preview-admin": "vite preview --outDir dist/admin" }, "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", "react-lottie-player": "^2.1.0", + "react-router-dom": "^6.26.0", "swiper": "^11.1.9", "zustand": "^4.5.4" }, diff --git a/prerender.js b/prerender.js deleted file mode 100644 index c6229910..00000000 --- a/prerender.js +++ /dev/null @@ -1,26 +0,0 @@ -import render from "./dist-ssg/main-server.js"; -import { readdirSync } from "node:fs"; -import { readFile, writeFile } from "node:fs/promises"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirName = dirname(fileURLToPath(import.meta.url)); -const toAbsolute = (p) => resolve(__dirName, p); - -console.log("--ssg result--"); - -const htmlPath = readdirSync(toAbsolute("dist")).filter((filePath) => - filePath.endsWith(".html"), -); - -async function injectSSGToHtml(path) { - const absolutePath = toAbsolute("dist/" + path); - - const template = await readFile(absolutePath, "utf-8"); - const html = template.replace("", render(path)); - - await writeFile(absolutePath, html); - console.log(`pre-rendered : ${path}`); -} - -htmlPath.forEach(injectSSGToHtml); diff --git a/public/active1.png b/public/images/active1.png similarity index 100% rename from public/active1.png rename to public/images/active1.png diff --git a/public/active2.png b/public/images/active2.png similarity index 100% rename from public/active2.png rename to public/images/active2.png diff --git a/public/active3.png b/public/images/active3.png similarity index 100% rename from public/active3.png rename to public/images/active3.png diff --git a/public/active4.png b/public/images/active4.png similarity index 100% rename from public/active4.png rename to public/images/active4.png diff --git a/public/active5.png b/public/images/active5.png similarity index 100% rename from public/active5.png rename to public/images/active5.png diff --git a/public/carimg1.png b/public/images/carimg1.png similarity index 100% rename from public/carimg1.png rename to public/images/carimg1.png diff --git a/public/carimg2.png b/public/images/carimg2.png similarity index 100% rename from public/carimg2.png rename to public/images/carimg2.png diff --git a/public/carimg3.png b/public/images/carimg3.png similarity index 100% rename from public/carimg3.png rename to public/images/carimg3.png diff --git a/public/carimg4.png b/public/images/carimg4.png similarity index 100% rename from public/carimg4.png rename to public/images/carimg4.png diff --git a/public/carimg5.png b/public/images/carimg5.png similarity index 100% rename from public/carimg5.png rename to public/images/carimg5.png diff --git a/public/gift_car.png b/public/images/gift_car.png similarity index 100% rename from public/gift_car.png rename to public/images/gift_car.png diff --git a/public/gift_jeju.png b/public/images/gift_jeju.png similarity index 100% rename from public/gift_jeju.png rename to public/images/gift_jeju.png diff --git a/public/gift_portable.png b/public/images/gift_portable.png similarity index 100% rename from public/gift_portable.png rename to public/images/gift_portable.png diff --git a/public/inactive1.png b/public/images/inactive1.png similarity index 100% rename from public/inactive1.png rename to public/images/inactive1.png diff --git a/public/inactive2.png b/public/images/inactive2.png similarity index 100% rename from public/inactive2.png rename to public/images/inactive2.png diff --git a/public/inactive3.png b/public/images/inactive3.png similarity index 100% rename from public/inactive3.png rename to public/images/inactive3.png diff --git a/public/inactive4.png b/public/images/inactive4.png similarity index 100% rename from public/inactive4.png rename to public/images/inactive4.png diff --git a/public/inactive5.png b/public/images/inactive5.png similarity index 100% rename from public/inactive5.png rename to public/images/inactive5.png diff --git a/src/adminPage/App.jsx b/src/adminPage/App.jsx new file mode 100644 index 00000000..68a41729 --- /dev/null +++ b/src/adminPage/App.jsx @@ -0,0 +1,23 @@ +import { Route, Routes } from "react-router-dom"; + +function App() { + return ( + <> + + event 생성 화면} + /> + event 보는 화면} /> + 이벤트 목록 화면} /> + 기대평 화면} /> + 기대평 검색 화면} /> + 로그인 화면} /> + hello} /> + + + ); +} + +export default App; diff --git a/src/adminPage/index.css b/src/adminPage/index.css new file mode 100644 index 00000000..5d8fc6ec --- /dev/null +++ b/src/adminPage/index.css @@ -0,0 +1,34 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@font-face { + font-family: "ds-digital"; + src: url("/font/DS-DIGI.TTF") format("truetype"); + font-display: swap; +} + +@font-face { + font-family: "hdsans"; + src: url("/font/HyundaiSansTextKROTFBold.otf") format("opentype"); + font-weight: bold; + font-display: swap; +} + +@font-face { + font-family: "hdsans"; + src: url("/font/HyundaiSansTextKROTFMedium.otf") format("opentype"); + font-weight: medium; + font-display: swap; +} + +@layer base { + body { + font-family: "hdsans"; + } + body.scrollLocked { + position: fixed; + width: 100%; + overflow-y: scroll; + } +} diff --git a/src/adminPage/main-client.jsx b/src/adminPage/main-client.jsx new file mode 100644 index 00000000..b7b99f54 --- /dev/null +++ b/src/adminPage/main-client.jsx @@ -0,0 +1,37 @@ +import { StrictMode } from "react"; +import { createRoot, hydrateRoot } from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; +import App from "./App.jsx"; +import "./index.css"; + +const $root = document.getElementById("root"); + +if (import.meta.env.DEV) { + // 개발 시 + const enableMocking = async function () { + // 실서버와 연동시 //return;의 주석 지워서 테스트해주세요 + // return; + const worker = (await import("./mock.js")).default; + await worker.start({ onUnhandledRequest: "bypass" }); + }; + enableMocking().then(() => { + const root = createRoot($root); + root.render( + + + + + , + ); + }); +} else { + // 배포 시 + hydrateRoot( + $root, + + + + + , + ); +} diff --git a/src/adminPage/main-server.jsx b/src/adminPage/main-server.jsx new file mode 100644 index 00000000..55aa0715 --- /dev/null +++ b/src/adminPage/main-server.jsx @@ -0,0 +1,30 @@ +import { StrictMode } from "react"; +import { renderToString } from "react-dom/server"; +import { StaticRouter } from "react-router-dom/server"; +import App from "./App.jsx"; + +export default function render(url) { + const path = url === "index" ? "/" : `/${url}`; + const nye = renderToString( + + + + + , + ); + return nye; +} + +/** + * 우리의 메인 컴포넌트를 문자열로 렌더링하는 함수를 반환합니다. + * + * 향후 페이지가 추가된다면, + * + * import SecondPage from "./SecondPage.jsx"; + * + * export default function render(url) { + * // 여기에서 url에 따라 분기처리를 하면 됩니다. + * } + * + * 현재로서는 단일 페이지이므로 render 함수 내에 분기처리를 하지 않습니다. + */ diff --git a/src/adminPage/mock.js b/src/adminPage/mock.js new file mode 100644 index 00000000..b9188b12 --- /dev/null +++ b/src/adminPage/mock.js @@ -0,0 +1,6 @@ +import { setupWorker } from "msw/browser"; + +// mocking은 기본적으로 각 feature 폴더 내의 mock.js로 정의합니다. +// 새로운 feature의 mocking을 추가하셨으면, mock.js의 setupWorker 내부 함수에 인자를 spread 연산자를 이용해 추가해주세요. +// 예시 : export default setupWorker(...authHandler, ...questionHandler, ...articleHandler); +export default setupWorker(); diff --git a/src/common/constants.js b/src/common/constants.js index f8ec92ef..15bb2edb 100644 --- a/src/common/constants.js +++ b/src/common/constants.js @@ -1,2 +1,3 @@ export const EVENT_ID = "0"; -export const TOKEN_ID = "AWESOME_ORANGE_ACCESS_TOKEN"; \ No newline at end of file +export const SERVICE_TOKEN_ID = "AWESOME_ORANGE_ACCESS_TOKEN"; +export const ADMIN_TOKEN_ID = "AWESOME_ORANGE_ADMIN_ACCESS_TOKEN"; diff --git a/src/common/dataFetch/tokenSaver.js b/src/common/dataFetch/tokenSaver.js index 67eea568..ae1404bf 100644 --- a/src/common/dataFetch/tokenSaver.js +++ b/src/common/dataFetch/tokenSaver.js @@ -1,31 +1,32 @@ -import { TOKEN_ID } from "@common/constants.js"; - class TokenSaver { initialized = false; token = null; - init() { + tokenId = null; + init(tokenId) { if (typeof window === "undefined") return; - this.token = localStorage.getItem(TOKEN_ID) ?? null; + this.tokenId = tokenId; + this.token = localStorage.getItem(this.tokenId) ?? null; this.initialized = true; } - get() { + get(tokenId = this.tokenId) { if (this.initialized) return this.token; - this.init(); + this.init(tokenId); return this.token; } set(token) { this.token = token; - if (typeof window !== "undefined") localStorage.setItem(TOKEN_ID, token); + if (typeof window !== "undefined") + localStorage.setItem(this.tokenId, token); this.initialzed = true; } - has() { + has(tokenId = this.tokenId) { if (this.initialized) return this.token !== null; - this.init(); + this.init(tokenId); return this.token !== null; } remove() { this.token = null; - if (typeof window !== "undefined") localStorage.removeItem(TOKEN_ID); + if (typeof window !== "undefined") localStorage.removeItem(this.tokenId); this.initialzed = true; } } diff --git a/src/common/modal/modal.jsx b/src/common/modal/modal.jsx index 6cbd318b..138a8d92 100644 --- a/src/common/modal/modal.jsx +++ b/src/common/modal/modal.jsx @@ -1,6 +1,5 @@ import { createContext, useCallback, useEffect, useState, useRef } from "react"; import useModalStore, { closeModal } from "./store.js"; -import { delay } from "@common/utils.js"; export const ModalCloseContext = createContext(() => { console.log("모달이 닫힙니다."); @@ -10,12 +9,16 @@ function Modal({ layer }) { const timeoutRef = useRef(null); const child = useModalStore(layer); const [opacity, setOpacity] = useState(0); - const close = useCallback(async () => { - setOpacity(0); - if (timeoutRef.current === null) { - await delay(150); - closeModal(layer); - } + const close = useCallback(() => { + return new Promise((resolve) => { + setOpacity(0); + + if (timeoutRef.current !== null) return resolve(); + timeoutRef.current = setTimeout(() => { + closeModal(layer); + resolve(); + }, 150); + }); }, [layer]); useEffect(() => { diff --git a/src/common/utils.js b/src/common/utils.js index 7b80d1a6..23a251cb 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -28,5 +28,5 @@ export function convertSecondsToString(time) { } export function delay(ms) { - return new Promise( resolve=>setTimeout(resolve, ms) ); + return new Promise((resolve) => setTimeout(resolve, ms)); } diff --git a/src/mainPage/features/interactions/content.json b/src/mainPage/features/interactions/content.json index 5548cffc..64020585 100644 --- a/src/mainPage/features/interactions/content.json +++ b/src/mainPage/features/interactions/content.json @@ -46,7 +46,7 @@ }, "gift": [ { - "src": "gift_jeju.png", + "src": "images/gift_jeju.png", "name": "제주 여행 패키지", "num": 10, "desc": "항공권, 숙박비, 아이오닉5 렌터카\n(충전 비용 포함)", @@ -55,7 +55,7 @@ "starTextColor": "#957029" }, { - "src": "gift_car.png", + "src": "images/gift_car.png", "name": "더뉴 아이오닉5 시승권", "num": 50, "desc": "10-12월 중 날짜 선택 가능", @@ -64,7 +64,7 @@ "starTextColor": "#36B1E6" }, { - "src": "gift_portable.png", + "src": "images/gift_portable.png", "name": "포터블 인덕션", "num": 100, "desc": "블랙/화이트 컬러 랜덤 증정", diff --git a/src/mainPage/features/interactions/description/InteractionSlide.jsx b/src/mainPage/features/interactions/description/InteractionSlide.jsx index fb3d2131..a0c8f6ba 100644 --- a/src/mainPage/features/interactions/description/InteractionSlide.jsx +++ b/src/mainPage/features/interactions/description/InteractionSlide.jsx @@ -20,8 +20,8 @@ export default function InteractionSlide({ slideTo, answer, }) { - const activeImgPath = `active${index + 1}.png`; - const inactiveImgPath = `inactive${index + 1}.png`; + const activeImgPath = `images/active${index + 1}.png`; + const inactiveImgPath = `images/inactive${index + 1}.png`; const numberImgPath = `icons/rect${index + 1}.svg`; function onClickExperience() { @@ -43,7 +43,7 @@ export default function InteractionSlide({
- + - 09/09 (mon) - 09/13 (fri) + + 09/09 (mon) - 09/13 (fri) +
diff --git a/src/mainPage/features/simpleInformation/content.json b/src/mainPage/features/simpleInformation/content.json index 30157ec0..720278c5 100644 --- a/src/mainPage/features/simpleInformation/content.json +++ b/src/mainPage/features/simpleInformation/content.json @@ -1,31 +1,31 @@ { "content": [ { - "src": "carimg1.png", + "src": "images/carimg1.png", "title": "독창적인 디자인", "desc": "아이오닉 브랜드를 상징하는\n**파라메트릭 픽셀 램프 디자인**으로\n유니크한 이미지를 구현합니다.", "sub": "프로젝션 타입과 MFR 타입 중 선택 가능" }, { - "src": "carimg2.png", + "src": "images/carimg2.png", "title": "전용 전기차 플랫폼(E-GMP)", "desc": "**새로워진 전기차 플랫폼 E-GMP**는\n알루미늄 압출재를 이용해\n구조적 안정성을 높였습니다.", "sub": "연출된 이미지로 실제 작동 사양과 다를 수 있음" }, { - "src": "carimg3.png", + "src": "images/carimg3.png", "title": "인터랙티브 픽셀 라이트", "desc": "**신규 디자인의 스티어링 휠**로\n아이오닉5만의 차별화된\n주행 경험을 제공합니다.", "sub": "시동 / 충전 중 / 후진 중 표시 가능" }, { - "src": "carimg4.png", + "src": "images/carimg4.png", "title": "증강현실 내비게이션", "desc": "주행에 필요한 각종 정보들을\n**증강현실 기술**을 통해\n직관적으로 제공합니다.", "sub": "인포테인먼트 시스템 화면 이미지는 업데이트에 따라 변동 가능" }, { - "src": "carimg5.png", + "src": "images/carimg5.png", "title": "디지털 사이드 미러", "desc": "보다 슬림해진 **디지털 사이드 미러**는\n카메라와 **OLED 모니터**를 통해\n선명한 후방 시야를 제공합니다.", "sub": "모니터는 내부 운전석에 위치" diff --git a/src/mainPage/features/simpleInformation/contentSection.jsx b/src/mainPage/features/simpleInformation/contentSection.jsx index 575821e2..c197dc72 100644 --- a/src/mainPage/features/simpleInformation/contentSection.jsx +++ b/src/mainPage/features/simpleInformation/contentSection.jsx @@ -58,7 +58,10 @@ export default function ContentSection({ content }) {
-

+

{makeHighlight(content.desc, style.highlightAnim)}

diff --git a/src/mainPage/shared/auth/store.js b/src/mainPage/shared/auth/store.js index 133d016a..c57bb7b7 100644 --- a/src/mainPage/shared/auth/store.js +++ b/src/mainPage/shared/auth/store.js @@ -1,5 +1,6 @@ import { create } from "zustand"; import tokenSaver from "@common/dataFetch/tokenSaver.js"; +import { SERVICE_TOKEN_ID } from "@common/constants.js"; const userStore = create(() => ({ isLogin: false, @@ -23,8 +24,8 @@ export function logout() { } export function initLoginState() { - tokenSaver.init(); - const token = tokenSaver.get(); + tokenSaver.init(SERVICE_TOKEN_ID); + const token = tokenSaver.get(SERVICE_TOKEN_ID); const userName = parseTokenToUserName(token); if (token === null) userStore.setState(() => ({ isLogin: false, userName: "" })); diff --git a/src/mainPage/shared/realtimeEvent/getEventDateState.js b/src/mainPage/shared/realtimeEvent/getEventDateState.js index 7ad0d42c..c0b1b9ef 100644 --- a/src/mainPage/shared/realtimeEvent/getEventDateState.js +++ b/src/mainPage/shared/realtimeEvent/getEventDateState.js @@ -6,4 +6,4 @@ export default function getEventDateState(currrentTimeDate, eventTimeDate) { if (currentTime < eventTime) return "default"; if (currentTime < eventEndTime) return "active"; return "ended"; -} \ No newline at end of file +} diff --git a/src/mainPage/shared/scroll/constants.js b/src/mainPage/shared/scroll/constants.js index 8c87183b..c8b8bc20 100644 --- a/src/mainPage/shared/scroll/constants.js +++ b/src/mainPage/shared/scroll/constants.js @@ -3,4 +3,4 @@ export const OTHER_SECTION = 0; export const INTERACTION_SECTION = 1; export const DETAIL_SECTION = 2; export const COMMENT_SECTION = 3; -export const FCFS_SECTION = 4; \ No newline at end of file +export const FCFS_SECTION = 4; diff --git a/vite-devRouter.js b/vite-devRouter.js new file mode 100644 index 00000000..ab85f5f9 --- /dev/null +++ b/vite-devRouter.js @@ -0,0 +1,33 @@ +// from monument gallery +export default function devRouter(rawRouteList) { + // 문자열로 된 route list를 정규표현식으로 변환하고, 정규표현식이 아닌 것들을 제거합니다. + const routeList = rawRouteList + .map(([route, html]) => { + if (typeof route === "string") { + route = new RegExp(`^${route.replace(/\*/g, ".*").replace(/\?/g, ".")}/?$`); + } + return [route, html]; + }) + .filter(([route]) => route instanceof RegExp); + + // 요청한 패스가 설정된 라우팅 경로에 맞는지 확인합니다. + function foundRoute(path) { + for (let [route, html] of routeList) { + if (route.test(path)) return html; + } + return null; + } + + return { + name: "route-server", + configureServer(server) { + server.middlewares.use((req, res, next) => { + const htmlPath = foundRoute(req.originalUrl); + if (htmlPath === null) return next(); + req.url = htmlPath; + req.originalUrl = htmlPath; + next(); + }); + }, + }; +} \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index 81964c96..34fa7ca0 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,12 +1,23 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; -import { resolve } from "path"; +import devRouter from "./vite-devRouter.js"; +import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; import svgr from "vite-plugin-svgr"; +const __dirname = fileURLToPath(new URL('.', import.meta.url)); + // https://vitejs.dev/config/ export default defineConfig({ base: "./", - plugins: [react(), svgr()], + plugins: [ + devRouter([ + ["/admin", "/admin.html"], + ["/admin/*", "/admin.html"], + ]), + react(), + svgr() + ], resolve: { alias: [ { find: "@", replacement: resolve(__dirname, "src") },