Do you build things with AMP? Fill out the AMP Developer Survey!
AMP

amp-script

Description

在Web Worker中运行自定义JavaScript。

Required Scripts

<script async custom-element="amp-script" src="https://cdn.ampproject.org/v0/amp-script-0.1.js"></script>

示例

用法

amp-script 组件允许您运行自定义JavaScript。为了保持AMP的性能保证,您的代码在Web Worker中运行,并且存在某些限制。

amp-script 元素可以通过两种方式加载JavaScript:

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

从远程URL加载JavaScript

使用该src属性从URL加载JavaScript:

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

从本地元素加载JavaScript

你也可以将你的JavaScript嵌入到一个 script 标签中。你必须:

  • 设置你的 amp-scriptscript 属性为本地 script 元素的 id.
  • 在你的 target="amp-script" 中包含 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="..."> 标签中使用script hash。同样, 同源的 src 文件必须有 Content-Type: application/javascripttext/javascript

它是如何工作的?

amp-script 在一个可以访问虚拟DOM的 Web Worker 中运行你的定制JavaScript。 当你的JavaScript代码修改这个虚拟DOM时, amp-script 会将这些修改转发到主线程,并将它们应用到 amp-script 元素子树中。

例如,在 document.body中添加一个元素:

// my-script.js
const p = document.createElement('p');
p.textContent = 'I am added to the body!';
document.body.appendChild(p);

Will be reflected on the page as a new child of the amp-script element:

<amp-script src="http://example.com/my-script.js" width="300" height="100">
  <p>I am added to the body!</p>
</amp-script>

在底层, amp-script 使用 @ampproject/worker-dom。有关设计细节,请参阅 "Intent to Implement" 问题.

状态操作

amp-script 支持通过JavaScript获取和设置 amp-state JSON。

这样就可以通过 amp-bindbindings 在页面上的 amp-script 和其他AMP元素之间进行高级交互。 调用 AMP.setState()amp-script 可能会导致DOM发生突变,只要它是由用户手势触发的,否则它将只隐式地设置状态 (类似于 amp-state 初始化).

AMP.setState() 要求 amp-bind 扩展脚本包含在文档头中。

/**
 * Deep-merges `json` into the current amp-state.
 * @param {!Object} json A JSON object e.g. must not contain circular references.
 */
AMP.setState(json) {}

/**
 * Asynchronously returns amp-state.
 * @param {string=} expr An optional JSON expression string e.g. "foo.bar".
 * @return {!Promise<!Object>}
 */
AMP.getState(expr) {}

使用WebSocket和AMP.setState()的例子

<amp-script width="1" height="1" script="webSocketDemo"> </amp-script>

<!--
  <amp-state> doesn't support WebSocket URLs in its "src" attribute,
  but we can use <amp-script> to work around it. :)
-->
<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>

限制

支持的 API

目前,支持大多数DOM元素及其属性。 DOM查询API(例如 querySelector )具有部分支持。 浏览器API(例如 History)尚未实现。有关详细信息,请参见 API compatibility table

如果您希望看到支持的API,请 file an issue 并提到 @choumx@kristoferbaxter.

JavaScript代码的大小

amp-script 有关于代码大小的标准:

  • 通过 script[type=text/plain][target=amp-script] 使用本地脚本的每个 amp-script 元素最多10,000字节。
  • 页面上所有 amp-script 元素的最大总数为150,000字节。

用户手势

在某些情况下, amp-script 需要一个用户手势来应用由JavaScript代码触发的变化 (我们称之为“突变”) 到 amp-script 的DOM子节点。这有助于避免意外的内容跳跃带来的糟糕用户体验。

突变的规律如下:

  1. 对于具有 non-container layoutamp-script 元素,突变总是允许的。
  2. 对于具有容器布局的 amp-script 元素,允许在用户手势之后的五秒钟内发生变化。如果触发了 fetch() 这个5秒的窗口将扩展一次。

创建AMP元素

关于AMP元素的动态创建(例如通过 document.createElement()),目前只允许 amp-imgamp-layout。 请对 #25344 和您的用例进行投票或评论。

计算脚本哈希

由于自定义JS运行在 amp-script 不受常规的 Content Security Policy,你需要添加这个脚本哈希:

  • 用于内联JavaScript
  • 用于从跨域源加载的JavaScript

在文档头的' meta[name=amp-script-src] 元素中包含脚本哈希。以下是构建哈希的几种方法:

  • 如果你省略 <meta> 标签,AMP将输出一个控制台错误,包含预期的哈希字符串。 你可以复制它来创建合适的 <meta> 标签。
  • AMP Optimizer node module 生成这个散列并自动插入' <meta> 标签。
  • 使用以下步骤自己构建它:
  1. 计算脚本内容的SHA384哈希值总和。此总和应以十六进制表示。
  2. base64url-encode 结果。
  3. sha384- 开头。

这是你如何在Node.js中计算哈希值的方法:

const crypto = require('crypto');
const hash = crypto.createHash('sha384');

function generateCSPHash(script) {
  const data = hash.update(script, 'utf-8');
  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

用于执行远程脚本。

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

script

用于执行本地脚本。

script[type=text/plain][target=amp-script] 元素的 id,其文本内容包含将在此 <amp-script> 上下文中执行的JS。

sandbox (可选)

对DOM应用额外的限制,这些限制可能会被此 <amp-script>更改。与 iframe[sandbox] 属性类似,该属性的值可以为空以应用所有限制,也可以为空格分隔的标记以解除特定限制:

  • allow-forms: 允许创建和修改 form elements。AMP需要特殊处理,以防止来自用户输入的未经授权的状态更改请求。有关更多详细信息,请参见amp-form的 安全注意事项

max-age (可选, 但如果指定了script,则需要进行签名交换)

需要 script 属性。

max-age 属性指定从[signed exchange (SXG) 发布开始允许提供的本地脚本的最大生存期(以秒为单位)。AMP Packager 使用此值来计算SXG expires 时间。

max-age 的值应该谨慎选择:

  • 更长的 max-age 增加了 SXG downgrade 的潜在安全影响。

  • 较短的 max-age 可能会阻止包含在具有最小SXG生存期的AMP缓存中。例如,Google AMP缓存至少需要 4 天 (345600 秒). 注意,目前没有理由选择超过7天(604800 seconds)的 max-age ,因为SXG规范设置了maximum

如果您不发布已签名的交换, max-age 将不会起任何作用。

常见属性

此元素包含扩展到 AMP 组件的 常见属性

错误

在使用 amp-script 时可能会遇到几种类型的运行时错误。

"Maximum total script size exceeded (...)"

amp-script 限制了可能使用的JS源代码的大小。 请参阅上面的Size of JavaScript code

"Script hash not found."

对于本地脚本和跨源脚本,您需要添加一个script hash以保证安全性。

"amp-script... was terminated due to illegal mutation"

为了避免意外的内容跳跃, amp-script 通常需要用户在DOM更改时使用手势。请参阅上面的User gestures

需要更多帮助?

您多次阅读的资料未能涵盖您的所有问题?也许其他人也这么觉得 在 Stack Overflow 上与他们取得联系。

前往 Stack Overflow
找到一个 bug 或者缺失某项功能?

AMP 项目强烈鼓励您参与其中并作出贡献!我们希望您能成为我们开源社区的持续参与者,但我们也欢迎您对热衷问题做出单次贡献。

前往 GitHub