重要提示:此文档不适用于您当前选择的格式 ads!
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”问题。
功能
支持的 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
将脚本哈希值包含在文档 head 中的 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 秒)。请注意,目前没有理由选择长于 7 天(604800 秒)的max-age
,因为 SXG 规范设置了最大值。
如果您不发布签名交换,则 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