重要事項:本文件不適用於您目前選取的格式 email!
amp-script
說明
在 Web Worker 中執行自訂 JavaScript。
必要指令碼
<script async custom-element="amp-script" src="https://cdn.ampproject.org/v0/amp-script-0.1.js"></script>
用法
amp-script
元件可讓您執行自訂 JavaScript。為了維持 AMP 的效能保證,您的程式碼會在 Web Worker 中執行,並適用某些限制。
虛擬 DOM
您的 JavaScript 可以存取頁面中包裝在 <amp-script>
元件內的部分。amp-script
會將元件的子項複製到虛擬 DOM。您的程式碼可以將該虛擬 DOM 當做 document.body
存取。
例如,這個 <amp-script>
元件定義一個由單一 <p>
組成的 DOM。
<amp-script src="http://example.com/my-script.js" width="300" height="100"> <p>A single line of text</p> </amp-script>
如果您的程式碼將元素附加到 document.body
// my-script.js const p = document.createElement('p'); p.textContent = 'A second line of text'; document.body.appendChild(p);
新元素將會放在該 <p>
之後。
<amp-script src="http://example.com/my-script.js" width="300" height="100"> <p>A single line of text</p> <p>A second line of text</p> </amp-script>
載入 JavaScript
amp-script
元素可以透過兩種方式載入 JavaScript
- 從遠端網址載入
- 從頁面上的
<script>
元素在本機載入
從遠端網址
使用 src
屬性從網址載入 JavaScript
<amp-script layout="container" src="https://example.com/hello-world.js"> <button>Hello amp-script!</button> </amp-script>
從本機元素
您也可以將 JavaScript 行內包含在 script
標記中。您必須
- 將
amp-script
的script
屬性設為本機script
元素的id
。 - 在您的
amp-script
中加入target="amp-script"
。 - 在您的
script
中加入type="text/plain"
。如此一來,瀏覽器就不會執行您的指令碼,讓 amp-script 可以控制它。
<!-- To use inline JavaScript, you must add a script hash to the document head. --> <head> <meta name="amp-script-src" content="sha384-YCFs8k-ouELcBTgzKzNAujZFxygwiqimSqKK7JqeKaGNflwDxaC3g2toj7s_kxWG" /> </head> ... <amp-script width="200" height="100" script="hello-world"> <button>Hello amp-script!</button> </amp-script> <!-- Add [target="amp-script"] to the <script> element. --> <script id="hello-world" type="text/plain" target="amp-script"> const btn = document.querySelector('button'); btn.addEventListener('click', () => { document.body.textContent = 'Hello World!'; }); </script>
script
或跨來源 src
屬性的 amp-script
元素需要在 <meta name="amp-script-src" content="...">
標記中包含指令碼雜湊值。此外,同來源 src
檔案必須具有 Content-Type
:application/javascript
或 text/javascript
。
如果您的頁面包含多個 amp-script
元素,而且每個元素都需要指令碼雜湊值,請將每個雜湊值以空白分隔的清單形式加入單一 <meta name="amp-script-src" content="...">
標記中 (如需包含多個指令碼雜湊值的範例,請參閱 examples/amp-script/example.amp.html)。
運作方式
amp-script
會在 Web Worker 中執行您的自訂 JavaScript。一般來說,Web Worker 無法存取 DOM。但是 amp-script
可讓您的程式碼存取虛擬 DOM。當您的 JavaScript 修改這個虛擬 DOM 時,amp-script
會更新實際 DOM。
在底層,amp-script
使用 @ampproject/worker-dom。如需設計詳細資料,請參閱「Intent to Implement」議題。
功能
支援的 API
DOM 元素及其屬性通常都受到支援,但有一些限制。例如,您的程式碼無法將新的 <script>
或 <style>
標記新增至 DOM。
amp-script
會重新建立許多常用的 DOM API,並讓您的程式碼可以使用。這個「Hello World」範例使用 getElementById()
、addEventListener()
、createElement()
、textContent
和 appendChild()
const button = document.getElementById('textarea'); button.addEventListener('click', () => { const h1 = document.createElement('h1'); h1.textContent = 'Hello World!'; document.body.appendChild(h1); });
支援的 DOM API 包括
- 元素 getter,例如
getElementByName()
、getElementsByClassName()
、getElementsByTagName()
、childNodes()
、parentNode()
和lastChild()
- Mutator,例如
createTextNode()
、appendChild()
、insertBefore()
、removeChild()
和replaceChild()
- 涉及事件的方法,例如
addEventListener()
、removeEventListener()
和createEvent()
- 屬性和屬性 getter,例如
getAttribute()
、hasAttribute()
- 事件屬性,例如
Event.target
、Event.type
和Event.bubbles
- 元素屬性,例如
attributes
、id
、outerHTML
、textContent
、value
、classList
和className
- 以及更多。
如需支援的 DOM API 的完整清單,請參閱API 相容性表。
querySelector()
支援簡單的選取器 - 元素、ID、類別和屬性。因此,document.querySelector('.class')
可以運作,但 document.querySelector('.class1 .class2')
無法運作。請參閱程式碼以取得詳細資訊。
amp-script
支援常見的 Web API,例如 Fetch
、WebSockets
、localStorage
、sessionStorage
和 Canvas
。目前,History
API 尚未實作,Cookie 也沒有。
amp-script
不支援整個 DOM API 或 Web API,因為這會使 amp-script
自己的 JavaScript 過於龐大且速度緩慢。如果您希望看到支援某個 API,請提交問題,或建議並自行貢獻變更。
amp-script
使用情況的範例集,請參閱這裡。架構和程式庫
目前,如果未經修改,jQuery 等程式庫將無法與 amp-script
搭配使用,因為它們使用不受支援的 DOM API。但是,@ampproject/worker-dom 的設計目的是為了支援熱門 JavaScript 架構使用的 API。amp-script
已使用 React 和 Preact 進行測試。為了保持套件大小較小,我們建議使用 Preact。其他架構可能可以運作,但尚未經過完整測試;如果您正在尋求支援,請提交問題或在此處貢獻。
建立 AMP 元素
您可以使用 amp-script
將 amp-img
或 amp-layout
元件新增至 DOM。目前不支援其他 AMP 元件。如果您需要建立不同的 AMP 元素,請在 #25344 上投票並新增註解,說明您的使用案例。
參照 amp-state
amp-script
支援取得和設定 amp-state
JSON。這可讓 amp-script
透過 amp-bind
繫結與頁面上的其他 AMP 元素互動。
如果您在使用者手勢後從 amp-script
叫用 AMP.setState()
,繫結可能會導致 DOM 發生突變。否則,狀態將會設定,但不會發生繫結 (類似於 amp-state
初始化)。如需詳細資訊,請參閱關於使用者手勢的章節。
AMP.setState()
的運作方式如 <amp-bind>
文件中所述,將物件常值合併到指定的狀態中。這個範例示範它如何影響 DOM
<script id="myscript" type="text/plain" target="amp-script"> const button = document.getElementsByTagName('button')[0]; function changer() { AMP.setState({myText: "I have changed!"}); } button.addEventListener('click', changer); </script> <amp-script layout="container" script="myscript"> <p [text]="myText">Will I change?</p> <button>Change it!</button> </amp-script>
AMP.getState()
是非同步的,並傳回 Promise。Promise 會使用傳遞給它的狀態變數的字串化值來解析。這些範例示範其與不同類型的用法
async function myFunction() { AMP.setState({'text': 'I am a string'}); let text = await AMP.getState('text'); AMP.setState({'number': 42}); let number = Number(await AMP.getState('number')); AMP.setState({'obj': {'text': 'I am a string', 'number': 42}}); let obj = JSON.parse(await AMP.getState('obj')); }
以下是另一個範例。amp-state
不支援其 src
屬性中的 WebSocket 網址,但我們可以使用 amp-script
從 WebSocket 傳入資料。
<amp-script width="1" height="1" script="webSocketDemo"> </amp-script> <script type="text/plain" target="amp-script" id="webSocketDemo"> const socket = new WebSocket('wss://websocket.example'); socket.onmessage = event => { AMP.setState({socketData: event.data}); }; </script>
AMP.setState()
,您必須在文件標頭中加入 amp-bind
擴充功能指令碼。請注意,如果其他項目變更了 <amp-script>
內部的 DOM,該變更不會傳播到虛擬 DOM。同步處理是單向的。因此,最好避免像下列程式碼
<amp-script layout="container" script="myscript"> <p [text]="myText">Will I change?</p> </amp-script> <button on="tap:AMP.setState({myText: 'I changed'})"> Change this and amp-script won't know </button>
擷取 <amp-list>
的資料
您可以匯出函式以做為 <amp-list>
的資料來源。匯出的函式必須傳回 JSON,或是使用 JSON 解析的 Promise。
匯出 API 在全域範圍中可用,且具有下列簽名
/** * @param {string} name the name to identify the function by. * @param {Function} function the function to export. */ function exportFunction(name, function) {}
限制
為了維持 AMP 對效能和版面配置穩定性的保證,amp-script
施加了一些限制。
JavaScript 程式碼大小
amp-script
對程式碼大小有標準
- 每個行內指令碼最多可包含 10,000 個位元組
- 頁面上的指令碼總共最多可包含 150,000 個位元組
- 在頁面上以沙箱模式執行的指令碼總共最多可包含 300,000 個位元組
使用者手勢
在某些情況下,除非 DOM 變更是由您的 JavaScript 程式碼 (例如,按鈕點擊) 觸發,否則 amp-script
不會套用這些變更。這有助於防止內容版面配置位移造成不良的使用者體驗。
對於大小無法變更的 amp-script
容器,規則較不嚴格。當 layout
不是 container
且維度在 HTML 中指定時,就會發生這種情況。我們將這類容器稱為「固定大小」。我們將大小可以變更的容器稱為「可變大小」。
以下是一些固定大小容器的範例
<amp-script layout="fill" height="300" width="500" script="myscript"></amp-script> <amp-script layout="fixed-height" height="300" script="myscript" ></amp-script>
以下是一些可變大小容器的範例
<amp-script layout="responsive" script="myscript"></amp-script> <amp-script layout="fixed" height="300" script="myscript"></amp-script> <amp-script layout="container" height="300" width="500" script="myscript"></amp-script>
DOM 變更的允許方式如下
- 在固定大小容器中,您的程式碼可以隨時進行任何變更。
- 在可變大小容器中,您的程式碼只能在使用者手勢後進行變更。然後它有 5 秒的時間可以進行變更。如果您的程式碼進行一或多個
fetch()
,它可以繼續進行變更,直到最後一個fetch()
完成後 5 秒。
固定大小容器 | 可變大小容器 | |
在頁面載入時變更 | 隨時允許 | 不允許 |
在使用者事件後變更 | 隨時允許 | 允許 5 秒 + fetch() |
計算指令碼雜湊值
由於在 amp-script
中執行的自訂 JS 不受一般內容安全政策的約束,因此您需要新增指令碼雜湊值
- 適用於行內 JavaScript
- 適用於從跨來源來源載入的 JavaScript
在文件標頭的 meta[name=amp-script-src]
元素中加入指令碼雜湊值。您需要每個 <amp-script>
元件使用的每個指令碼的雜湊值。以下是幾種建構雜湊值的方法
- 如果您省略
<meta>
標記,AMP 會輸出包含預期雜湊字串的控制台錯誤。您可以複製此字串來建立適當的<meta>
標記。 - AMP Optimizer 節點模組會產生此雜湊值,並自動插入
<meta>
標記。 - 自行建構,使用下列步驟
- 計算指令碼內容的 SHA384 雜湊總和。此總和應以十六進位表示。
- 將結果編碼為 base64url。
- 在前面加上
sha384-
。
以下說明如何在 Node.js 中計算雜湊值
const crypto = require('crypto'); const hash = crypto.createHash('sha384'); function generateCSPHash(script) { const data = hash.update(script, 'utf8'); return ( 'sha384-' + data .digest('base64') .replace(/=/g, '') .replace(/\+/g, '-') .replace(/\//g, '_') ); }
@ampproject/toolbox-script-csp 節點模組也會計算雜湊值。
此範例說明如何在 HTML 中使用指令碼雜湊值
<head> <!-- A meta[name="amp-script-src"] element contains all script hashes for <amp-script> elements on the page, delimited by spaces. --> <meta name="amp-script-src" content=" sha384-fake_hash_of_remote_js sha384-fake_hash_of_local_script " /> </head> <body> <!-- A "src" attribute with a cross-origin URL requires adding a script hash. If the hash of remote.js's contents is "fake_hash_of_remote_js", we'll add "sha384-fake_hash_of_remote_js" to the <meta> tag above. --> <amp-script src="cross.origin/remote.js" layout="container"> </amp-script> <!-- A "script" attribute also requires adding a script hash. If the hash of #myScript's text contents is "fake_hash_of_local_script", we'll add "sha384-fake_hash_of_local_script" to the <meta> tag above. --> <amp-script script="myScript" layout="container"> </amp-script> <script type="text/plain" target="amp-script" id="myScript"> document.body.textContent += 'Hello world!'; </script> </body>
data-ampdevmode
屬性新增至 amp-script
元素或根 html 節點,以停用 JavaScript 大小和指令碼雜湊值需求。將此屬性新增至根 html 節點會抑制頁面上的所有驗證錯誤。將其新增至 amp-script
元素只會抑制有關大小和指令碼雜湊值的錯誤。屬性
src
適用於執行遠端指令碼。
將在此 <amp-script>
的內容中執行的 JS 檔案網址。網址的協定必須是 HTTPS。HTTP 回應的 Content-Type
必須是 application/javascript
或 text/javascript
。
script
適用於執行本機指令碼。
script[type=text/plain][target=amp-script]
元素的 id
,其文字內容包含將在此 <amp-script>
的內容中執行的 JS。
sandbox
對此 <amp-script>
可能突變的 DOM 套用額外限制。與 iframe[sandbox]
屬性類似,屬性的值可以是空白以套用所有限制,或是以空格分隔的符記來解除特定限制
max-age
需要 script
屬性。此屬性為選用屬性,但如果已指定 script
,則簽署交換必須使用此屬性。
max-age
屬性指定本機指令碼允許從簽署交換 (SXG) 發布時間起算的最大生命週期 (以秒為單位)。AMP Packager 使用此值來計算 SXG expires
時間。
max-age
的值應謹慎選擇
-
較長的
max-age
會增加SXG 降級的潛在安全性影響。 -
較短的
max-age
可能會阻止納入具有最短 SXG 生命週期的 AMP 快取。例如,Google AMP 快取至少需要 4 天 (345600 秒)。請注意,由於 SXG 規格設定的上限,目前沒有理由選擇超過 7 天 (604800 秒) 的max-age
。
如果您未發布簽署交換,max-age
不會執行任何動作。
nodom
選用的 nodom
屬性會最佳化 <amp-script>
,使其可用做資料層而非 UI 層。它會移除 <amp-script>
進行 DOM 修改的功能,以換取顯著較小的套件大小,進而獲得更佳效能。它也會自動隱藏 <amp-script>
,因此您可以省略高度和寬度屬性。
sandboxed
如果設定此屬性,這將表示 worker-dom 應啟動沙箱模式。在此模式下,Worker 會位於其自己的跨來源 iframe 中,建立強大的安全性界限。它也會強制執行 nodom 模式。由於安全性界限強大,沙箱指令碼不需要提供指令碼雜湊值。
通用屬性
此元素包含擴充至 AMP 元件的通用屬性。
錯誤和警告
使用 amp-script
時可能會遇到一些執行階段錯誤。
行內指令碼為 (...) 個位元組,超出 10,000 個位元組的限制。
沒有任何行內指令碼可以超過 10,000 個位元組。請參閱上方的JavaScript 程式碼大小。
超出最大指令碼總大小 (...)。
頁面使用的所有非沙箱指令碼總共不能超過 150,000 個位元組。請參閱上方的JavaScript 程式碼大小。
頁面使用的所有沙箱指令碼 (請參閱沙箱模式) 總共不能超過 300,000 個位元組。請參閱上方的JavaScript 程式碼大小。
找不到指令碼雜湊值。
對於本機指令碼和跨來源指令碼,您需要新增指令碼雜湊值以確保安全性。
(...) 必須在 meta[name="amp-script-src"] 中具有 "sha384-(...)"
再次提醒,您需要指令碼雜湊值。只需將此錯誤中的值複製到您的 <meta>
標記即可。
JavaScript 指令碼雜湊值需求在沙箱模式中已停用。
JavaScript 大小和指令碼雜湊值需求在開發模式中已停用。
如果您的 <amp-script>
包含 data-ampdevmode
屬性,AMP 將不會檢查您的指令碼雜湊值或程式碼大小。
已封鎖 (...) 嘗試修改 (...)
為了避免不必要的內容版面配置位移,amp-script
在某些情況下不允許 DOM 突變。請參閱上方的使用者手勢。
amp-script... 因非法突變而終止
如果指令碼嘗試進行太多不允許的 DOM 變更,amp-script
可能會停止指令碼,使其不會與 DOM 過於不同步。
AMP.setState 僅更新頁面狀態,且由於最近缺少使用者互動,因此未重新評估繫結。
如果您在使用者互動之前在可變大小容器中修改狀態變數,amp-script
將不會更新 DOM 以避免不必要的內容版面配置位移。請參閱上方的參照 amp-state。
除非您的 <amp-script>
包含屬性 sandbox="allow-forms",否則無法突變表單元素 (...)。
此屬性為安全性所需。請參閱上方的通用屬性。
已清理節點:(...)
如果您的程式碼新增了不允許的元素 (例如 <script>
、<style>
或不受支援的 AMP 元件),amp-script
將會移除它。
您已經閱讀這份文件十幾次了,但它仍然沒有涵蓋您的所有問題?或許其他人也有相同的感受:在 Stack Overflow 上與他們交流。
前往 Stack Overflow 發現錯誤或缺少功能?AMP 專案強烈鼓勵您的參與和貢獻!我們希望您能成為我們開放原始碼社群的長期參與者,但我們也歡迎您針對您特別熱衷的問題提供一次性貢獻。
前往 GitHub