給 WordPress 開發者的 Astro

本頁內容

Astro 是面向內容型網站的 Web 框架。搭配 EmDash 時,Astro 會取代你的 WordPress 主題—負責範本、路由與呈現。

本指南把你已理解的 WordPress 概念對應到 Astro 基礎。

關鍵範式轉移

預設在伺服器端呈現

與 PHP 一樣,Astro 程式碼在伺服器上執行。與 PHP 不同,它預設輸出零 JavaScript 的靜態 HTML。

不加就沒有 JS

WordPress 會自動載入 jQuery 與主題腳本。除非你明確加入,否則 Astro 不會向瀏覽器送出任何腳本。

以元件為中心的架構

不再散落範本標籤與 include,而是用可組合、自給自足的元件來搭建。

以檔案為基礎的路由

不必重寫規則或 query_varssrc/pages/ 的檔案結構直接決定 URL。

專案結構

WordPress 主題往往是帶「魔法檔名」的扁平結構。Astro 使用明確的目錄:

WordPressAstro用途
index.php, single.phpsrc/pages/路由(URL)
template-parts/src/components/可重複使用的 UI 片段
header.php + footer.phpsrc/layouts/頁面外殼
style.csssrc/styles/全域 CSS
functions.phpastro.config.mjs網站設定

典型的 Astro 專案:

src/
├── components/        # Reusable UI (Header, PostCard, etc.)
├── layouts/           # Page shells (Base.astro)
├── pages/             # Routes - files become URLs
│   ├── index.astro    # → /
│   ├── posts/
│   │   ├── index.astro      # → /posts
│   │   └── [slug].astro     # → /posts/hello-world
│   └── [slug].astro   # → /about, /contact, etc.
└── styles/
    └── global.css

Astro 元件

.astro 檔相當於 PHP 範本。每個檔案分兩部分:

  1. Frontmatter--- 之間)— 伺服器端程式碼,類似範本頂端的 PHP
  2. 範本— 含表達式的 HTML,類似 PHP 範本的其餘部分
---
// Frontmatter: runs on server, never sent to browser
interface Props {
  title: string;
  excerpt: string;
  url: string;
}

const { title, excerpt, url } = Astro.props;
---
<!-- Template: outputs HTML -->
<article class="post-card">
  <h2><a href={url}>{title}</a></h2>
  <p>{excerpt}</p>
</article>

與 PHP 的主要差異:

  • Frontmatter 是隔離的。 在那裡宣告的變數可在範本中使用,但程式碼本身不會送到瀏覽器。
  • import 寫在 frontmatter。 元件、資料、工具—都在頂端 import。
  • 可以使用 TypeScript。interface Props 定義 prop 類型,取得編輯器補完與驗證。

範本表達式

Astro 範本用 {大括號} 代替 <?php ?>。語法類似 JSX,但輸出純 HTML。

Astro

---
import { getEmDashCollection } from "emdash";

const { entries: posts } = await getEmDashCollection("posts");
const showTitle = true;
---
{showTitle && <h1>Latest Posts</h1>}

{posts.length > 0 ? (
  <ul>
    {posts.map(post => (
      <li>
        <a href={`/posts/${post.id}`}>{post.data.title}</a>
      </li>
    ))}
  </ul>
) : (
  <p>No posts found.</p>
)}

PHP

<?php
$posts = new WP_Query(['post_type' => 'post']);
$show_title = true;
?>

<?php if ($show_title): ?>
  <h1>Latest Posts</h1>
<?php endif; ?>

<?php if ($posts->have_posts()): ?>
  <ul>
    <?php while ($posts->have_posts()): $posts->the_post(); ?>
      <li>
        <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
      </li>
    <?php endwhile; wp_reset_postdata(); ?>
  </ul>
<?php else: ?>
  <p>No posts found.</p>
<?php endif; ?>

表達式模式

Pattern作用
{variable}輸出值
{condition && <Element />}條件呈現
{condition ? <A /> : <B />}If/else
{items.map(item => <Li>{item}</Li>)}迴圈

Props 與插槽

元件透過 props(類似函式參數)與 slots(類似 do_action 插入點)接收資料。

Astro

---
interface Props {
  title: string;
  featured?: boolean;
}

const { title, featured = false } = Astro.props;
---
<article class:list={["card", { featured }]}>
  <h2>{title}</h2>
  <slot />
  <slot name="footer" />
</article>

用法:

<Card title="Hello" featured>
  <p>This goes in the default slot.</p>
  <footer slot="footer">Footer content</footer>
</Card>

PHP

<?php
// Usage: get_template_part('template-parts/card', null, [
//   'title' => 'Hello',
//   'featured' => true
// ]);

$title = $args['title'] ?? '';
$featured = $args['featured'] ?? false;
$class = $featured ? 'card featured' : 'card';
?>
<article class="<?php echo esc_attr($class); ?>">
  <h2><?php echo esc_html($title); ?></h2>
  <?php
  // No direct equivalent to slots.
  // WordPress uses do_action() for similar patterns:
  do_action('card_content');
  do_action('card_footer');
  ?>
</article>

Props 與 $args

在 WordPress 中,get_template_part() 透過 $args 陣列傳資料。Astro 的 props 有型別且可解構:

---
// Type-safe with defaults
interface Props {
  title: string;
  count?: number;
}
const { title, count = 10 } = Astro.props;
---

插槽與掛勾

WordPress 用 do_action() 建立插入點。Astro 用插槽:

WordPressAstro
do_action('before_content')<slot name="before" />
Default content area<slot />
do_action('after_content')<slot name="after" />

差異在於:插槽在呼叫處接收子元素,WordPress 掛勾則需要在別處另外呼叫 add_action()

版面

版面以共同的 HTML 結構—<head>、頁首、頁尾以及頁面間共用部分—包住頁面。取代 header.php + footer.php

---
import "../styles/global.css";

interface Props {
  title: string;
  description?: string;
}

const { title, description = "My EmDash Site" } = Astro.props;
---
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content={description} />
    <title>{title}</title>
  </head>
  <body>
    <header>
      <nav><!-- Navigation --></nav>
    </header>

    <main>
      <slot />
    </main>

    <footer>
      <p>&copy; {new Date().getFullYear()}</p>
    </footer>
  </body>
</html>

在頁面中使用版面:

---
import Base from "../layouts/Base.astro";
---
<Base title="Home">
  <h1>Welcome</h1>
  <p>Page content goes in the slot.</p>
</Base>

樣式

Astro 提供多種樣式做法。最特別的是 scoped styles(作用域樣式)

作用域樣式

<style> 標籤內的樣式會自動限定到該元件:

<article class="card">
  <h2>Title</h2>
</article>

<style>
  /* Only affects .card in THIS component */
  .card {
    padding: 1rem;
    border: 1px solid #ddd;
  }

  h2 {
    color: navy;
  }
</style>

產生的 HTML 會帶唯一類名,避免樣式外洩。較少為特異性拉扯。

全域樣式

網站層級樣式可建立 CSS 檔並在版面中 import:

---
import "../styles/global.css";
---

條件類別

class:list 指令取代手動拼接類名字串:

Astro

---
const { featured, size = "medium" } = Astro.props;
---
<article class:list={[
  "card",
  size,
  { featured, "has-border": true }
]}>

輸出:<article class="card medium featured has-border">

PHP

<?php
$classes = ['card', $size];
if ($featured) $classes[] = 'featured';
if (true) $classes[] = 'has-border';
?>
<article class="<?php echo esc_attr(implode(' ', $classes)); ?>">

用戶端 JavaScript

Astro 預設不送出 JavaScript。這是相對 WordPress 最大的思維轉換。

加入互動

簡單互動可加入 <script> 標籤:

<button id="menu-toggle">Menu</button>
<nav id="mobile-menu" hidden>
  <slot />
</nav>

<script>
  const toggle = document.getElementById("menu-toggle");
  const menu = document.getElementById("mobile-menu");

  toggle?.addEventListener("click", () => {
    menu?.toggleAttribute("hidden");
  });
</script>

指令碼會自動打包並去重。元件在頁面上出現兩次時,指令碼只執行一次。

進階:互動元件

更複雜的互動可按需載入 JavaScript 元件(React、Vue、Svelte)。這是可選項—多數網站只用 <script> 就夠。

---
import SearchWidget from "../components/SearchWidget.jsx";
---
<!-- Only load JavaScript when the search box scrolls into view -->
<SearchWidget client:visible />
DirectiveJavaScript 何時載入
client:load頁面載入後立即
client:visible元件進入可視區域時
client:idle瀏覽器閒置時

路由

Astro 使用 以檔案為基礎的路由src/pages/ 下的檔案即 URL:

FileURL
src/pages/index.astro/
src/pages/about.astro/about
src/pages/posts/index.astro/posts
src/pages/posts/[slug].astro/posts/hello-world
src/pages/[...slug].astroAny path (catch-all)

動態路由

對 CMS 內容,用方括號語法表示動態區段:

---
import { getEmDashCollection, getEmDashEntry } from "emdash";
import Base from "../../layouts/Base.astro";
import { PortableText } from "emdash/ui";

// For static builds, define which pages to generate
export async function getStaticPaths() {
  const { entries: posts } = await getEmDashCollection("posts");
  return posts.map(post => ({
    params: { slug: post.id },
    props: { post },
  }));
}

const { post } = Astro.props;
---
<Base title={post.data.title}>
  <article>
    <h1>{post.data.title}</h1>
    <PortableText value={post.data.content} />
  </article>
</Base>

與 WordPress 對照

WordPressAstro
Template hierarchy (single-post.php)Explicit file: posts/[slug].astro
Rewrite rules + query_varsFile structure
$wp_query determines templateURL maps directly to file
add_rewrite_rule()Create files or folders

WordPress 概念對應何處

查找 WordPress 功能在 Astro/EmDash 中對應方式的參考表:

範本

WordPressAstro/EmDash
Template hierarchyFile-based routing in src/pages/
get_template_part()Import and use components
the_content()<PortableText value={content} />
the_title(), the_*()Access via post.data.title
Template tagsTemplate expressions {value}
body_class()class:list directive

資料與查詢

WordPressAstro/EmDash
WP_QuerygetEmDashCollection(type, filters)
get_post()getEmDashEntry(type, id)
get_posts()getEmDashCollection(type)
get_the_terms()Access via entry.data.categories
get_post_meta()Access via entry.data.fieldName
get_option()getSiteSettings()
wp_nav_menu()getMenu(location)

擴充性

WordPressAstro/EmDash
add_action()EmDash hooks, Astro middleware
add_filter()EmDash hooks
add_shortcode()Portable Text custom blocks
register_block_type()Portable Text custom blocks
register_sidebar()EmDash widget areas
PluginsAstro integrations + EmDash plugins

內容類型

WordPressAstro/EmDash
register_post_type()Create collection in admin UI
register_taxonomy()Create taxonomy in admin UI
register_meta()Add field to collection schema
Post statusEntry status (draft, published, etc.)
Featured imageMedia reference field
Gutenberg blocksPortable Text blocks

摘要

從 WordPress 跳到 Astro 變化大,但邏輯清楚:

  1. PHP 範本 → Astro 元件 — 同一想法(伺服器程式碼 + HTML),結構更清楚
  2. 範本標籤 → props 與 import — 明確資料流,少依賴全域
  3. 主題檔案 → pages 目錄 — URL 與檔案結構一致
  4. 掛勾 → 插槽與中介層 — 插入點更可預測
  5. 預設 jQuery → 預設零 JS — 有意識地加入互動

Getting Started 建立第一個 EmDash 網站,或閱讀 Working with Content 學習如何查詢與呈現 CMS 資料。