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-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。有关设计详情,请参阅 “实施意向”问题。
功能
支持的 API
通常支持 DOM 元素及其属性,但有一些限制。例如,你的代码无法向 DOM 添加新的 <script>
或 <style>
标记。
amp-script
重新创建了许多常用的 DOM 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 包括
- 元素获取器,如
getElementByName()
、getElementsByClassName()
、getElementsByTagName()
、childNodes()
、parentNode()
和lastChild()
- 变异器,如
createTextNode()
、appendChild()
、insertBefore()
、removeChild()
和replaceChild()
- 涉及事件的方法,如
addEventListener()
、removeEventListener()
和createEvent()
- 属性和获取器,如
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 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-script
中运行的自定义 JS 不受常规 内容安全策略 的约束,因此您需要添加脚本哈希
- 用于内联 JavaScript
- 用于从跨源加载的 JavaScript
在文档头部中 meta[name=amp-script-src]
元素中包含脚本哈希。您需要一个哈希,供 <amp-script>
组件使用。以下是一些构建哈希的方法
- 如果您省略
<meta>
标记,AMP 将输出一个控制台错误,其中包含预期的哈希字符串。您可以复制此内容以创建适当的<meta>
标记。 - AMP Optimizer 节点模块 将生成此哈希并自动插入
<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 节点模块也会计算哈希。
此示例显示了如何在 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/javascript
或 text/javascript
。
script
用于执行本地脚本。
id
为 script[type=text/plain][target=amp-script]
元素,其文本内容包含将在 <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 秒)。请注意,由于 SXG 规范设置的最大值,目前没有理由选择超过 7 天(604800 秒)的max-age
。
如果您不发布已签名的交换,则 max-age
无效。
nodom
可选的 nodom
属性优化了 <amp-script>
,使其用作数据层而不是 UI 层。它移除了 <amp-script>
进行 DOM 修改的能力,以支持显著更小的包大小,从而获得更好的性能。它还自动隐藏 <amp-script>
,因此您可以省略高度和宽度属性。
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