">文档:<amp-script> - amp.dev - AMP 框架
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

  • 远程,从 URL
  • 本地,从页面上的 <script> 元素

从远程 URL

使用 src 属性从 URL 加载 JavaScript

<amp-script layout="container" src="https://example.com/hello-world.js">
  <button>Hello amp-script!</button>
</amp-script>

从本地元素

您还可以在 script 标签中内联包含您的 JavaScript。您必须

  • 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-scriptWeb Worker 中运行自定义 JavaScript。通常,Web Worker 无法访问 DOM。但 amp-script 让你的代码可以访问虚拟 DOM。当你的 JavaScript 修改此虚拟 DOM 时,amp-script 会更新真实 DOM。

在底层,amp-script 使用 @ampproject/worker-dom。有关设计详情,请参阅 “实施意向”问题

功能

支持的 API

通常支持 DOM 元素及其属性,但有一些限制。例如,你的代码无法向 DOM 添加新的 <script><style> 标记。

amp-script 重新创建了许多常用的 DOM 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 包括

  • 元素获取器,如 getElementByName()getElementsByClassName()getElementsByTagName()childNodes()parentNode()lastChild()
  • 变异器,如 createTextNode()appendChild()insertBefore()removeChild()replaceChild()
  • 涉及事件的方法,如 addEventListener()removeEventListener()createEvent()
  • 属性和获取器,如 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 URL,但我们可以使用 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 节点模块 将生成此哈希并自动插入 <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 节点模块也会计算哈希。

此示例显示了如何在 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>

在开发期间,您可以通过向 amp-script 元素或根 html 节点添加 data-ampdevmode 属性来禁用 JavaScript 大小和脚本哈希要求。将此属性添加到根 html 节点将禁止页面上的所有验证错误。将其添加到 amp-script 元素将仅仅禁止有关大小和脚本哈希的错误。

属性

src

用于执行远程脚本。

将在该 <amp-script> 的上下文中执行的 JS 文件的 URL。URL 的协议必须为 HTTPS。HTTP 响应的 Content-Type 必须为 application/javascripttext/javascript

script

用于执行本地脚本。

idscript[type=text/plain][target=amp-script] 元素,其文本内容包含将在 <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 秒)。请注意,由于 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