WordPress 上的 JavaScript 簡直是地獄……這就是為什麼
已發表: 2022-05-05近年來,WordPress 開發堆棧發生了很大變化。 自從 Gutenberg 出現以來,JavaScript 在我們最喜歡的 CMS 中的作用比以往任何時候都更加重要。 在這篇博客中,我們已經詳細討論了這對開發人員帶來的好處(舉幾個例子,我們已經討論了 Gutenberg 的擴展、關於 TypeScript 和 React 的建議、開發工具、插件示例等等),但是每個故事有其陰暗面……這就是我們今天要在這裡討論的內容。
在今天的帖子中,我將與您分享 WordPress 插件開發人員未來可能面臨的 3 個主要問題。 有趣的是,他們每個人都有不同的罪魁禍首:要么是 WordPress 本身,要么是其他開發人員,要么是你自己。 所以我們開始吧:您可能會遇到的最常見的 JavaScript 令人頭疼的問題,以及您可以採取哪些措施來避免/修復它們。
#1 WPO 插件會破壞你的插件和你的網站
讓我們從在 Nelio 獲得大量票證的問題開始:WPO 插件。
我相信您已經閱讀了大量文章和博客文章,其中概述了擁有快速加載的輕量級網站的重要性。 我的意思是,我們也曾多次寫過它! 在他們通常提供的提示中,您會發現諸如切換到更好的託管服務提供商、使用緩存插件和 CDN、使您的服務器和 WordPress 保持最新,或者(這是第一個問題)安裝 WPO 插件。 後者的一些例子包括:
- W3 Total Cache 安裝量超過 100 萬
- SiteGround Optimizer 也有超過 100 萬次安裝(順便說一下,我們在我們的網站中使用了其中之一)
- 我們的好朋友 Fernando Tellado 的 WordPress WPO 調整和優化
這些插件承諾通過一系列合理的優化來加速您的網站,一般來說,“任何 WordPress 網站都可以從中受益”。 這些優化包括:
- 在前端出列不必要的腳本,例如表情符號或 Dashicons
- 緩存頁面和數據庫查詢
- 減少標題中包含的信息量
- 組合和縮小 JavaScript 腳本和 CSS 樣式
- 縮小 HTML
- 從靜態資產的 URL 中刪除版本查詢 arg
- 延遲 JavaScript 腳本和/或異步加載它們
- ETC
正如我所說,這些類型的優化通常可能是有益的。 但根據我們的經驗,WordPress 網站上的所有 JS 優化往往會導致更多問題,從而使他們所謂的改進毫無用處。 這些年來我看到的真實例子是:
- 結合腳本。 您的瀏覽器必須請求的腳本越少越好。 這就是為什麼將所有腳本合二為一的原因。 然而,這可能是有問題的。 一般來說,如果 JavaScript 腳本崩潰,它的執行就會結束,並且錯誤會在瀏覽器的控制台中報告。 但只有該腳本的執行被停止; 您的其他腳本將正常運行。 但是如果你把它們全部結合起來……好吧,一旦一個腳本失敗,其他腳本(可能包括你的)就不會運行,你的用戶會認為是你的插件沒有按預期工作。
- 縮小腳本。 我見過一些縮小過程,不管你信不信,它們破壞了正則表達式並導致 JS 腳本出現語法錯誤。 當然,距離我上次遇到這個已經有一段時間了,但是...... :-/
- 查詢參數。 當你在 WordPress 中加入一個腳本時,你可以使用它的版本號(順便說一下,它可能是由
@wordpress/scripts
自動生成的)。 版本號非常有用:如果您更新插件並且更改了腳本,則此新版本號將確保所有訪問者看到不同的 URL,因此他們的瀏覽器將請求新版本。 不幸的是,如果 WPO 插件刪除了查詢字符串,您的訪問者可能不會意識到腳本已更改,他們將使用所述腳本的緩存副本……這可能會也可能不會導致意外後果。 該死!
一場徹底的災難,不是嗎? 但是等到你聽到下一個:
推遲腳本
在 Nelio,我們實施了一個 A/B 測試插件,用於跟踪您的訪問者並發現哪些設計和內容獲得了最多的轉化。 可以想像,我們的跟踪腳本類似於:
window.NelioABTesting = window.NelioABTesting || {}; window.NelioABTesting.init = ( settings ) => { // Add event listeners to track visitor events... console.log( settings ); };
也就是說,它公開了一個我們必須調用的init
函數,以便讓腳本知道當前正在運行的測試。 為了調用這個方法,我們在 PHP 中加入了一個內聯腳本,如下所示:
function nab_enqueue_tracking_script() { wp_enqueue_script( 'nab-tracking', ... ); wp_add_inline_script( 'nab-tracking', sprintf( 'NelioABTesting.init( %s );', wp_json_encode( nab_get_tracking_settings() ) ) ); } add_action( 'wp_enqueue_scripts', 'nab_enqueue_tracking_script' );
這會產生以下 HTML 標記:
<head> ... <script type="text/javascript" src="https://.../dist/tracking.js" ></script> <script type="text/javascript" > NelioABTesting.init( {"experiments":[...],...} ); </script> ... </head> <body> ...
但是當您的 WPO 插件將defer
屬性添加到我們的腳本時會發生什麼?
<head> ... <script defer <!-- This delays its loading... --> type="text/javascript" src="https://.../dist/tracking.js" ></script> <script type="text/javascript" > NelioABTesting.init( {"experiments":[...],...} ); </script> ... </head> <body> ...
好吧,腳本現在被推遲了……這意味著前面的代碼片段等同於這個:
<head> ... <script type="text/javascript" > NelioABTesting.init( {"experiments":[...],...} ); </script> ... </head> <body> ... <script type="text/javascript" src="https://.../dist/tracking.js" ></script> </body> </html>
並且,因此, nab-tracking-js
不再在它應該加載的時候加載,因此,在它之後並依賴它的內聯腳本將簡單地失敗: nab-tracking-js-after
使用NelioABTesting.init
哪個,由於defer
指令,目前還不可用。 可怕!
解決方案
最有效的解決方案很明確:告訴您的用戶禁用腳本優化並收工。 畢竟,JavaScript 中的依賴管理通常非常複雜(特別是如果我們使用defer
和async
指令),WordPress 也不例外。 看看這個關於這個話題的 12 歲討論吧!
但是,如果這不可行(我知道不可行),我建議您做與我們相同的事情:擺脫init
方法並顛倒您的常規和內聯腳本的職責。 也就是說,在常規腳本之前添加一個內聯腳本,並使用它來定義一個具有要求設置的全局變量:
function nab_enqueue_tracking_script() { wp_enqueue_script( 'nab-tracking', ... ); wp_add_inline_script( 'nab-tracking', sprintf( 'NelioABTestingSettings = %s;', wp_json_encode( nab_get_tracking_settings() ) ), 'before' ); } add_action( 'wp_enqueue_scripts', 'nab_enqueue_tracking_script' );
這樣生成的 HTML 如下所示:
<head> ... <script type="text/javascript" > NelioABTestingSettings = {"experiments":[...],...}; </script> <script type="text/javascript" src="https://.../dist/tracking.js" ></script> ... </head> <body> ...
因此外部腳本的執行是否延遲並不重要——它總是出現在內聯腳本之後,從而滿足它們之間的依賴關係。
最後,如果您想確保沒有人會修改您的設置,請將變量聲明為const
並使用Object.freeze
凍結其值:
... sprintf( 'const NelioABTestingSettings = Object.freeze( %s );', wp_json_encode( nab_get_tracking_settings() ) ), ...
所有現代瀏覽器都支持它。
#2 WordPress 中可能會或可能不會起作用的依賴項……
依賴管理在 WordPress 中也可能存在問題,尤其是在談論 WordPress 的內置腳本時。 讓我解釋。
例如,想像一下,我們正在為 Gutenberg 創建一個小型擴展,正如我們在此處解釋的那樣。 我們插件的源代碼可能會有一些像這樣的import
語句:
import { RichTextToolbarButton } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { registerFormatType } from '@wordpress/rich-text'; // ...
當這個 JS 源代碼被轉譯後,Webpack(或你使用的工具)會將所有依賴項和你自己的源代碼打包成一個 JS 文件。 這是您稍後將從 WordPress 中排入隊列的文件,以便一切按您的預期工作。
如果您使用@wordpress/scripts
創建這樣的文件,則某些依賴項將不會包含在輸出文件中,因為內置過程假定包將在全局範圍內可用。 這意味著以前的導入將被轉譯成類似這樣的東西:
const { RichTextToolbarButton } = window.wp.blockEditor; const { __ } = window.wp.i18n; const { registerFormatType } = window.wp.richText; // ...
為了確保您不會錯過任何腳本的依賴項, @wordpress/scripts
不僅會轉譯您的 JS 代碼,還會生成一個包含其 WordPress 依賴項的 PHP 文件:
<?php return array( 'dependencies' => array('wp-block-editor','wp-i18n','wp-rich-text'), 'version' => 'a12850ccaf6588b1e10968124fa4aba3', );
很整潔吧? 那麼,有什麼問題呢? 好吧,這些 WordPress 軟件包正在持續開發中,並且它們經常更改,添加新功能和改進。 因此,如果您使用最新版本的 WordPress 開發您的插件,您最終可能會無意中使用該最新版本中可用的功能或特性(因此一切正常),但在“舊”WordPress 版本中卻沒有……
你怎麼知道?
解決方案
我的建議很簡單:使用最新的 WordPress 版本開發你的插件,但在舊版本上測試你的版本。 特別是,我建議您至少使用您的插件應該支持的最低 WordPress 版本來測試您的插件。 您可以在插件的readme.txt
中找到最低版本:
=== Nelio Content === ... Requires PHP: 7.0 Requires at least: 5.4 Tested up to: 5.9 ...
從一個 WordPress 版本切換到另一個版本就像運行以下 WP CLI 命令一樣簡單:
wp core update --version=5.4 --force
#3 箭頭函數比你想像的要復雜
最後,讓我分享一下我幾天前遇到的最新問題之一,這讓我發瘋了。 簡而言之,我們有一個類似於這個的 JavaScript 文件:
import domReady from '@wordpress/dom-ready'; domReady( () => [ ...document.querySelectorAll( '.nelio-forms-form' ) ] .forEach( initForm ) ); // Helpers // ------- const initForm = ( form ) => { ... } // ...
它在前端初始化您的 Nelio Forms。 劇本很簡單,不是嗎? 它定義了一個在 DOM 準備就緒時調用的匿名函數。 此函數使用名為initForm
的輔助(箭頭)函數。 好吧,事實證明,這樣一個簡單的例子可能會崩潰! 但僅在某些特定情況下(即,如果腳本由 WPO 插件使用defer
屬性“優化”)。
下面是 JS 如何執行前面的腳本:
-
domReady
裡面的匿名函數是定義的 domReady
運行- 如果 DOM 尚未準備好(通常在加載腳本時未準備好),
domReady
不會運行回調函數。 相反,它只是跟踪它,以便以後可以調用它 - JavaScript 繼續解析文件並加載
initForm
函數 - 一旦 DOM 準備好,回調函數最終會被調用
現在,如果當我們到達第三步時,DOM 已經準備好,因此domReady
直接調用匿名函數怎麼辦? 好吧,在那種情況下,腳本會觸發一個未定義的錯誤,因為initForm
仍然是undefined
。
事實上,這一切最令人好奇的是,這兩種解決方案是等價的:
domReady( aux ); const aux = () => {};
domReady( () => aux() ); const aux = () => {}
JavaScript linter 只會在第一個錯誤上拋出錯誤,而不是最新的。
解決方案
有兩種可能的解決方案:要么使用function
關鍵字定義輔助函數而忘記箭頭函數,要么在定義所有輔助函數後將domReady
語句移到最後:
domReady( aux ); function aux() { // ... }
const aux = () => { // ... }; domReady( aux );
如果您想知道為什麼第一個解決方案顯然與我們的原始解決方案等效,那麼它完全是關於 JavaScript 提升的工作原理。 簡而言之,在 JavaScript 中,您可以在定義function
之前使用函數(使用 function 定義),但不能對變量和常量(以及箭頭函數)執行相同操作。
總之
JavaScript 中可能會出錯的地方有很多。 幸運的是,他們都有解決方案,特別是如果我們注意我們所做的事情。 我希望你今天學到了一些新的東西,我相信由於我過去所犯的錯誤和錯誤,你將來能夠避免在自己的肉體中遭受痛苦。
Ian Stauffer 在 Unsplash 上的特色圖片。