這篇是針對使用 WordPress.org 作為 Quick Deploy Solution,快速製作 POC (Proof of Concept) 時前台該如何開發的介紹。由於牽涉到各團隊的組成,有的團隊有 RD,有的團隊沒有,因此這套 Solution 必須要能滿足不同使用情境。
術語 Terminology | 概念 Concept |
---|---|
Theme | 佈景主題,也就是網站的外觀 |
Plugin | 外掛,一些網站額外的功能組件 |
Hooks | WP 核心的 functions ,用來串接資料庫的資料 |
CMS | Content Management System,內容管理系統 |
Monolithic CMS | 前後端耦合,theme 提供外觀、plugin 提供功能,透過 WP 的核心串在一起,直連 DB |
Headless CMS | 前後端分離,前台是另一個獨立的網站(可以是 static site 或 server),WP 提供管理後台和 Rest API,透過 API 溝通資料。 |
Route | 對外開放的端點 URI |
Endpoints | 處理 response 的 Functions |
Controllers | 決定這個 Route 該使用哪個 Endpoint 處理 |
Schema | 傳入 Endpoints 的結構化資料 |
為了之後方便 Deploy ,建議採用 Docker 架設 WP Server。
- 參考 官方指南 安裝 Docker CE Stable
- Docker Store 下載 WordPress Container
- 建立專案資料夾,新建 compose.yml
$ mkdir wp-server && cd wp-server
$ touch compose.yml
- 撰寫 compose.yml
version: '3.1'
services:
wordpress:
image: wordpress
restart: always
ports:
- 5000:80 # 前面是本機開的 port,後面是 container 開的 port
environment:
WORDPRESS_DB_PASSWORD: password # MySQL DB 的密碼
volumes: # 把 WordPress 核心程式碼與本機的某個資料夾(wp)同步
- ~/Projects/wp-server/wp:/var/www/html
mysql:
image: mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: password # MySQL DB 的密碼
# 注意:這是最簡易的架設,並無考慮資安議題。
# 如需要架設 Production,請參照 Docker Store 的說明,安全地設定 DB 連線。
- 啟動 / 停止 Server
# 啟動
$ docker-compose up
# 停止,請記得不要 docker kill container,否則所有資料會消失
$ docker-compose down
# | 情境 | 做法 | 參考資源 |
---|---|---|---|
1 | 使用現成 theme,但沒有 RD 可以改 code | 安裝現成的 theme builder 或 page builder 外掛,直接在後台製作網站外觀 | - 7 Page Builder Plugins - Page Builder Framework |
2 | 修改現成 theme(WP 原生方法) 開發全新的 theme(WP 原生方法) |
參考官方手冊,直接改 code 吧 | - Theme Handbook - Plugin Handbook |
3 | 開發全新的 theme(開發模板工具) | 使用 theme 相關的開發模板工具。需參考部分 WP 官方手冊做進一步客製化。 | - Sage - Timber |
4 | 獨立的前台 | 前端自由發揮,串 API 就好 | - REST API Handbook - 官方 plugin - 第三方套件 |
#1 ~ #3 使用 Hooks 串接資料 #4 使用 API 串接資料
注意!這些版面製作工具的自訂彈性皆有限,若要照設計給的 mockup 製作頁面,必須對 HTML、CSS 有一定的瞭解,花在熟悉工具、測試自訂彈性的時間會滿多的。故適合靜態頁面、版面設計需求不高的情境。若需要互動性、版面較複雜的情境,建議尋求前端資源協助。
| Plugin | 收費 | 說明 |
|--|--|--|--|--|
| Elementor | 免費版、付費版 | 三個工具裡面操作最直覺、最容易上手。友善的中文介面,拖拉編輯版面,支援 RWD、依裝置設定不同外觀(桌機、平板、手機)。不需要很了解 CSS 即可編輯。 |
| Visual Composer | 免費版、付費版 | 英文介面,需自己中文化,拖拉編輯版面,使用相對直覺,支援圖層、local / global css。
Widget 選項彈性較少,必須懂 css 才能客製化版面。 |
| SiteOrigin | 免費 | 包含三個工具:
- Page Builder:建立頁面外觀,要搭配 widget,
- Widgets Bundle:小工具,圖片輪播、header...,有限的自訂選項
- CSS:用編輯器 or Live Editor 修改 CSS
使用上一定要懂 css 才能製作版面,彈性相當有限 |
還有其他幾款 Page Builder 請參考這篇文章介紹。 7 Great Page Builder Plugins For WordPress: Design Made Easy
有時候 Page Builder 的自訂性很有限,可以搭配 Page Builder Framework 這個 theme 去擴充彈性。
按照 WordPress.org 官方手冊去建構原生的 WP Theme。
理想中 Theme 負責網站外觀、Plugin 負責功能,Theme 串接資料需要透過 WordPress 內建的 functions ,按一定的生命週期取得資料,也就是 hooks
。除了內建 hooks ,也能自定義新的 hooks ,一般建議後端在 plugin 中定義可用的 hooks,前端在 theme 的 functions.php
中自訂 template_tag
接收 hooks 的回傳結果,以便於在任何頁面中顯示結果。
- theme:使用約定的 hooks,
do_action
、apply_filters
- plugin:註冊約定的 hooks,
add_aciton
、add_filter
do_action
不會 return 任何東西,而apply_filters
會 return 資料,使用上要注意
theme/01-basic-theme-demo/functions.php
<?php
/* Custom Template Tag */
// 按回吐資料狀況,執行 UI render logic
function get_movie_title(){
/* Action 版本 */
// do_action('get_movie_title');
/* Filtet 版本 */
$des = apply_filters('get_movie_title', []);
echo is_null($des) ? 'No description loaded' : "電影名稱:$des";
}
theme/01-basic-theme-demo/index.php
<main class="shadow vertical-center round">
<h1>Event Site Demo Theme</h1>
<p class="description round text-ellipsis">
<?php
/* hooks */
do_action('get_movie_title');
apply_filters('get_movie_title', []);
/* custom template_tag */
get_movie_title();
?>
</p>
<button class="btn round">Login</button>
</main>
<?php
/*
Plugin Name: WP Hooks Demo Plugin
Plugin URI: https://developer.wordpress.org/plugins/the-basics/
Description: Basic WordPress Plugin for Using Custom Hooks
Version: 20160911
Author: WP QDS
Author URI: https://developer.wordpress.org/
License: GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: wporg
Domain Path: /languages
*/
// 負責打 API 並回傳部分資料
function fetch_movie_title() {
$res = wp_remote_get('https://cloud.culture.tw/frontsite/trans/SearchShowAction.do?method=doFindTypeJ&category=8');
$body = json_decode(wp_remote_retrieve_body( $res ));
$des = $body[0]->title;
// Action return 無法被 do_action 接收到,只能夠 echo
// echo $des;
// Filter return 可以被 apply_filters 接收到
return $des;
}
/* Action 版本 */
// add_action('get_movie_title', 'fetch_movie_title');
/* Filter 版本 */
add_filter('get_movie_title', 'fetch_movie_title');
這個方法是情境 #2 的延伸,需要先理解 Theme、Plugin、Hooks。
由於原生 WP Theme 的資料、頁面 render 邏輯全部混雜在 php 中,對前端開發相當受限。因此,透過 PHP 模板引擎將 Theme 的資料、頁面分離,方便前端使用現代化的 web 開發,又兼顧與 WordPress 的相容性。
不需要再透過前端切版、後端拆版套用的模式協作開發,開發完的模板即可直接給後端使用。
目前有三套可行方案:
- Sage(使用 Laravel Blade)
- Timber(使用 Twig)
- 使用 Handlebars & WP原生開發
至於要不要讓前端負責 Theme 的資料串接(會碰到 php),這部分可以按團隊的狀況做彈性調整。
目前最推薦這個方案,也是星星數最多的工具。建議直上 v9 版,工具已經整合了 Sass、Webpack、Bootstrap 4...等,可以立即開始開發。Blade 寫法比較特別,可以參考官方文件說明。
語法與 EJS 很相似,除了引用其他模板的 include
以外,多了模板繼承 extends
的概念(類似圖層),可以使用 block
蓋掉指定區塊的內容。
此開發工具僅有 twig 模板、php 頁面,並不包含 Sass、Babel、Webpack 等工具,需要自己額外安裝設置。一般建議載完 repo 後,裝上必要的 Sass、babel 等工具,直接在 local 起一個 WP Server �開發,以節省 mock 資料的踩雷過程。
- 官方安裝設定指南
- 開發模板工具
- 整合了 104 前端開發工具 & Timber Starter Theme,方便使用 ES6、SCSS 開發 twig ,
npm run build
產生能直接部署到 WP Server 的 Theme。� - 這個工具僅能用於 view 開發,背後需要自己 mock 掉� Timber、Twig 相關的內建 function、filter。
- 資料串接仍需部署到 WP Server ,使用 php 操作。
- 整合了 104 前端開發工具 & Timber Starter Theme,方便使用 ES6、SCSS 開發 twig ,
# | 前端 | 協作約定 | 後端 |
---|---|---|---|
1 | 維護 Theme,製作 Twig 模板 | 模板所需變數 schema | 維護 Theme 資料串接、Plugin |
2 | 維護 Theme,製作 Twig 模板、資料串接 (in php) | 約定好 hooks | 維護 Plugin |
注意! 開發工具使用 twig-loader 處理模板,背後的 twig.js 目前尚未完全實現PHP 端的所有 feature(支援度),此外,部分寫法是 Timber 額外擴充,在使用上需要特別小心!必要的時候得自己 mock。
目前測到有問題的部分
名稱 | 語法範例 | 說明 |
---|---|---|
resize filter | `{{post.thumbnail.src | resize(1200, 300)}}` |
function() | {{function('wp_head')}} |
Timber 擴充的功能 |
concat operator | {% include ['./tease-' ~ post.post_type ~ '.twig'] %} |
twig.js 照理說有實現,但這個會出錯,目前原因不明 |
Handlebars 是另一個 PHP 的模板引擎,目前 WordPress 社群沒有相關的開發模板,因此需要自己安裝、設定,可以搭配上方 Timber 所使用的 104 前端開發工具進行開發。
- 後端可以使用 Laravel 和 WordPress 做搭配:corcel
plugins/02-custom-api-demo.php
主要透過 WP REST API 與前台溝通資料,官方有內建的接口,亦可以擴充自訂的接口。因此前端可以自由決定要使用哪個框架。
需特別注意的是,這種情境無法搭配 SEO plugin 使用,兩大常見的 plugin 如 Yoast SEO
、 All in One SEO Pack
開放的 API 接口很少、甚至沒有,因此 SEO 的部分必須自己處理(server-side render)。
- WordPress 4.7 以後內建,4.7 以前需加裝官方 Plugin WordPress REST API (Version 2)。
- 支援 jsonp、envelop(把原始的 response 包起來,status code 永遠回傳 200)、X-HTTP-Method-Override(相容性考量,用於未支援特殊請求方法的情境:DELETE、HEAD...)
- 支援 link(回傳與這個 resource 相關的所有 URI)、embed(將相關的 URI 資料一併包在同一個 response)
- 採用 cookie 驗證 + nonce URI。
- 若需要其他驗證機制,需安裝對應的 Plugin
- JWT Authentication for WP REST API
- Application Passwords
- Basic Auth,每次 request 傳輸 password,請勿使用在正式產品
- Client Libraries
- 內建 Backbone.js Client,但 Backbone.js 現已過時
- node-wpapi
- Ember Wordpress
- 支援自訂 route 、 namespace
- 需要啟用 pretty permalinks,也就是
固定網址
來建立 RESTful Endpoints,否則需改用WP_Query
的方式存取資源
- 管理後台 => 設定 => 固定網址,調整成一般以外的網址選項。
- WordPress 預設已啟用 mod_write,如果沒有請參考文件,按 server 不同 (nginx、apache...) 做設定。
# No Pretty Permalinks,固定網址選「一般」
http://localhost:5000/?rest_route=/
# Pretty Permalinks,固定網址選「一般」以外的選項
http://localhost:5000/wp-json/
其他作法請參考文件。
- 撰寫 Endpoint (Function),可以使用的 return value
// 取得 WordPress 內部的文章
function get_all_posts( $data ) {
$posts = get_posts();
return empty( $posts ) ? null : $posts;
}
- 決定 Namespace 並註冊新的 Route,這裡可以 validate 傳入參數
args
。
register_rest_route()
一定要放在rest_api_init
的 callback 中,避免在 API 未啟動前就執行- 通常以
vendor/version
來命名 Namespace,例如:myplugin/v1
- Route 的 regex 有點不同,格式為
/(?P<[param_name]>[param_value_regex])
,例如:/(?P<id>\d+)
等同於 express 的/:id
<?php
add_action( 'rest_api_init', function () {
$namespace = 'myplugin/v1';
register_rest_route( $namespace, '/postlist', array(
'methods' => 'GET',
'callback' => 'get_all_posts',
) );
} );
- 試打 Route
# No Pretty Permalinks
http://localhost:5000/?rest_route=/myplugin/v1/postlist
# Pretty Permalinks
http://localhost:5000/wp-json/myplugin/v1/postlist
- 自訂限制存取權限的 Route
限定某些 Route 要驗證權限才能存取,可以使用 Permissions Callback ,亦可搭配其他驗證的 plugin 如 JWT Authentication for WP REST API。
// 取得機敏資料
function get_private_data() {
return rest_ensure_response( 'This is private data.' );
}
// 驗證身份
function get_private_data_permissions_check( WP_REST_Request $request ) {
// 簡單的檢查 token 是否相同
$token = $request['token'];
if ( $token === 'thisistesttoken' ) {
return true;
}
// 驗證使用者是否有編輯文章權限
/*
if ( current_user_can( 'edit_posts' ) ) {
return true;
}
*/
return new WP_Error( 'rest_forbidden', esc_html__( 'You cannot view private data.', 'my-text-domain' ), array( 'status' => 401 ) );
}
// 註冊新的 Route
add_action( 'rest_api_init', function () {
$namespace = 'myplugin/v1';
register_rest_route( $namespace, '/private-data', array(
'methods' => 'GET',
'permission_callback' => 'get_private_data_permissions_check', // 陣列順序沒有影響,這個會比下方的 main callback 早執行
'callback' => 'get_private_data',
'args' => array(
'token' => array(
'required' => true // 必填,否則回 400
)
)
) );
} );
- Route 比較多與複雜時,建立 controller 便於擴充維護,請參考這篇。
更多詳細的用法,請參考文件。
名稱 | 說明 |
---|---|
Yoast SEO | 經典的 SEO Plugin 之一 |
All in One SEO Pack | 經典的 SEO Plugin 之一 |
WooCommerce | 經典的電子商務 Plugin,快速建置電商平台 |
名稱 | 說明 |
---|---|
PHP Settings | 修改 php.ini 設定 |
Query Monitor | 檢視 wp 的 query 開發工具 |