重要事項:本文件不適用於您目前選取的廣告格式!
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。您的程式碼可以 document.body
的形式存取該虛擬 DOM。
例如,這個 <amp-script>
元件定義了一個 DOM,其中包含單一個 <p>
。
<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 元素及其屬性都受到支援,但有一些限制。例如,您的程式碼無法在 DOM 中新增 <script>
或 <style>
標記。
amp-script
重新建立許多常用的 DOM API,並讓您的程式碼可以使用這些 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 個位元組
使用者手勢
在某些情況下,除非 JavaScript 程式碼是由使用者手勢 (例如按鈕輕觸) 觸發,否則 amp-script
不會套用 JavaScript 程式碼觸發的 DOM 變更。這有助於防止內容版面配置位移造成不良的使用者體驗。
對於大小無法變更的 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 Node.js 模組會產生這個雜湊值,並自動插入
<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 Node.js 模組也會計算雜湊值。
這個範例說明如何在 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 秒)。請注意,目前沒有理由選擇超過 7 天 (604800 秒) 的max-age
,因為 SXG 規格設定了上限。
如果您未發布簽署交換,max-age
不會執行任何動作。
nodom
選填的 nodom
屬性會針對做為資料層而非 UI 層用途的 <amp-script>
進行最佳化。這個屬性會移除 <amp-script>
進行 DOM 修改的功能,以換取顯著縮減的套件大小,進而提升效能。這個屬性也會自動隱藏 <amp-script>
,因此您可以省略 height 和 width 屬性。
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