Important: this documentation is not applicable to your currently selected format stories!
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>
Supported Layouts
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.
Virtual DOM
Your JavaScript can access the area of the page wrapped within the <amp-script> component. amp-script copies the component's children to a virtual DOM. Your code can access that virtual DOM as document.body.
For example, this <amp-script> component defines a DOM consisting of a single <p>.
<amp-script src="http://example.com/my-script.js" width="300" height="100"> <p>A single line of text</p> </amp-script>
If your code appends an element to document.body:
// my-script.js const p = document.createElement('p'); p.textContent = 'A second line of text'; document.body.appendChild(p);
the new element will be placed after that <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>
Loading JavaScript
An amp-script element can load JavaScript in two ways:
- remotely, from a URL
- locally, from a
<script>element on the page
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>
From a local element
You can also include your JavaScript inline, in a script tag. You must:
- Set the
scriptattribute of youramp-scriptto the localscriptelement'sid. - Include
target="amp-script"in youramp-script. - Include
type="text/plain"in yourscript. 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>
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.
If your page contains multiple amp-script elements, each requiring a script hash, include each as a whitespace-delimited list in a single <meta name="amp-script-src" content="..."> tag (see examples/amp-script/example.amp.html for an example with multiple script hashes).
How does it work?
amp-script runs your custom JavaScript in a Web Worker. Normally Web Workers don't have access to the DOM. But amp-script gives your code access to a virtual DOM. When your JavaScript modifies this virtual DOM, amp-script updates the real DOM.
Under the hood, amp-script uses @ampproject/worker-dom. For design details, see the "Intent to Implement" issue.
Capabilities
Supported APIs
DOM elements and their properties are generally supported, with a few limits. For example, your code can't add a new <script> or <style> tag to the DOM.
amp-script recreates many commonly used DOM APIs and makes them available to your code. This "hello world" example uses getElementById(), addEventListener(), createElement(), textContent, and appendChild():
const button = document.getElementById('textarea'); button.addEventListener('click', () => { const h1 = document.createElement('h1'); h1.textContent = 'Hello World!'; document.body.appendChild(h1); });
Supported DOM APIs include:
- Element getters like
getElementByName(),getElementsByClassName(),getElementsByTagName(),childNodes(),parentNode(), andlastChild() - Mutators like
createTextNode(),appendChild(),insertBefore(),removeChild(), andreplaceChild() - Methods involving events like
addEventListener(),removeEventListener(), andcreateEvent() - Property and attribute getters like
getAttribute(),hasAttribute() - Event properties like
Event.target,Event.type, andEvent.bubbles - Element properties like
attributes,id,outerHTML,textContent,value,classList, andclassName - And many more.
For a complete list of supported DOM APIs, see the API compatibility table.
querySelector() is supported for simple selectors - element, id, class, and attribute. So, document.querySelector('.class') will work, but document.querySelector('.class1 .class2') will not. See the code for details.
amp-script supports common Web APIs like Fetch, WebSockets, localStorage, sessionStorage, and Canvas. Presently, the History API is not implemented, and neither are cookies.
amp-script does not support the entire DOM API or Web API, as this would make amp-script's own JavaScript too large and slow. If there's an API you'd like to see supported, please file an issue or suggest and contribute the change yourself.
amp-script in use, see here. Frameworks and libraries
Presently, libraries like jQuery will not work with amp-script without modification, as they use unsupported DOM APIs. However, @ampproject/worker-dom was designed to support the APIs that popular JavaScript frameworks use. amp-script has been tested with React and Preact. To keep bundle sizes small, we recommend using Preact. Other frameworks may work but have not been thoroughly tested; if you're looking for support, please file an issue or contribute here.
Creating AMP elements
You can use amp-script to add an amp-img or amp-layout component to the DOM. Other AMP components are presently unsupported. If you need to create a different AMP element, please upvote on #25344 and add a comment describing your use case.
Referencing amp-state
amp-script supports getting and setting amp-state JSON. This lets amp-script interact with other AMP elements on the page via amp-bind bindings.
If you invoke AMP.setState() from amp-script after a user gesture, bindings can cause mutations to the DOM. Otherwise, state will be set, but binding will not occur (similar to amp-state initialization). For more information, see the section on user gestures.
AMP.setState() works as described in the <amp-bind> documentation, merging an object literal into the specified state. This example shows how it can affect the 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() is asynchronous and returns a Promise. The promise resolves with the stringified value of the state variable it's passed. These examples demonstrate its use with different types:
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')); }
Here's another example. amp-state doesn't support WebSocket URLs in its src attribute, but we can use amp-script to pass in data from a 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(), you must include the amp-bind extension script in the document head. Note that if something else changes the DOM inside your <amp-script>, that change will not propagate to the virtual DOM. The syncing process is unidirectional. Thus it's best to avoid code like the following:
<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>
Retrieving data for <amp-list>
You may export a function to act as the data source for an <amp-list>.
The exported function must return either JSON, or a Promise that resolves with JSON.
The export API is available on the global scope and has the following signature:
/** * @param {string} name the name to identify the function by. * @param {Function} function the function to export. */ function exportFunction(name, function) {}
Restrictions
In order to maintain AMP's guarantees of performance and layout stability, amp-script imposes some restrictions.
Size of JavaScript code
amp-script has standards for the size of code:
- Each inline script can contain up to 10,000 bytes
- The scripts on a page can contain a total of up to 150,000 bytes
- The scripts running in sandboxed mode on a page can contain a total of up to 300,000 bytes
User gestures
In some cases, amp-script will not apply DOM changes triggered by your JavaScript code unless the code was triggered by a user gesture - say, a button tap. This helps keep content layout shift from causing a poor user experience.
The rules are less restrictive for amp-script containers whose size cannot change. This is the case when the layout is not container and when the dimensions are specified in HTML. Let's call such containers "fixed-size". Containers whose size can change, we'll call "variable-sized".
Here are some examples of fixed-size containers:
<amp-script layout="fill" height="300" width