AMP

重要事項:本文件不適用於您目前選取的廣告格式!

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-scriptscript 屬性設為本機 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-Typeapplication/javascripttext/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()textContentappendChild()

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.targetEvent.typeEvent.bubbles
  • 元素屬性,例如 attributesidouterHTMLtextContentvalueclassListclassName
  • 以及更多。

如需完整的支援 DOM API 清單,請參閱 API 相容性表

querySelector() 支援簡單的選取器 (元素、ID、類別和屬性)。因此,document.querySelector('.class') 可以運作,但 document.querySelector('.class1 .class2') 則否。如需詳細資訊,請參閱程式碼

amp-script 支援常見的 Web API,例如 FetchWebSocketslocalStoragesessionStorageCanvas。目前尚未實作 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 已在 ReactPreact 中測試過。為了縮減套件大小,建議您使用 Preact。其他框架可能也可以運作,但尚未經過完整測試;如果您需要相關支援,請提出問題,或在這裡貢獻

建立 AMP 元素

您可以使用 amp-scriptamp-imgamp-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 版面配置系統的詳細資訊,請參閱這裡

計算指令碼雜湊值

由於在 amp-script 中執行的自訂 JS 不受一般 內容安全政策的限制,因此您需要加入指令碼雜湊值

  • 適用於內嵌 JavaScript
  • 適用於從跨網域來源載入的 JavaScript

在文件標頭的 meta[name=amp-script-src] 元素中加入指令碼雜湊值。您需要為每個 <amp-script> 元件使用的指令碼加入雜湊值。以下是幾種建立雜湊值的方法

  • 如果您省略 <meta> 標記,AMP 會輸出主控台錯誤,其中包含預期的雜湊字串。您可以複製這個字串來建立適當的 <meta> 標記。
  • AMP Optimizer Node.js 模組會產生這個雜湊值,並自動插入 <meta> 標記。
  • 請按照下列步驟自行建置
  1. 計算指令碼內容的 SHA384 雜湊總和。這個總和應以十六進位表示。
  2. 以 base64url 編碼結果。
  3. 在前面加上 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/javascripttext/javascript

script

適用於執行本機指令碼。

script[type=text/plain][target=amp-script] 元素的 id,該元素的文字內容包含將在此 <amp-script> 環境中執行的 JS。

sandbox

對這個 <amp-script> 可能變更的 DOM 施加額外限制。與 iframe[sandbox] 屬性類似,屬性的值可以是空白 (套用所有限制),也可以是空格分隔的符記 (取消特定限制)

  • allow-forms:允許建立及修改表單元素。AMP 需要特殊處理,以防止未經授權的狀態變更要求來自使用者輸入。如需更多詳細資訊,請參閱 amp-form 的安全考量

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

請勿與 sandbox 屬性混淆。

如果設定這個屬性,就會發出訊號,表示 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