Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support media attribute for hero image preloading #805

Merged
merged 3 commits into from
May 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 31 additions & 30 deletions packages/optimizer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@

AMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:

* Server-side render AMP layouts.
* **Automatically import all missing AMP component scripts**.
* **Automatically add any missing mandatory AMP tags**.
* Remove the AMP boilerplate (when possible).
* Remove not needed whitespace.
* Extract and move CSS keyframe animations to the bottom of the page.
* Optimize AMP framework and custom font loading
* Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.
- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).
- **Automatically import all missing AMP component scripts**.
- **Automatically add any missing mandatory AMP tags**.
- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.
- Remove the AMP boilerplate (when possible).
- Remove not needed whitespace.
- Extract and move CSS keyframe animations to the bottom of the page.
- Optimize AMP framework and custom font loading
- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.

The performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).

**Good to know:**

* AMP Optimizer will produce valid AMP.
* AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.
- AMP Optimizer will produce valid AMP.
- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.

## Usage

Expand Down Expand Up @@ -50,15 +51,15 @@ You can find a sample implementation [here](/packages/optimizer/demo/simple). If

### Incomplete markup

It's possible to pass incomplete documents and AMP Optimizer will add any
missing tags and extension imports required by a valid AMP document.
It's possible to pass incomplete documents and AMP Optimizer will add any
missing tags and extension imports required by a valid AMP document.

```
const originalHtml = `
<h1>Hello World!</h1>
<amp-twitter width="375"
height="472"
layout="responsive"
<amp-twitter width="375"
height="472"
layout="responsive"
data-tweetid="1182321926473162752">
</amp-twitter>
`;
Expand Down Expand Up @@ -111,15 +112,15 @@ const ampOptimizer = AmpOptimizer.create({
const markdown = `
# Markdown 🤯

Here is an image declared in Markdown syntax:
Here is an image declared in Markdown syntax:

![A random image](https://unsplash.it/800/600).

You can directly declare AMP components:

<amp-twitter width="375"
height="472"
layout="responsive"
<amp-twitter width="375"
height="472"
layout="responsive"
data-tweetid="1182321926473162752">
</amp-twitter>

Expand Down Expand Up @@ -188,8 +189,8 @@ $ npx @ampproject/toolbox-cli optimize myFile.html

The biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:

* if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).
* if an AMP component uses the `media`, `sizes` or `heights` attribute ([documentation](https://amp.dev/documentation/guides-and-tutorials/learn/common_attributes/?format=websites#heights)). A simple workaround is to replace the `media`, `sizes` or `heights` attributes with normal CSS media queries.
- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).
- if an AMP component uses the `media`, `sizes` or `heights` attribute ([documentation](https://amp.dev/documentation/guides-and-tutorials/learn/common_attributes/?format=websites#heights)). A simple workaround is to replace the `media`, `sizes` or `heights` attributes with normal CSS media queries.

To find out, why the AMP boilerplate could not be removed, enable `verbose` mode:

Expand Down Expand Up @@ -293,10 +294,10 @@ Add placeholders for `amp-img` and `amp-video` posters. The placeholders are blu

This transformer supports the following options:

* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.
* `imageBasePath`: specifies a base path used to resolve an image during build.
* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.
* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached
- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.
- `imageBasePath`: specifies a base path used to resolve an image during build.
- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.
- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached
to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if
all placeholders should be cached (good for static sites). Defaults to 30.

Expand All @@ -316,6 +317,7 @@ const optimizer = AmpOptimizer.create({
It's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.

Example:

```
const ampOptimizer = require('@ampproject/toolbox-optimizer');

Expand Down Expand Up @@ -347,6 +349,7 @@ Ideally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched
where in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.

Example:

```
const ampOptimizer = require('@ampproject/toolbox-optimizer');

Expand Down Expand Up @@ -383,13 +386,11 @@ Transformer tests are located in:
```

The transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected
outcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing
a transformer implementation, run:
outcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing
a transformer implementation, run:

```
$ npm run test:optimizer:snapshot
```

to store a new snapshot version in `expected_output.html`.


to store a new snapshot version in `expected_output.html`.
15 changes: 15 additions & 0 deletions packages/optimizer/lib/AmpConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,25 @@
*/
'mode strict';

const {hasAttribute} = require('./NodeUtils');

module.exports = {
AMP_TAGS: ['amp', '⚡', '⚡4ads', 'amp4ads', '⚡4email', 'amp4email'],
AMP_CACHE_HOST: 'https://cdn.ampproject.org',
AMP_FORMATS: ['AMP', 'AMP4EMAIL', 'AMP4ADS'],
AMP_RUNTIME_CSS_PATH: '/v0.css',
appendRuntimeVersion: (prefix, version) => prefix + '/rtv/' + version,
isTemplate: (node) => {
if (node.tagName === 'template') {
return true;
}
if (
node.tagName === 'script' &&
hasAttribute(node, 'template') &&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason to check for its existence? if it doesn't, the next line would just be against undefined, right?

seems like it could be terse-d up into

return node.tagName === 'template' || node.tageName === 'script' && node.attribs.template === 'amp-mustache'

or even

return node.tagName === 'template' || node.attribs.template === 'amp-mustache'

no worries either way

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attribs might be undefined

node.attribs.template === 'amp-mustache'
) {
return true;
}
return false;
},
};
23 changes: 12 additions & 11 deletions packages/optimizer/lib/transformers/PreloadHeroImage.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
const {createElement, hasAttribute, insertAfter, firstChildByTag} = require('../NodeUtils');
const {findMetaViewport} = require('../HtmlDomHelper');
const {isValidImageSrcURL} = require('../URLUtils');
const {isTemplate} = require('../AmpConstants');
const parseSrcSet = require('../parseSrcSet');

// Images smaller than 150px are considered tiny
Expand Down Expand Up @@ -59,6 +60,9 @@ class PreloadHeroImage {
'as': 'image',
'data-hero': '',
});
if (heroImage.media) {
preload.attribs.media = heroImage.media;
}
if (heroImage.srcset) {
try {
parseSrcSet(heroImage.srcset);
Expand All @@ -79,7 +83,7 @@ class PreloadHeroImage {
return null;
}
// Ignore images inside templates
if (root.tagName === 'template') {
if (isTemplate(root)) {
return null;
}

Expand All @@ -96,6 +100,7 @@ class PreloadHeroImage {
return this.isCandidateIframePlaceholderImage(root);
}

let heroImage;
for (const child of root.children) {
const heroImage = this.findHeroImage(child);
if (heroImage) {
Expand All @@ -114,11 +119,11 @@ class PreloadHeroImage {
return null;
}

const {layout, width, height} = ampVideo.attribs;
const {layout, width, height, media} = ampVideo.attribs;
if (this.isTinyNode(layout, width, height)) {
return null;
}
return {src: poster, srcset: ''};
return {src: poster, media, srcset: ''};
}

isCandidateIframePlaceholderImage(ampIframe) {
Expand All @@ -127,7 +132,7 @@ class PreloadHeroImage {
return null;
}

const {layout, width, height} = ampIframe.attribs;
const {layout, width, height, media} = ampIframe.attribs;

if (this.isTinyNode(layout, width, height)) return null;

Expand All @@ -137,7 +142,7 @@ class PreloadHeroImage {
hasAttribute(child, 'placeholder') &&
isValidImageSrcURL(child.attribs.src)
) {
return {src: child.attribs.src, srcset: child.attribs.srcset || ''};
return {src: child.attribs.src, media, srcset: child.attribs.srcset || ''};
}
}
return null;
Expand All @@ -155,11 +160,7 @@ class PreloadHeroImage {
return null;
}

let width = ampImg.attribs.width;
let height = ampImg.attribs.height;
const srcset = ampImg.attribs.srcset;

const layout = ampImg.attribs.layout;
let {width, height, srcset, layout, media} = ampImg.attribs;

if (!width && !height) {
if (layout === 'fill') {
Expand All @@ -171,7 +172,7 @@ class PreloadHeroImage {
if (this.isTinyNode(layout, width, height)) {
return null;
}
return {src, srcset};
return {src, srcset, media};
}

// Any node with width or height less than 150 pixels and a non-responsive layout.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<html>
<head>
<script async src="https://cdn.ampproject.org/v0.js"></script>
<link rel="preload" href="http://example.com/foo.png" as="image" data-hero media="(max-width: 649px)">
</head>
<body>
<amp-iframe src="/test" layout="responsive" width="320" height="900" media="(max-width: 649px)">
<amp-img placeholder layout="fill" src="http://example.com/foo.png"></amp-img>
</amp-iframe>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<script async src="https://cdn.ampproject.org/v0.js"></script>
</head>
<body>
<amp-iframe src="/test" layout="responsive" width="320" height="900" media="(max-width: 649px)">
<amp-img placeholder layout="fill" src="http://example.com/foo.png"></amp-img>
</amp-iframe>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<html>
<head>
<script async src="https://cdn.ampproject.org/v0.js"></script>
<link rel="preload" href="/foo.png" as="image" data-hero media="(max-width: 649px)">
</head>
<body>
<amp-img width="500" height="400" src="/foo.png" media="(max-width: 649px)"></amp-img>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>
<head>
<script async src="https://cdn.ampproject.org/v0.js"></script>
</head>
<body>
<amp-img width="500" height="400" src="/foo.png" media="(max-width: 649px)"></amp-img>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<html>
<head>
<script async src="https://cdn.ampproject.org/v0.js"></script>
<link rel="preload" href="http://example.com/foo.png" as="image" data-hero media="(max-width: 649px)">
</head>
<body>
<amp-video-iframe src="/test" layout="responsive" width="320" height="900" media="(max-width: 649px)">
<amp-img placeholder layout="fill" src="http://example.com/foo.png"></amp-img>
</amp-video-iframe>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<script async src="https://cdn.ampproject.org/v0.js"></script>
</head>
<body>
<amp-video-iframe src="/test" layout="responsive" width="320" height="900" media="(max-width: 649px)">
<amp-img placeholder layout="fill" src="http://example.com/foo.png"></amp-img>
</amp-video-iframe>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<link rel="preload" href="https://example-com.cdn.ampproject.org/foo.png" as="image" data-hero media="(max-width: 649px)">
</head>
<body>
<amp-video poster="https://example-com.cdn.ampproject.org/foo.png" width="400" height="400" media="(max-width: 649px)">
<source src="foo.mp4">
</amp-video>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>
<head></head>
<body>
<amp-video poster="https://example-com.cdn.ampproject.org/foo.png" width="400" height="400" media="(max-width: 649px)">
<source src="foo.mp4" />
</amp-video>
</body>
</html>