Wordpress

WordPress enqueue scripts 怎麼寫?掛勾、依賴、版本號一次搞懂

外掛設定頁那段只想自己用的 JavaScript,你圖方便直接把 <script> 標籤寫進 header.php,結果上線當天後台工具列一片紅字,console 跳出一串 jQuery is not defined。重新整理幾次,錯誤有時候消失、有時候又冒出來,你開始懷疑是不是自己哪裡打錯字,其實問題根本不在你寫的那幾行程式碼。

真正的原因是,WordPress 早就內建一套管理 CSS/JS 的佇列機制,叫做 enqueue,用 wp_enqueue_script()wp_enqueue_style() 把資源登記進這套系統,由 WordPress 統一排序、統一輸出,而不是各自把 <script><link> 標籤硬寫進版型檔案。跳過這套機制,等於讓你的資源跟其他外掛、佈景主題各自為政。誰先載入、誰的版本被誰蓋掉,全看巧合。WordPress.org 官方外掛目錄與多數主題市集的審核規範,也明文要求走 enqueue,硬寫標籤的外掛送審一律打回。

搞懂這套機制不難,難的是它一共有五、六個掛勾時機與六、七個參數,每一個都藏著一個很容易踩的坑,像用錯掛勾、漏寫依賴、$ver 亂填,症狀都長得差不多(資源沒生效、順序亂、跟別的外掛互相衝突),但成因完全不同。

直接把 <script><link> 寫進版型檔案要付出的代價

<script src="..."><link rel="stylesheet" href="..."> 直接寫進 header.phpfooter.php,或是在 wp_headwp_footer 掛勾裡用 echo 直接吐出標籤,短期內看起來完全沒問題。網頁照樣能跑,樣式照樣套上去。但這種做法繞過了 WordPress 官方設計的資源管理系統,代價通常要等到網站裝了更多外掛之後才會爆出來。

最常見的第一個問題是同一個函式庫被重複載入。jQuery 是最典型的例子:你自己的主題寫死一份 jQuery,另一個外掛也各自帶了自己那份 jQuery,兩份版本不一定相同,後載入的會直接覆蓋先載入的變數。表面上看程式碼完全沒寫錯,卻莫名其妙跳出 undefined 的錯誤,因為你呼叫的那個方法,根本不在後來覆蓋進來的那個版本裡。這類錯誤最難查,因為出錯的那支腳本本身是對的,問題出在別人蓋掉了它依賴的東西。

第二個問題是審核關卡直接被擋下。WordPress.org 官方外掛目錄與絕大多數佈景主題市集,審核規範都明文要求資源走 enqueue 機制載入,不接受硬寫 <script><link> 標籤。這不是官方的道德勸說,是實際會擋件的硬性條款。想上架、想過審,這步繞不過去。

第三個問題最容易被忽略,卻影響最大:少了 handle、少了依賴宣告,WordPress 完全不知道這個資源存在。它排不進佇列,系統也就沒辦法幫你算出正確的載入順序,更沒辦法讓其他外掛安全地移除、延後、或改寫這個資源。日後想停用某支腳本、或想把它挪到頁尾,得回頭去改版型檔案裡那行寫死的標籤,而不是像 enqueue 過的資源一樣,一行 wp_dequeue_script() 就能處理掉。走 enqueue,等於把資源的控制權交還給一個所有外掛都認得的共同系統,而不是鎖死在你自己的版型檔案裡。

前台、後台、登入頁與區塊編輯器各自對應的載入時機掛勾

enqueue 函式本身不會憑空知道「現在該不該輸出」,它必須被掛在某個時機點上,由 WordPress 在對的時候主動呼叫。不同畫面對應不同的掛勾,搞錯掛勾是最常見的第二層錯誤。資源明明程式碼寫對了,卻完全沒有出現在頁面上,或是出現在不該出現的地方。

不同畫面各自對應一個 enqueue 掛勾,先確認資源要出現在前台、後台、登入頁或區塊編輯器,再選對應的 hook
先確認資源要出現在哪個畫面,再對照選出該掛的 hook,前台、後台、登入頁、區塊編輯器各有專屬掛勾。

前台資源交給 wp_enqueue_scripts

前台(訪客看得到的網頁)唯一正確的掛勾是 wp_enqueue_scripts。官方文件寫得很直白:「wp_enqueue_scripts is the proper hook to use when enqueuing scripts and styles that are meant to appear on the front end. Despite the name, it is used for enqueuing both scripts and styles.」名字裡雖然只有 script,實際上前台的 CSS 與 JS 都歸它管,不需要另外找一個名字裡有 style 的掛勾。

常見的誤區是圖方便,直接在 functions.php 最上層呼叫 wp_enqueue_script(),不透過 add_action() 掛勾。這麼寫在某些狀況下看起來會動,但並不可靠,因為 functions.php 最上層的程式碼,不管這次請求是前台頁面、後台頁面、AJAX 還是 REST API,都會被執行到,而這個時間點連 WordPress 的主查詢都還沒跑完,is_page()is_singular() 這類條件標籤根本拿不到正確結果,也沒有機制篩掉不該載入資源的情境,等於資源不分場合被硬塞進每一次請求裡。正確做法是把要 enqueue 的邏輯包成一個函式,再用 add_action('wp_enqueue_scripts', '你的函式名稱') 掛上去,讓 WordPress 只在真正要輸出前台頁面、且時機正確(也就是 wp_head 之前)的時候,才去呼叫你的函式。

後台頁面用 admin_enqueue_scripts 並帶 $hook 限定範圍

後台管理頁(外掛設定頁、自訂欄位頁這類只在 wp-admin 裡出現的畫面)要換掉前台那個掛勾,改用 admin_enqueue_scripts。這個掛勾除了自己的資源要用它,也是很多外掛效能問題的起點。用錯或漏加限制,就會讓資源在整個後台的每一頁都被載入。

admin_enqueue_scripts 會傳入一個 $hook 參數給你的回呼函式,這是目前後台頁面的識別字串,例如新增文章頁是 post-new.php、你自己外掛掛在選單下的自訂頁面則會是類似 toplevel_page_my-plugin 這種格式。拿這個字串做條件判斷,就能把 enqueue 限定在真正需要的那一頁:

function my_plugin_admin_assets( $hook ) {
    if ( 'toplevel_page_my-plugin' !== $hook ) {
        return;
    }

    wp_enqueue_style( 'my-plugin-admin', plugins_url( 'admin.css', __FILE__ ) );
    wp_enqueue_script( 'my-plugin-admin', plugins_url( 'admin.js', __FILE__ ), array( 'jquery' ), '1.0.0', true );
}
add_action( 'admin_enqueue_scripts', 'my_plugin_admin_assets' );Code language: PHP (php)

$hook 不符合就直接 return,後面的 enqueue 語句完全不會被執行。這個特性只屬於後台的 admin_enqueue_scripts,前台的 wp_enqueue_scripts 沒有對應的參數可以這樣判斷,想在前台限定範圍,得改靠 is_page()has_shortcode() 這類條件標籤自己判斷。

登入頁掛 login_enqueue_scripts,區塊編輯器依情境分兩種掛勾

除了前台與後台這兩個最常見的畫面,還有幾個常被忽略的場景,各自對應不同的掛勾。想改登入頁的樣式(換背景、換 logo 連結、改按鈕顏色)常見的做法是掛 login_enqueue_scripts,只在登入畫面載入,不會跑到前台或後台的每一頁。

區塊編輯器(Gutenberg)則分成兩種情境:只想在編輯畫面本身生效的資源(例如自訂區塊的編輯介面樣式、只有編輯者需要的 JS)掛 enqueue_block_editor_assets;而區塊本身呈現用的樣式,如果編輯器預覽畫面跟前台輸出都要套用同一份 CSS,就掛 enqueue_block_assets。這個掛勾在編輯器與前台都會被觸發。另外還有一個較少用到但同樣存在的場景:自訂佈景主題外觀頁(即時預覽介面)專屬的資源,對應 customize_controls_enqueue_scripts

這幾個掛勾名稱本身就是 WordPress 核心程式碼裡固定下來的命名,不需要死背,但要建立一個判斷習慣,先問「這個資源要出現在哪個畫面」,答案直接決定該掛哪一個 hook,不是每種資源都塞進同一個 wp_enqueue_scripts 裡用條件判斷去擋。畫面對了,程式碼才會在對的時間點被執行。

先註冊、後掛上,wp_register_*wp_enqueue_* 的分工

WordPress 把「告訴系統這個資源存在」跟「真的把它排進這一次頁面要輸出的佇列」拆成兩個動作,wp_register_script()wp_register_style() 負責前者,wp_enqueue_script()wp_enqueue_style() 負責後者。拆開的原因很直接。不是每個註冊過的資源,都該在每一次頁面請求裡被印出來。

wp_enqueue_script() 官方文件其實已經寫明,這個函式本身內部就會做註冊這一步:「Registers the script if $src provided (does NOT overwrite), and enqueues it.」也就是說,單獨呼叫 wp_enqueue_script() 就是「一步到位」,同時完成註冊與掛上兩件事,而且它不會覆寫已經存在的同名註冊。多數簡單的場景,直接呼叫 wp_enqueue_script() 就夠了,不需要刻意拆成兩段。

真正該拆開的情境是「先聲明、晚點才視情況掛上」。典型做法是在 wp_enqueue_scripts 掛勾裡,把所有可能用到的資源先統一用 wp_register_script()wp_register_style() 註冊起來。這一步不會把資源印在頁面上,只是讓 WordPress 記住它的 handle、路徑、依賴這些資訊。真正需要輸出的時候,例如某個 shortcode 被實際插進文章內容裡才需要對應的 JS,再另外呼叫 wp_enqueue_script('那個handle') 把它加進這一次的輸出佇列:

function my_plugin_register_assets() {
    wp_register_script( 'my-plugin-gallery', plugins_url( 'gallery.js', __FILE__ ), array( 'jquery' ), '1.2.0', true );
}
add_action( 'wp_enqueue_scripts', 'my_plugin_register_assets' );

function my_plugin_gallery_shortcode( $atts ) {
    wp_enqueue_script( 'my-plugin-gallery' );
    return '<div class="my-plugin-gallery">...</div>';
}
add_shortcode( 'my_gallery', 'my_plugin_gallery_shortcode' );Code language: PHP (php)

這樣寫的好處是,只有頁面內容真的插入了 [my_gallery] 這個 shortcode,gallery.js 才會被輸出;其他沒用到這個 shortcode 的頁面,即使外掛已經啟用,也不會白白多載入一支用不到的腳本。先註冊、後掛上,本質上就是把「這個資源存在」跟「這一頁要不要用它」分開判斷,是後面「條件式載入」那一節能夠成立的基礎。

handle 與 src 兩個參數,命名前綴與路徑寫法是撞名與掉檔的關鍵

wp_enqueue_script()wp_enqueue_style() 的第一、第二個參數,分別是 $handle$src,看起來最單純,卻是實務上最容易撞名、最容易在子主題環境掉檔的兩個地方。

wp_enqueue_script() 依序傳入 handle、src、deps、ver、in_footer 五個參數,分別決定資源的識別、來源、依賴、版本快取與載入位置
五個參數依序傳入:handle 認名、src 指路、deps 排順序、ver 控快取、in_footer 決定載入位置與策略。

handle 要唯一,前綴是避免撞名的慣例

$handle 是這個資源在整個網站範圍內唯一的識別字串,官方文件對它的定義只有一句:「Name of the script. Should be unique.」這個字串不只是拿來標記自己,wp_localize_script()wp_add_inline_script()wp_dequeue_script() 這些函式都是靠指名這個 handle 去操作對應的資源,handle 撞名,操作就會打到錯的目標。

撞名最容易發生在多個外掛都用簡單、通用的字當 handle,例如 mainstylescriptcommon。你的外掛用 main 當 handle,另一個外掛剛好也用 main,後註冊的會直接覆蓋先註冊的那份(依照上一節提到的「不覆蓋已存在的註冊」規則,實際行為是先到的贏、後到的被忽略)。業界的通用做法是替 handle 加上自己外掛或主題的專屬前綴,例如 mytheme-mainmyplugin-admin,雖然官方沒有強制規定這個命名慣例,但它能大幅降低跟其他外掛、主題撞名的機率,幾乎是所有正式發布的外掛都會做的事。

子主題的 src 要用 get_stylesheet_directory_uri()

$src 是資源的完整網址,或是相對 WordPress 根目錄的路徑,官方定義是:「Full URL of the script, or path of the script relative to the WordPress root directory.」實務上最常見的路徑錯誤,發生在子主題(child theme)環境下。

get_template_directory_uri() 回傳的永遠是「目前作用中主題的父主題」目錄,而 get_stylesheet_directory_uri() 回傳的則是「目前作用中主題本身」的目錄。如果作用中的是子主題,後者指向的就是子主題自己的資料夾。如果你在子主題的 functions.php 裡誤用 get_template_directory_uri() 去指向自己新增的 CSS 檔案,實際指到的路徑會是父主題的資料夾,而你的檔案根本不在那裡,結果就是資源完全載入不到,而且錯誤訊息通常只會是一個安靜的 404,不會有任何 PHP 錯誤提醒你哪裡寫錯。

// 子主題環境下,取得子主題自己的目錄網址
$style_uri = get_stylesheet_directory_uri() . '/assets/custom.css';
wp_enqueue_style( 'mytheme-custom', $style_uri, array(), '1.0.0' );Code language: PHP (php)

外掛則慣用另一組函式,也就是 plugins_url()plugin_dir_url( __FILE__ ),取得外掛自身目錄的網址。這兩個函式都是相對 __FILE__ 動態算出來的,不是寫死的字串路徑,就算外掛資料夾之後被改名(例如從 my-plugin 改成 my-plugin-pro),路徑依然會正確算出來,不需要回頭改任何一行程式碼。

用依賴陣列避免同一個函式庫被重複載入

第三個參數 $deps 是一個陣列,列出這個資源依賴哪些「已經註冊過」的 handle。官方定義是:「An array of registered script handles this script depends on.」WordPress 會根據這層依賴關係,自動算出正確的載入順序,同一個 handle 也不會被重複印出兩次。

最常見的情境是依賴 jQuery。WordPress 核心本身已經內建註冊好 jquery 這個 handle,你不需要自己重新註冊、也不需要另外上傳一份 jQuery 檔案,只要在 $deps 陣列裡寫上 array('jquery'),WordPress 就會確保這支腳本一定排在 jQuery 之後載入:

wp_enqueue_script( 'mytheme-gallery', get_stylesheet_directory_uri() . '/js/gallery.js', array( 'jquery' ), '1.0.0', true );Code language: PHP (php)

如果依賴的是你自己另外寫的其他腳本,而不是核心內建的 jQuery,那支被依賴的腳本必須事先被註冊過,不然依賴它的這一支會整支消失在輸出裡。WordPress 找不到對應的 handle,並不是忽略這條依賴、照樣把資源印出來,而是判定依賴沒有滿足,直接不印這支腳本,而且通常不會跳出任何警告或錯誤訊息,得自己肉眼比對頁面原始碼才查得出來哪支不見了。

依賴關係還會鏈式串接。假設 A 依賴 BB 又依賴 jqueryA 其實不需要重複宣告依賴 jquery,因為 B 已經把 jQuery 帶進來了,WordPress 會沿著依賴樹一路往前算清楚。例如先註冊一支處理相簿基礎功能的 gallery,再讓進階燈箱效果的 gallery-lightbox 依賴 gallery:

wp_enqueue_script( 'mytheme-gallery', $gallery_src, array( 'jquery' ), '1.0.0', true );
wp_enqueue_script( 'mytheme-gallery-lightbox', $lightbox_src, array( 'mytheme-gallery' ), '1.0.0', true );Code language: PHP (php)

gallery-lightbox 不必再重複寫一次 jquery,因為 gallery 已經帶進來了。不過為了避免日後改動載入順序或拆分檔案時漏掉某層依賴,不少開發者仍會習慣把完整的依賴鏈都寫清楚。這是保守的做法,不算錯,只是多寫幾個字。

版本號控制瀏覽器何時該重新抓取檔案

第四個參數 $ver 常被誤會成一個純標示用、可有可無的版本字串,實際上它的作用是被加到檔案網址的後面,變成一段查詢字串,例如 style.css?ver=1.2.0。瀏覽器與 CDN 判斷「這是不是同一份已經快取過的檔案」,靠的就是這段查詢字串。網址完全相同,才會直接使用快取,不重新下載。

三種取值方式,行為各自不同。傳一個固定字串(如 '1.2.0')是手動控制,每次改版就自己動手改這個字串,讀者的瀏覽器才會知道該重新下載;傳 false(這也是預設值)則交給 WordPress 自動處理,會自動帶入目前安裝的 WordPress 版本號當作 ver 值;傳 null 則完全不加版本號查詢字串,官方文件寫得很明確:「If version is set to false, a version number is automatically added equal to current installed WordPress version. If set to null, no version is added.」

依賴 WordPress 版本號當快取判斷,對主題或外掛自己的 CSS/JS 來說並不理想。你改了一次 CSS,但只要 WordPress 核心版本沒變,查詢字串就不會變,讀者的瀏覽器可能還在用舊的快取檔案,看不到你剛剛的改動。更進階的做法是用 filemtime() 讀取檔案本身最後被修改的時間戳,直接拿這個時間戳當版本號:

$style_path = get_stylesheet_directory() . '/assets/custom.css';
$style_uri  = get_stylesheet_directory_uri() . '/assets/custom.css';

wp_enqueue_style( 'mytheme-custom', $style_uri, array(), filemtime( $style_path ) );Code language: PHP (php)

這樣一來,檔案內容一改,時間戳自動跟著變,版本號也就跟著變,不必每次改完 CSS 或 JS 都手動去改一次版本字串,也能確保讀者不會吃到舊的快取檔案。這個寫法要注意的是 filemtime() 吃的是伺服器上的實體檔案路徑(用 get_stylesheet_directory(),不是網址),跟 $src 需要的網址(get_stylesheet_directory_uri())是兩個不同的函式,拿錯路徑會直接噴出檔案不存在的錯誤。

in_footerstrategy,第五個參數共同決定的載入位置與策略

第五個參數是整篇文章的核心,也是最多舊教學文只寫到一半的地方。很多資料只講到「第五個參數傳 true 就會被放到頁面底部」,但 WordPress 6.3(2023 年)之後,這個參數已經升級成可以傳一個陣列,除了原本的頁尾位置設定,還多了 strategy 這個鍵值,可以設成 'defer''async',讓瀏覽器原生控制腳本什麼時候該執行。

先講位置。把 in_footer 設為 true,腳本會被輸出在 </body> 標籤之前,而不是頁面最上方的 <head> 裡。理由很直接:讓瀏覽器先把 HTML 內容渲染完,再去載入、執行腳本,避免瀏覽器被一支還沒下載完的腳本擋住,拖慢整個頁面顯示出來的速度。

再講 deferasync 的差異,這兩個屬性常被搞混,但作用完全不同。官方部落格說明得很清楚:「Deferred scripts are executed in the same order they were printed/added in the DOM, unlike asynchronous scripts.」defer 標記的腳本,會等到整個 DOM 樹完整解析完之後才執行,而且會照著它們被加入頁面的順序依序跑,不會插隊。async 則相反:「Asynchronous scripts do not have a guaranteed execution order.」標記 async 的腳本只要一下載完就立刻執行,誰先下載完誰先跑,完全不保證順序。

這個差異決定了怎麼選。腳本如果需要依賴 DOM 元素存在(例如要抓取某個 <div> 去綁事件)、或跟其他腳本之間有先後順序要求,用 defer 比較安全;如果是完全獨立、不依賴 DOM 也不依賴其他腳本的功能,例如埋碼、分析工具這類「丟出去就好」的資源,用 async 讓它盡快載入執行反而更合適。實際寫法是把陣列當第五個參數傳入:

wp_enqueue_script(
    'mytheme-main',
    get_stylesheet_directory_uri() . '/js/main.js',
    array( 'jquery' ),
    filemtime( get_stylesheet_directory() . '/js/main.js' ),
    array(
        'in_footer' => true,
        'strategy'  => 'defer',
    )
);Code language: PHP (php)

在 WordPress 6.3 之前,想做到同樣效果只能靠 script_loader_tag 這個過濾器,在腳本標籤已經印出來之後,事後用字串替換的方式硬加上 deferasync 屬性。官方部落格直接形容這種舊做法「less than ideal」,問題在於它是在標籤輸出的最後一刻才動手,並不清楚這支腳本真正的依賴樹長什麼樣子,容易在有依賴關係的情境下打亂原本正確的載入順序,造成一些難以預期的相容性問題。新版的 strategy 參數則是在 WordPress 內部真正處理依賴計算的階段就把 deferasync 一起算進去,加上策略設定不會打亂資源原本的依賴順序,這是舊的過濾器做法幾乎沒辦法做到的事。

這個第五參數的陣列還在持續擴充,WordPress 6.9 加入了 fetchpriority(控制瀏覽器抓取這支資源的優先度),7.0 又加入了 module_dependencies,給 ES module 的動態 import 情境用。這兩個比較新、比較進階的鍵值先不必深究,strategy 是目前這個位置最常被實際用到的一個,先把它跟 in_footer 搭配用熟,已經能處理絕大多數場景。

條件式載入,只在真正用到的頁面才掛上資源

把所有資源不分青紅皂白全站載入,是很常見的效能浪費,也是後台常被抱怨「每一頁都很慢」的原因之一。設定頁專用的 CSS,結果每篇文章編輯頁都跟著載入;某個 shortcode 專用的 JS,結果沒用到這個 shortcode 的頁面也照樣多下載一支檔案。

做法是把 wp_enqueue_style()wp_enqueue_script() 的呼叫包進條件判斷裡,用 WordPress 內建的條件標籤,或是內容檢查函式,判斷目前這次請求是不是真的需要這個資源,是才呼叫、不是就整段跳過。常用的條件標籤包括 is_page()(判斷是不是特定頁面)、is_singular()(判斷是不是某個文章類型的單篇頁)、is_front_page()(判斷是不是首頁);內容檢查則靠 has_shortcode()has_block(),判斷目前這篇文章的內容裡有沒有出現特定的 shortcode 或區塊。

官方教學課程直接示範了這種寫法,用 is_singular('book') 包住整個 enqueue 函式,不符合的請求直接 return:

function my_plugin_book_assets() {
    if ( ! is_singular( 'book' ) ) {
        return;
    }

    wp_enqueue_style( 'my-plugin-book', plugins_url( 'book.css', __FILE__ ) );
}
add_action( 'wp_enqueue_scripts', 'my_plugin_book_assets' );Code language: PHP (php)

這個寫法也能跟前面「先註冊、後掛上」那節的技巧搭配著用。統一在 wp_enqueue_scripts 掛勾裡把可能用到的資源全部先 wp_register_script()wp_register_style() 登記好,真正的 wp_enqueue_script()wp_enqueue_style() 則移到 has_shortcode() 判斷為真的地方才呼叫。只有頁面內容真的插入了對應的 shortcode,資源才會被排進輸出佇列。兩種做法本質相同,都是把「這個資源存在」跟「這一次要不要輸出它」拆開判斷,差別只在你想用哪種條件標籤去做這個判斷。

wp_localize_script() 把 PHP 資料傳進 JavaScript

JavaScript 檔案本身是靜態檔案,沒辦法直接讀到 PHP 端的變數,例如目前登入使用者的 ID、AJAX 請求該打去哪個網址、或是用來防止跨站請求偽造的 nonce 值。wp_localize_script() 就是為了解決這個落差而存在的函式。

它的運作方式是把一個 PHP 關聯陣列轉換成一個 JavaScript 全域物件,插入在指定的那支腳本標籤之前輸出,讓這支 JS 檔案可以直接讀取這個物件的屬性,拿到 PHP 端傳過來的資料。最常見的實戰用途,是傳遞 admin_url('admin-ajax.php') 這個 AJAX 請求要打的固定網址,加上 wp_create_nonce() 產生的 nonce 值供 AJAX 呼叫驗證使用:

wp_enqueue_script( 'mytheme-ajax', get_stylesheet_directory_uri() . '/js/ajax.js', array( 'jquery' ), '1.0.0', true );

wp_localize_script( 'mytheme-ajax', 'mytheme_ajax_object', array(
    'ajax_url' => admin_url( 'admin-ajax.php' ),
    'nonce'    => wp_create_nonce( 'mytheme_ajax_nonce' ),
) );Code language: PHP (php)

ajax.js 裡就可以直接讀 mytheme_ajax_object.ajax_urlmytheme_ajax_object.nonce 這兩個屬性,不需要另外寫任何 API 去要這筆資料。

有幾個地方特別容易出錯。第一是呼叫順序,官方文件開宗明義寫著「Works only if the script has already been registered.」wp_localize_script() 必須排在 wp_register_script()wp_enqueue_script() 之後呼叫,順序顛倒的話,系統找不到對應的 handle,這次呼叫會直接失效,而且通常不會有明顯的錯誤訊息提醒你。第二是 $object_name 這個第二個參數,建議加上自己的前綴(像上面範例的 mytheme_),避免跟其他外掛、主題各自宣告的全域變數撞名。第三個坑在資料型別,透過 wp_localize_script() 傳過去的值,全部會被轉換成字串,如果 PHP 端傳的原本是數字,JavaScript 端拿到的也會是字串形式的數字,想當成 number 使用,得自己在 JS 那邊用 parseInt()Number() 轉換,不會自動變回原本的數字型別。

上線前,用瀏覽器開發者工具與 Query Monitor 確認載入順序

寫法都對了之後,最後一步是養成習慣去驗證自己寫的是不是真的照預期執行。這不是在處理「為什麼沒生效」的除錯流程,而是把正確做法確定下來的最後一道關卡。

最直接的方法是打開瀏覽器內建的開發者工具,切到 Network 或 Elements 面板,或直接看網頁原始碼,確認目標的 <script><link> 標籤有沒有出現在頁面上、ver= 後面帶的版本查詢字串是不是你剛剛設定的那個版本、deferasync 屬性有沒有正確被帶出來。這幾件事光靠肉眼掃過原始碼就能大致確認,不需要額外裝任何工具。

想看得更完整,可以裝 Query Monitor,這是 WordPress.org 官方外掛目錄收錄的免費外掛,開發階段常被拿來當除錯工具。它能直接列出目前這個頁面所有被註冊、被掛上佇列的 handle,以及它們彼此之間的依賴關係,還能抓出有沒有同一個 handle 被不同外掛或主題重複註冊。比起自己翻原始碼、一行一行對照 handle 跟依賴,Query Monitor 的清單直接把整個佇列攤開給你看,省下大量肉眼比對的時間,尤其是站上外掛數量一多、$deps 鏈變得複雜的時候特別有用。

從繞過機制直接寫死標籤,到搞清楚每個掛勾對應哪個畫面、每個參數各自的作用,再到最後用工具驗證載入順序,這一整套流程的核心,其實是把資源的控制權交還給 WordPress 自己的佇列系統,而不是鎖死在版型檔案裡的幾行標籤。往後不管是自己接手別人的專案,還是同時裝了十幾個外掛的正式站,這套機制都是唯一能讓所有人的資源和平共存的共同語言。

常見問答

本區問答由 AI 依文章內容自動整理,僅供快速參考,正式內容仍以全文為準。

前台載入 CSS/JS 該掛哪個掛勾?

enqueue scripts 前台資源要掛在 wp_enqueue_scripts 這個掛勾上,這個名稱雖然只有 script,但 CSS 與 JS 都歸它管,不需要另找一個名字裡有 style 的掛勾。

後台頁面的 enqueue 掛勾怎麼限定只在一頁生效?

admin_enqueue_scripts 掛勾會傳入 $hook 參數,也就是目前後台頁面的識別字串,用它做條件判斷、不符合就 return,就能把資源限定在真正需要的那一頁載入,避免拖慢整個後台。

先註冊再掛上跟直接 enqueue 有什麼不同?

wp_register_script 只登記資源存在、不會輸出到頁面;wp_enqueue_script 才是真正把資源排進這一次的輸出佇列,需要先聲明、晚點視情況才輸出時才拆開用,簡單場景直接呼叫 enqueue 就夠。

enqueue 的 handle 撞名會怎樣?

同名 handle 只有先註冊的那份會生效,後到的會被忽略,因為 WordPress 不會覆寫已存在的註冊;常見做法是替 handle 加上專屬前綴,例如 mytheme-main,降低撞名機率。

defer 和 async 屬性有什麼不同?

defer 標記的腳本會等 DOM 解析完才執行,且照加入頁面的順序依序跑;async 則是下載完就立刻執行,不保證順序。需要依賴 DOM 或有先後順序的腳本用 defer,獨立的用 async。

資料來源
  1. wp_enqueue_script() — WordPress
  2. wp_enqueue_scripts — WordPress
  3. Registering scripts with async and defer attributes in WordPress 6.3 — WordPress
  4. wp_localize_script() — WordPress
  5. Enqueuing CSS or JavaScript — WordPress