WordPress 테마는 체계적으로 EmDash로 변환할 수 있습니다. 시각적 디자인, 콘텐츠 구조, 동적 기능 모두 3단계 접근 방식을 사용하여 이전됩니다.
3단계 접근 방식
-
디자인 추출
WordPress 테마에서 CSS 변수, 폰트, 색상, 레이아웃 패턴을 추출합니다. 라이브 사이트를 분석하여 계산된 스타일과 반응형 브레이크포인트를 캡처합니다.
-
템플릿 변환
PHP 템플릿을 Astro 컴포넌트로 변환합니다. WordPress 템플릿 계층을 Astro 라우트에 매핑하고 템플릿 태그를 EmDash API 호출로 변환합니다.
-
동적 기능
내비게이션 메뉴, 위젯 영역, 택소노미, 사이트 설정을 EmDash 대응 항목으로 포팅합니다. 완전한 콘텐츠 모델을 캡처하기 위한 시드 파일을 생성합니다.
1단계: 디자인 추출
CSS와 디자인 토큰 찾기
| 파일 | 용도 |
|---|---|
style.css | 테마 헤더가 있는 메인 스타일시트 |
assets/css/ | 추가 스타일시트 |
theme.json | 블록 테마 (WP 5.9+) - 구조화된 토큰 |
디자인 토큰 추출
| WordPress 패턴 | EmDash 변수 |
|---|---|
| 본문 폰트 패밀리 | --font-body |
| 제목 폰트 | --font-heading |
| 기본 색상 | --color-primary |
| 배경 | --color-base |
| 텍스트 색상 | --color-contrast |
| 콘텐츠 너비 | --content-width |
기본 레이아웃 생성
추출된 CSS 변수, 헤더/푸터 구조, 폰트 로딩, 반응형 브레이크포인트로 src/layouts/Base.astro를 생성합니다:
---
import { getSiteSettings, getMenu } from "emdash";
import "../styles/global.css";
const { title, description } = Astro.props;
const settings = await getSiteSettings();
const primaryMenu = await getMenu("primary");
const pageTitle = title ? `${title} | ${settings.title}` : settings.title;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{pageTitle}</title>
</head>
<body>
<header>
{settings.logo ? (
<img src={settings.logo.url} alt={settings.title} />
) : (
<span>{settings.title}</span>
)}
<nav>
{primaryMenu?.items.map((item) => (
<a href={item.url}>{item.label}</a>
))}
</nav>
</header>
<main><slot /></main>
</body>
</html>
2단계: 템플릿 변환
템플릿 계층 매핑
| WordPress 템플릿 | Astro 라우트 |
|---|---|
index.php | src/pages/index.astro |
single.php | src/pages/posts/[slug].astro |
single-{post_type}.php | src/pages/{type}/[slug].astro |
page.php | src/pages/[...slug].astro |
archive.php | src/pages/posts/index.astro |
category.php | src/pages/categories/[slug].astro |
tag.php | src/pages/tags/[slug].astro |
search.php | src/pages/search.astro |
404.php | src/pages/404.astro |
header.php / footer.php | src/layouts/Base.astro의 일부 |
sidebar.php | src/components/Sidebar.astro |
템플릿 태그 매핑
| WordPress 함수 | EmDash 대응 |
|---|---|
have_posts() / the_post() | getEmDashCollection() |
get_post() | getEmDashEntry() |
the_title() | post.data.title |
the_content() | <PortableText value={post.data.content} /> |
the_excerpt() | post.data.excerpt |
the_permalink() | /posts/${post.slug} |
the_post_thumbnail() | post.data.featured_image |
get_the_date() | post.data.publishedAt |
get_the_category() | getEntryTerms(coll, id, "categories") |
get_the_tags() | getEntryTerms(coll, id, "tags") |
루프 변환
WordPress
<?php while (have_posts()) : the_post(); ?>
<article>
<h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
<?php the_excerpt(); ?>
</article>
<?php endwhile; ?> EmDash
---
import { getEmDashCollection } from "emdash";
import Base from "../../layouts/Base.astro";
const { entries: posts } = await getEmDashCollection("posts", {
where: { status: "published" },
orderBy: { publishedAt: "desc" },
});
---
<Base title="Blog">
{posts.map((post) => (
<article>
<h2><a href={`/posts/${post.slug}`}>{post.data.title}</a></h2>
<p>{post.data.excerpt}</p>
</article>
))}
</Base> 단일 템플릿 변환
WordPress
<?php get_header(); ?>
<article>
<h1><?php the_title(); ?></h1>
<?php the_content(); ?>
<div class="post-meta">
Posted in: <?php the_category(', '); ?>
</div>
</article>
<?php get_footer(); ?> EmDash
---
import { getEmDashCollection, getEntryTerms } from "emdash";
import { PortableText } from "emdash/ui";
import Base from "../../layouts/Base.astro";
export async function getStaticPaths() {
const { entries: posts } = await getEmDashCollection("posts");
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const categories = await getEntryTerms("posts", post.id, "categories");
---
<Base title={post.data.title}>
<article>
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
<div class="post-meta">
Posted in: {categories.map((cat) => (
<a href={`/categories/${cat.slug}`}>{cat.label}</a>
))}
</div>
</article>
</Base> 템플릿 파트 변환
WordPress의 get_template_part() 호출은 Astro 컴포넌트 임포트가 됩니다. template-parts/content-post.php 파셜은 루프에서 임포트하고 렌더링하는 PostCard.astro 컴포넌트가 됩니다.
3단계: 동적 기능
내비게이션 메뉴
functions.php에서 메뉴를 식별하고 해당하는 EmDash 메뉴를 생성합니다:
---
import { getMenu } from "emdash";
const menu = await getMenu("primary");
---
{menu && (
<nav class="primary-nav">
<ul>
{menu.items.map((item) => (
<li>
<a href={item.url} aria-current={Astro.url.pathname === item.url ? "page" : undefined}>
{item.label}
</a>
{item.children.length > 0 && (
<ul class="submenu">
{item.children.map((child) => (
<li><a href={child.url}>{child.label}</a></li>
))}
</ul>
)}
</li>
))}
</ul>
</nav>
)}
위젯 영역 (사이드바)
테마에서 위젯 영역을 식별하고 렌더링합니다:
---
import { getWidgetArea, getMenu } from "emdash";
import { PortableText } from "emdash/ui";
import RecentPosts from "./widgets/RecentPosts.astro";
const sidebar = await getWidgetArea("sidebar");
const widgetComponents = { "core:recent-posts": RecentPosts };
---
{sidebar && sidebar.widgets.length > 0 && (
<aside class="sidebar">
{sidebar.widgets.map(async (widget) => (
<div class="widget">
{widget.title && <h3>{widget.title}</h3>}
{widget.type === "content" && <PortableText value={widget.content} />}
{widget.type === "menu" && (
<nav>
{await getMenu(widget.menuName).then((m) =>
m?.items.map((item) => <a href={item.url}>{item.label}</a>)
)}
</nav>
)}
{widget.type === "component" && widgetComponents[widget.componentId] && (
<Fragment>
{(() => {
const Component = widgetComponents[widget.componentId];
return <Component {...widget.componentProps} />;
})()}
</Fragment>
)}
</div>
))}
</aside>
)}
위젯 타입 매핑
| WordPress 위젯 | EmDash 위젯 타입 |
|---|---|
| Text/Custom HTML | type: "content" |
| Custom Menu | type: "menu" |
| Recent Posts | component: "core:recent-posts" |
| Categories | component: "core:categories" |
| Tag Cloud | component: "core:tag-cloud" |
| Search | component: "core:search" |
택소노미
테마에 등록된 택소노미를 쿼리합니다:
---
import { getTaxonomyTerms, getEntriesByTerm } from "emdash";
import Base from "../../layouts/Base.astro";
export async function getStaticPaths() {
const genres = await getTaxonomyTerms("genre");
return genres.map((genre) => ({
params: { slug: genre.slug },
props: { genre },
}));
}
const { genre } = Astro.props;
const books = await getEntriesByTerm("books", "genre", genre.slug);
---
<Base title={genre.label}>
<h1>{genre.label}</h1>
{books.map((book) => (
<article>
<h2><a href={`/books/${book.slug}`}>{book.data.title}</a></h2>
</article>
))}
</Base>
사이트 설정 매핑
| WordPress Customizer | EmDash 설정 |
|---|---|
| Site Title | title |
| Tagline | tagline |
| Site Icon | favicon |
| Custom Logo | logo |
| Posts per page | postsPerPage |
숏코드에서 Portable Text로
WordPress 숏코드는 Portable Text 사용자 정의 블록이 됩니다:
WordPress
add_shortcode('gallery', function($atts) {
$ids = explode(',', $atts['ids']);
return '<div class="gallery">...</div>';
}); EmDash
---
const { images } = Astro.props;
---
<div class="gallery">
{images.map((img) => (
<img src={img.url} alt={img.alt || ""} loading="lazy" />
))}
</div>PortableText에 등록합니다:
<PortableText value={content} components={{ gallery: Gallery }} /> 시드 파일 구조
시드 파일에 완전한 콘텐츠 모델을 캡처합니다. 설정, 택소노미, 메뉴, 위젯 영역을 포함합니다:
{
"$schema": "https://emdashcms.com/seed.schema.json",
"version": "1",
"meta": { "name": "Ported Theme" },
"settings": { "title": "My Site", "tagline": "Welcome", "postsPerPage": 10 },
"taxonomies": [
{
"name": "category",
"label": "Categories",
"hierarchical": true,
"collections": ["posts"]
}
],
"menus": [
{
"name": "primary",
"label": "Primary Navigation",
"items": [
{ "type": "custom", "label": "Home", "url": "/" },
{ "type": "custom", "label": "Blog", "url": "/posts" }
]
}
],
"widgetAreas": [
{
"name": "sidebar",
"label": "Main Sidebar",
"widgets": [
{
"type": "component",
"componentId": "core:recent-posts",
"props": { "count": 5 }
}
]
}
]
}
전체 명세는 시드 파일 형식을 참조하세요.
포팅 체크리스트
1단계 (디자인): CSS 변수 추출, 폰트 로딩, 색상 구성 일치, 반응형 브레이크포인트 작동.
2단계 (템플릿): 홈페이지, 단일 포스트, 아카이브, 404 페이지가 모두 올바르게 렌더링됨.
3단계 (동적): 사이트 설정 구성, 메뉴 작동, 택소노미 쿼리 가능, 위젯 영역 렌더링, 시드 파일 완성.
엣지 케이스
자식 테마
테마에 부모가 있는 경우(style.css에서 Template: 확인), 부모 테마를 먼저 분석한 다음 자식 테마 오버라이드를 적용합니다.
블록 테마 (FSE)
WordPress 5.9+ 블록 테마는 디자인 토큰에 theme.json을, 블록 마크업에 templates/*.html을 사용합니다. 블록 마크업을 Astro 컴포넌트로 변환하고 theme.json에서 토큰을 추출합니다.
페이지 빌더
Elementor, Divi 또는 유사한 빌더로 구축된 콘텐츠는 테마 파일이 아닌 포스트 메타에 저장됩니다. 이 콘텐츠는 테마 포팅이 아닌 WXR을 통해 가져옵니다. 테마 포팅은 셸에 집중하세요—페이지 빌더 콘텐츠는 가져오기 후 Portable Text를 통해 렌더링됩니다.
다음 단계
- 테마 만들기 — 배포 가능한 EmDash 테마 구축
- 시드 파일 형식 — 완전한 시드 파일 명세
- WordPress에서 마이그레이션 — WordPress 콘텐츠 가져오기