重要提示:此文档不适用于您当前选择的格式 stories!
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。有关设计细节,请参阅“Intent to Implement” issue。
功能
支持的 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 包括
- 元素 getter,如
getElementByName()
、getElementsByClassName()
、getElementsByTagName()
、childNodes()
、parentNode()
和lastChild()
- Mutator,如
createTextNode()
、appendChild()
、insertBefore()
、removeChild()
和replaceChild()
- 涉及事件的方法,如
addEventListener()
、removeEventListener()
和createEvent()
- 属性和属性 getter,如
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,请提交一个 issue 或建议并自行贡献更改。
amp-script
用法的一组示例,请参阅此处。框架和库
目前,诸如 jQuery 之类的库在未经修改的情况下将无法与 amp-script
一起使用,因为它们使用不受支持的 DOM API。但是,@ampproject/worker-dom 旨在支持流行的 JavaScript 框架使用的 API。amp-script
已经过 React 和 Preact 的测试。为了保持捆绑包大小较小,我们建议使用 Preact。其他框架可能可以工作,但尚未经过全面测试;如果您正在寻求支持,请提交 issue 或在此处贡献 here。
创建 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()
,您必须在文档 head 中包含 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 字节
用户手势
在某些情况下,除非您的 JavaScript 代码是由用户手势触发的(例如,按钮点击),否则 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>
amp-script
元素或根 html 节点添加 data-ampdevmode
属性来禁用 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>
,因此您可以省略 height 和 width 属性。
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