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

Important: this documentation is not applicable to your currently selected format email!

amp-script

Description

Runs custom JavaScript in a Web Worker.

Required Scripts

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

Usage

The amp-script component allows you to run custom JavaScript. To maintain AMP's performance guarantees, your code runs in a Web Worker, and certain restrictions apply.

An amp-script element can load JavaScript in two ways:

  • remotely, from a URL
  • locally, from a <script> element on the page

Load JavaScript from a remote URL

Use the src attribute to load JavaScript from a URL:

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

Load JavaScript from a local element

You can also include your JavaScript inline, in a script tag. You must:

  • Set the script attribute of your amp-script to the local script element's id.
  • Include target="amp-script" in your amp-script.
  • Include type="text/plain" in your script. This way, the browser won't execute your script, allowing amp-script to control it.
<!-- 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>

For security reasons, amp-script elements with a script or cross-origin src attribute require a script hash in a <meta name="amp-script-src" content="..."> tag. Also, same-origin src files must have Content-Type: application/javascript or text/javascript.

How does it work?

amp-script runs your custom JavaScript in a Web Worker that has access to a virtual DOM. When your JavaScript code modifies this virtual DOM, amp-script forwards these changes to the main thread and applies them to the amp-script element subtree.

For example, adding an element to 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>

Under the hood, amp-script uses @ampproject/worker-dom. For design details, see the "Intent to Implement" issue.

State manipulation

amp-script supports getting and setting amp-state JSON via JavaScript.

This enables advanced interactions between amp-script and other AMP elements on the page via amp-bind bindings. Invoking AMP.setState() from amp-script may cause mutations to the DOM as long as it was triggered by user gesture, otherwise it will only implicitly set state (similar to amp-state initialization).

AMP.setState() requires the amp-bind extension script to be included in the document head.

/**
 * 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) {}

Example with WebSocket and 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>

Restrictions

Allowed APIs

Currently, most DOM elements and their properties are supported. DOM query APIs like querySelector have partial support. Browser APIs like History are not implemented yet. See the API compatibility table for details.

If there's an API you'd like to see supported, please file an issue and mention @choumx and @kristoferbaxter.

Size of JavaScript code

amp-script has the following restrictions on JavaScript file size:

  • Maximum of 10,000 bytes per amp-script element that uses a local script via script[type=text/plain][target=amp-script].
  • Maximum total of 150,000 bytes for all amp-script elements on the page.

User gestures

In some cases, amp-script requires a user gesture to apply changes triggered by your JavaScript code (we call these "mutations") to the amp-script's DOM children. This helps avoid poor user experience from unexpected content jumping.

The rules for mutations are as follows:

  1. For amp-script elements with non-container layout, mutations are always allowed.
  2. For amp-script elements with container layout, mutations are allowed for five seconds following a user gesture. This five second window is extended once if a fetch() is triggered.

Creating AMP elements

With regard to dynamic creation of AMP elements (e.g. via document.createElement()), only amp-img and amp-layout are currently allowed. Please upvote or comment on #25344 with your use case.

Calculating the script hash

Since custom JS run in amp-script is not subject to normal Content Security Policy, you need to add this script hash:

  • for inline JavaScript
  • for JavaScript loaded from a cross-origin source

Include the script hash in a meta[name=amp-script-src] element in the document head. Here are a few ways to build the hash:

  • If you omit the <meta> tag, AMP will output a console error containing the expected hash string. You can copy this to create the appropriate <meta> tag.
  • The AMP Optimizer node module generates this hash and inserts the <meta> tag automatically.
  • Build it yourself, using the following steps:

  • Compute the SHA384 hash sum of the script's contents. This sum should be expressed in hexadecimal.

  • base64url-encode the result.
  • Prefix that with sha384-.

Here's how you calculate the hash in 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, '_')
  );
}

There is also a node module available which does it for you: @ampproject/toolbox-script-csp.

This example shows how to use the script hash in 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>

During development, you can disable the JavaScript size and script hash requirements by adding a data-ampdevmode attribute to either the amp-script element or the root html node. Adding this to the root html node will suppress all validation errors on the page. Adding it to the amp-script element will simply suppress errors about the size and the script hash.

Attributes

src

For executing remote scripts.

The URL of a JS file that will be executed in the context of this <amp-script>. The URL's protocol must be HTTPS and the HTTP response's Content-Type must be application/javascript or text/javascript.

script

For executing local scripts.

The id of a script[type=text/plain][target=amp-script] element whose text content contains JS that will be executed in the context of this <amp-script>.

sandbox (optional)

Applies extra restrictions to DOM that may be mutated by this <amp-script>. Similar to the iframe[sandbox] attribute, the value of the attribute can either be empty to apply all restrictions, or space-separated tokens to lift particular restrictions:

  • allow-forms: Allows form elements to be created and modified. AMP requires special handling to prevent unauthorized state changing requests from user input. See amp-form's security considerations for more detail.

max-age (optional, but required for signed exchanges if script is specified)

Requires the script attribute.

The max-age attribute specifies the maximum lifetime in seconds the local script is allowed to be served from the time of signed exchange (SXG) publishing. AMP Packager uses this value to compute the SXG expires time.

The value of max-age should be chosen carefully:

  • A longer max-age increases the potential security impact of a SXG downgrade.

  • A shorter max-age may prevent inclusion in AMP Caches that have a minimum SXG lifetime. For instance, the Google AMP Cache requires at least 4 days (345600 seconds). Note that there's currently no reason to select max-age longer than 7 days (604800 seconds), due to the maximum set by the SXG spec.

If you don't publish signed exchanges, max-age does nothing.

common attributes

This element includes common attributes extended to AMP components.

Errors

There are several types of runtime errors that may be encountered when using amp-script.

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

amp-script limits the size of the JS source that may be used. See Size of JavaScript code above.

"Script hash not found."

For local scripts and cross-origin scripts, you need to add a script hash for security.

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

To avoid unexpected content jumping, amp-script generally requires user gestures for DOM changes. See User gestures above.

Besoin d'une aide supplémentaire ?

Vous avez lu ce document une douzaine de fois mais il ne répond pas à toutes vos questions ? D'autres personnes ont peut-être eu le même sentiment. Contactez-les sur Stack Overflow.

Se rendre sur Stack Overflow
Vous avez trouvé un bug ou une fonctionnalité manquante ?

Le projet AMP encourage fortement votre participation et vos contributions ! Nous espérons que vous deviendrez un membre régulier de notre communauté open source, mais nous serons également ravis de recevoir des contributions ponctuelles concernant les questions qui vous intéressent particulièrement.

Se rendre sur GitHub