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>
来自本地元素
您也可以将 JavaScript 内联包含在 script
标记中。您必须
- 将
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
向 DOM 添加 amp-img
或 amp-layout
组件。目前不支持其他 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 node 模块 会生成此哈希值并自动插入
<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 node 模块也会计算哈希值。
此示例展示了如何在 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 文件的 URL。URL 的协议必须为 HTTPS。HTTP 响应的 Content-Type
必须为 application/javascript
或 text/javascript
。
script
用于执行本地脚本。
script[type=text/plain][target=amp-script]
元素的 id
,该元素的文本内容包含将在该 <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