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

🚀 [Story performance] Experiment to load first page assets before loading other pages to improve LCP #34846

Merged
merged 29 commits into from
Jul 12, 2021
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1fca7ce
Added lcp, tests, etc
mszylkowski Jun 11, 2021
11c5ccb
Cleaned up
mszylkowski Jun 11, 2021
46ed6c0
Merge branch 'main' of github.com:ampproject/amphtml into px_firstpage
mszylkowski Jun 15, 2021
6d20a91
Added return
mszylkowski Jun 17, 2021
792ba51
Merge branch 'main' of github.com:ampproject/amphtml into px_firstpage
mszylkowski Jun 17, 2021
8595d55
Use webp
mszylkowski Jun 17, 2021
345f1bc
Fixed imports
mszylkowski Jun 17, 2021
4b397e3
Fixed page loading before distance set
mszylkowski Jun 17, 2021
b63c8a7
Fix isExperimentOn and overrideDistance
mszylkowski Jun 18, 2021
a6d2d9d
Removed log
mszylkowski Jun 18, 2021
080e7b0
Fixed setDistance on navigation
mszylkowski Jun 22, 2021
064bafd
Removed unused import / export
mszylkowski Jun 22, 2021
9a37526
Fix html checks
mszylkowski Jun 22, 2021
6e31458
Updated tests
mszylkowski Jun 23, 2021
ee43c1f
revert const to let
mszylkowski Jun 23, 2021
9c4602a
Update extensions/amp-story/1.0/amp-story.js
mszylkowski Jun 23, 2021
03d6e6c
Simplified preloadAll call
mszylkowski Jun 23, 2021
51bccf9
Removed comment
mszylkowski Jun 23, 2021
da3d7fc
Updated tests and removed getDistance
mszylkowski Jun 23, 2021
c87d3dc
Rely on store service
mszylkowski Jun 24, 2021
acbab68
Added csi experiment
mszylkowski Jun 24, 2021
57d67dc
Merge branch 'main' of github.com:ampproject/amphtml into px_firstpage
mszylkowski Jun 24, 2021
f3a9b94
Merge branch 'main' of github.com:ampproject/amphtml into px_firstpage
mszylkowski Jun 25, 2021
d261b82
Add comment and move experiment
mszylkowski Jul 1, 2021
ca67192
Split tests in compressed / uncompressed
mszylkowski Jul 2, 2021
3566ac6
Merge branch 'main' of github.com:ampproject/amphtml into px_firstpage
mszylkowski Jul 2, 2021
3de9ad7
Catch inside then
mszylkowski Jul 2, 2021
939f55e
Merge branch 'main' of github.com:ampproject/amphtml into px_firstpage
mszylkowski Jul 7, 2021
ba37419
Fixed test
mszylkowski Jul 7, 2021
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
105 changes: 105 additions & 0 deletions examples/amp-story/performance/lcp-compressed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<!-- Use #load=first to enable the experiment. -->
<!doctype html>
<html ⚡ lang="en">
<head>
<meta charset="utf-8">
<link rel="canonical" href="lcp-compressed.html">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<script async src="https://cdn.ampproject.org/v0.js"></script>
<title>LCP test</title>

<script async custom-element="amp-story" src="https://cdn.ampproject.org/v0/amp-story-1.0.js"></script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro&family=Source+Sans+Pro:wght@400;600&display=swap" rel="stylesheet">

<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<style amp-custom>
* {
box-sizing: border-box;
}
amp-story-page {
font-family: 'Source Sans Pro', sans-serif;
color: white;
background: black;
}
code {
font-family: 'Source Code Pro', monospace;
background: #fff2;
}
amp-story p {
line-height: 1.5;
}
.bold {
font-weight: bold;
}
.bottom {
align-content: end;
}
.last {
padding-bottom: 8.83vh;
}
.byline {
letter-spacing: 1.28px;
}
amp-img {
margin: 1em;
margin-bottom: 5em;
}
</style>
</head>
<body>
<script>
const loadFirst = location.hash.includes('load=first');
(self.AMP = self.AMP || []).push(function(AMP) {
AMP.toggleExperiment('amp-story-load-first-page-only', loadFirst);
});
</script>
mszylkowski marked this conversation as resolved.
Show resolved Hide resolved
<amp-story standalone id="cats"
title="LCP test" publisher="The AMP team"
publisher-logo-src="./img/amplogo.png"
poster-portrait-src="./img/overview.jpg">

<amp-story-page id="page-1" title="Lorem ipsum dolor sit amet">
<amp-story-grid-layer template="fill">
<amp-img
id="img1"
width="400"
height="750"
src="https://images.unsplash.com/photo-1515705576963-95cad62945b6?w=1750&q=30&fm=webp"
layout="fill">
</amp-img>
</amp-story-grid-layer>
<amp-story-grid-layer template="vertical" class="bottom">
<p class="bold">LCP for images (compressed)</p>
<p class="byline">Add <code>#load=first</code> to activate experiment</p>
</amp-story-grid-layer>
</amp-story-page>

<amp-story-page id="page-2">
<amp-story-grid-layer template="fill">
<amp-img
id="img2"
width="400"
height="750"
src="https://images.unsplash.com/photo-1502318217862-aa4e294ba657?w=1080&q=30&fm=webp"
layout="fill">
</amp-img>
</amp-story-grid-layer>
</amp-story-page>

<amp-story-page id="page-3">
<amp-story-grid-layer template="fill">
<amp-img
id="img3"
width="400"
height="750"
src="https://images.unsplash.com/photo-1513809491260-0e192158ae44?w=1080&q=30&fm=webp"
layout="fill">
</amp-img>
</amp-story-grid-layer>
</amp-story-page>
</amp-story>
</body>
</html>
105 changes: 105 additions & 0 deletions examples/amp-story/performance/lcp.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<!-- Use #load=first to enable the experiment. -->
<!doctype html>
<html ⚡ lang="en">
<head>
<meta charset="utf-8">
<link rel="canonical" href="lcp.html">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<script async src="https://cdn.ampproject.org/v0.js"></script>
<title>LCP test</title>

<script async custom-element="amp-story" src="https://cdn.ampproject.org/v0/amp-story-1.0.js"></script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro&family=Source+Sans+Pro:wght@400;600&display=swap" rel="stylesheet">

<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<style amp-custom>
* {
box-sizing: border-box;
}
amp-story-page {
font-family: 'Source Sans Pro', sans-serif;
color: white;
background: black;
}
code {
font-family: 'Source Code Pro', monospace;
background: #fff2;
}
amp-story p {
line-height: 1.5;
}
.bold {
font-weight: bold;
}
.bottom {
align-content: end;
}
.last {
padding-bottom: 8.83vh;
}
.byline {
letter-spacing: 1.28px;
}
amp-img {
margin: 1em;
margin-bottom: 5em;
}
</style>
</head>
<body>
<script>
const loadFirst = location.hash.includes('load=first');
(self.AMP = self.AMP || []).push(function(AMP) {
AMP.toggleExperiment('amp-story-load-first-page-only', loadFirst);
});
</script>
<amp-story standalone id="cats"
title="LCP test" publisher="The AMP team"
publisher-logo-src="./img/amplogo.png"
poster-portrait-src="./img/overview.jpg">

<amp-story-page id="page-1" title="Lorem ipsum dolor sit amet">
<amp-story-grid-layer template="fill">
<amp-img
id="img1"
width="400"
height="750"
src="https://images.unsplash.com/photo-1515705576963-95cad62945b6?w=1750&q=100&fm=webp"
layout="fill">
</amp-img>
</amp-story-grid-layer>
<amp-story-grid-layer template="vertical" class="bottom">
<p class="bold">LCP for images (uncompressed)</p>
<p class="byline">Add <code>#load=first</code> to activate experiment</p>
</amp-story-grid-layer>
</amp-story-page>

<amp-story-page id="page-2">
<amp-story-grid-layer template="fill">
<amp-img
id="img2"
width="400"
height="750"
src="https://images.unsplash.com/photo-1502318217862-aa4e294ba657?w=1080&q=100&fm=webp"
layout="fill">
</amp-img>
</amp-story-grid-layer>
</amp-story-page>

<amp-story-page id="page-3">
<amp-story-grid-layer template="fill">
<amp-img
id="img3"
width="400"
height="750"
src="https://images.unsplash.com/photo-1513809491260-0e192158ae44?w=1080&q=100&fm=webp"
layout="fill">
</amp-img>
</amp-story-grid-layer>
</amp-story-page>
</amp-story>
</body>
</html>
3 changes: 3 additions & 0 deletions extensions/amp-story/1.0/amp-story-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,9 @@ export class AmpStoryPage extends AMP.BaseElement {
if (this.isAd()) {
distance = Math.min(distance, 2);
}
if (distance == this.getDistance()) {
jridgewell marked this conversation as resolved.
Show resolved Hide resolved
return;
}

this.element.setAttribute('distance', distance);
this.element.setAttribute('aria-hidden', distance != 0);
Expand Down
36 changes: 32 additions & 4 deletions extensions/amp-story/1.0/amp-story.js
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,11 @@ export class AmpStory extends AMP.BaseElement {
);
});
}
if (isExperimentOn(this.win, 'amp-story-load-first-page-only')) {
Services.performanceFor(this.win).addEnabledExperiment(
'amp-story-load-first-page-only'
);
}

if (this.maybeLoadStoryDevTools_()) {
return;
Expand Down Expand Up @@ -1532,7 +1537,7 @@ export class AmpStory extends AMP.BaseElement {
// the navigation happened, like preloading the following pages, or
// sending analytics events.
() => {
this.preloadPagesByDistance_();
this.preloadPagesByDistance_(/* prioritizeActivePage */ !oldPage);
this.triggerActiveEventForPage_();

this.systemLayer_.resetDeveloperLogs();
Expand Down Expand Up @@ -2225,8 +2230,11 @@ export class AmpStory extends AMP.BaseElement {
return map;
}

/** @private */
preloadPagesByDistance_() {
/**
* @param {boolean=} prioritizeActivePage
* @private
*/
preloadPagesByDistance_(prioritizeActivePage = false) {
if (this.platform_.isBot()) {
this.pages_.forEach((page) => {
page.setDistance(0);
Expand All @@ -2236,13 +2244,33 @@ export class AmpStory extends AMP.BaseElement {

const pagesByDistance = this.getPagesByDistance_();

this.mutateElement(() => {
const preloadAllPages = () => {
pagesByDistance.forEach((pageIds, distance) => {
pageIds.forEach((pageId) => {
const page = this.getPageById(pageId);
page.setDistance(distance);
});
});
};

this.mutateElement(() => {
if (
!isExperimentOn(this.win, 'amp-story-load-first-page-only') ||
mszylkowski marked this conversation as resolved.
Show resolved Hide resolved
!prioritizeActivePage
) {
return preloadAllPages();
}

const activePageId = devAssert(pagesByDistance[0][0]);
new Promise((res, rej) => {
mszylkowski marked this conversation as resolved.
Show resolved Hide resolved
const page = this.getPageById(activePageId);
page.setDistance(0);
page.signals().whenSignal(CommonSignals.LOAD_END).then(res);
// Don't call preload if user navigates before page loads, since the navigation will call preload properly.
this.storeService_.subscribe(StateProperty.CURRENT_PAGE_ID, rej);
jridgewell marked this conversation as resolved.
Show resolved Hide resolved
})
.then(() => preloadAllPages())
mszylkowski marked this conversation as resolved.
Show resolved Hide resolved
.catch();
mszylkowski marked this conversation as resolved.
Show resolved Hide resolved
});
}

Expand Down
61 changes: 61 additions & 0 deletions extensions/amp-story/1.0/test/test-amp-story.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ import {Keys} from '#core/constants/key-codes';
import {LocalizationService} from '#service/localization';
import {MediaType} from '../media-pool';
import {PageState} from '../amp-story-page';
import {Performance} from '#service/performance-impl';
import {Services} from '#service';
import {Signals} from '#core/data-structures/signals';
import {VisibilityState} from '#core/constants/visibility-state';
import {createElementWithAttributes} from '#core/dom';
import {registerServiceBuilder} from '../../../../src/service-helpers';
Expand All @@ -58,6 +60,8 @@ describes.realWin(
let replaceStateStub;
let win;

const nextTick = () => new Promise((resolve) => win.setTimeout(resolve, 0));

/**
* @param {number} count
* @param {Array<string>=} ids
Expand Down Expand Up @@ -1837,5 +1841,62 @@ describes.realWin(
).to.be.false;
});
});

describe('experiment for amp-story-load-first-page-only', () => {
let pages;
let performanceImpl;
beforeEach(async () => {
toggleExperiment(win, 'amp-story-load-first-page-only', true);
pages = await createStoryWithPages(2, ['page-1', 'page-2'], false);
performanceImpl = new Performance(env.win);
env.sandbox.stub(story, 'mutateElement').callsFake((fn) => fn());
env.sandbox.stub(Services, 'performanceFor').returns(performanceImpl);
});

it('should position the active page so it preloads', async () => {
story.buildCallback();
await story.layoutCallback();

// Check page 0 is loaded with distance 0.
expect(pages[0].getAttribute('distance')).to.be.equal('0');
});

it('should not position the inactive page so it preloads before the active page is loaded', async () => {
const signals = new Signals();
pages[0].signals = () => signals;
story.buildCallback();
await story.layoutCallback();

// Check page 1 is not loaded.
expect(pages[1].hasAttribute('distance')).to.be.false;
});

it('should position the inactive page so it preloads after the active page is loaded', async () => {
const signals = new Signals();
pages[0].signals = () => signals;
story.buildCallback();
await story.layoutCallback();

// Check page 1 is not loaded.
expect(pages[1].hasAttribute('distance')).to.be.false;

signals.signal(CommonSignals.LOAD_END);
await nextTick();

// Check page 1 is loaded with distance 1.
expect(pages[1].getAttribute('distance')).to.be.equal('1');
});

it('should enable the CSI experiment', async () => {
const enableSpy = env.sandbox.spy(
performanceImpl,
'addEnabledExperiment'
);
story.buildCallback();
await story.layoutCallback();

expect(enableSpy).to.be.calledWith('amp-story-load-first-page-only');
});
});
mszylkowski marked this conversation as resolved.
Show resolved Hide resolved
}
);