diff --git a/packages/vanilla/deploy.js b/packages/vanilla/deploy.js new file mode 100644 index 00000000..289d2f15 --- /dev/null +++ b/packages/vanilla/deploy.js @@ -0,0 +1,95 @@ +import fs from "fs"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +/** + * 파일 시스템 기반 배포 스크립트 + * 빌드된 정적 파일들을 배포 디렉토리로 복사 + */ +async function deploy() { + console.log("🚀 파일 시스템 기반 배포 시작..."); + + const sourceDir = join(__dirname, "../../dist/vanilla"); + const deployDir = process.env.DEPLOY_DIR || join(__dirname, "../../dist/deploy/vanilla"); + + // 소스 디렉토리 확인 + if (!fs.existsSync(sourceDir)) { + console.error(`❌ 소스 디렉토리를 찾을 수 없습니다: ${sourceDir}`); + console.error(" 먼저 'pnpm run build:ssg'를 실행해주세요."); + process.exit(1); + } + + // 배포 디렉토리 생성 + if (fs.existsSync(deployDir)) { + console.log(`🗑️ 기존 배포 디렉토리 삭제: ${deployDir}`); + fs.rmSync(deployDir, { recursive: true, force: true }); + } + fs.mkdirSync(deployDir, { recursive: true }); + + console.log(`📦 소스: ${sourceDir}`); + console.log(`📤 배포: ${deployDir}`); + + // 디렉토리 복사 함수 + function copyDirectory(src, dest) { + const entries = fs.readdirSync(src, { withFileTypes: true }); + + for (const entry of entries) { + const srcPath = join(src, entry.name); + const destPath = join(dest, entry.name); + + if (entry.isDirectory()) { + fs.mkdirSync(destPath, { recursive: true }); + copyDirectory(srcPath, destPath); + } else { + fs.copyFileSync(srcPath, destPath); + } + } + } + + // 파일 복사 + console.log("📋 파일 복사 중..."); + copyDirectory(sourceDir, deployDir); + + // 배포 정보 파일 생성 + const deployInfo = { + timestamp: new Date().toISOString(), + source: sourceDir, + destination: deployDir, + files: countFiles(deployDir), + }; + + fs.writeFileSync(join(deployDir, ".deploy-info.json"), JSON.stringify(deployInfo, null, 2)); + + console.log(`✅ 배포 완료!`); + console.log(` 배포 디렉토리: ${deployDir}`); + console.log(` 생성된 파일 수: ${deployInfo.files}`); + console.log(` 배포 정보: .deploy-info.json`); +} + +/** + * 디렉토리 내 파일 개수 계산 + */ +function countFiles(dir) { + let count = 0; + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = join(dir, entry.name); + if (entry.isDirectory()) { + count += countFiles(fullPath); + } else { + count++; + } + } + + return count; +} + +// 실행 +deploy().catch((error) => { + console.error("배포 중 오류 발생:", error); + process.exit(1); +}); diff --git a/packages/vanilla/package.json b/packages/vanilla/package.json index ab5ae3fd..1da4426a 100644 --- a/packages/vanilla/package.json +++ b/packages/vanilla/package.json @@ -5,11 +5,11 @@ "type": "module", "scripts": { "dev": "vite --port 5173", - "dev:ssr": "PORT=5174 node server.js", + "dev:ssr": "PORT=5173 node server.js", "build:client": "rm -rf ./dist/vanilla && vite build --outDir ./dist/vanilla && cp ./dist/vanilla/index.html ./dist/vanilla/404.html", "build:client-for-ssg": "rm -rf ../../dist/vanilla && vite build --outDir ../../dist/vanilla", "build:server": "vite build --outDir ./dist/vanilla-ssr --ssr src/main-server.js", - "build:ssg": "pnpm run build:client-for-ssg && node static-site-generate.js", + "build:ssg": "pnpm run build:client-for-ssg && pnpm run build:server && node static-site-generate.js", "build:without-ssg": "pnpm run build:client && pnpm run build:server", "build": "pnpm run build:client && pnpm run build:server && pnpm run build:ssg", "lint:fix": "eslint --fix ./src", @@ -20,6 +20,8 @@ "preview:ssr-with-build": "pnpm run build:without-ssg && pnpm run preview:ssr", "preview:ssg": "vite preview --outDir ../../dist/vanilla --port 4178", "preview:ssg-with-build": "pnpm run build && pnpm run preview:ssg", + "deploy": "node deploy.js", + "deploy:build": "pnpm run build:ssg && pnpm run deploy", "serve:test:dev": "concurrently -n \"DevCSR,DevSSR,ProdCSR,ProdSSR,SSG\" -c \"#FF6B6B,#006D77,#FFD166,#6A5ACD,#00C2A8\" \"pnpm run dev\" \"pnpm run dev:ssr\" \"pnpm run preview:csr\" \"pnpm run preview:ssr\" \"pnpm run preview:ssg\"", "serve:test": "pnpm run build:without-ssg && pnpm run build:ssg && pnpm run serve:test:dev", "prepare": "husky" diff --git a/packages/vanilla/server.js b/packages/vanilla/server.js index 67f03afa..21e484a8 100644 --- a/packages/vanilla/server.js +++ b/packages/vanilla/server.js @@ -1,4 +1,13 @@ import express from "express"; +import compression from "compression"; +import sirv from "sirv"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; +import { readdirSync, readFileSync } from "fs"; +import { render } from "./dist/vanilla-ssr/main-server.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); const prod = process.env.NODE_ENV === "production"; const port = process.env.PORT || 5173; @@ -6,29 +15,206 @@ const base = process.env.BASE || (prod ? "/front_7th_chapter4-1/vanilla/" : "/") const app = express(); -const render = () => { - return `
태그 앞에 스크립트 삽입 + result = result.replace("", `${initialDataScript}\n `); + } -app.get("*all", (req, res) => { - res.send( - ` + return result; +} + +/** + * SSR 렌더링 미들웨어 + */ +async function ssrMiddleware(req, res, next) { + try { + const url = req.originalUrl.replace(base, "/") || "/"; + const query = req.query; + + // SSR 렌더링 (html과 initialData 반환) + const { html, initialData } = await render(url, query); + + // HTML 템플릿 생성 및 응답 (initialData 포함) + const template = createHtmlTemplate(html, "", base, prod, assetFiles, initialData); + res.setHeader("Content-Type", "text/html"); + res.send(template); + } catch (error) { + if (prod) { + console.error("SSR Error:", error.message); + } else { + console.error("SSR Error:", error); + } + next(error); + } +} + +/** + * 에러 핸들링 미들웨어 + */ +// eslint-disable-next-line no-unused-vars +function errorMiddleware(err, req, res, next) { + if (prod) { + console.error("Server Error:", err.message); + } else { + console.error("Server Error:", err); + } + + const errorMessage = prod ? "서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요." : err.message; + const errorDetails = prod + ? "" + : `
${err.stack}`; + + res.status(500).send(` - -
- - -
- -
+ +
+ + +
+ + +