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。您的程式碼可以將該虛擬 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-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 元素及其屬性通常都受到支援,但有一些限制。例如,您的程式碼無法將新的 <script><style> 標記新增至 DOM。

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()
  • 變更器,例如 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 個位元組

使用者手勢

在某些情況下,除非程式碼是由使用者手勢 (例如按鈕點擊) 觸發,否則 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,則簽署交換 (Signed Exchange) 需要此屬性。

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

請勿與 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