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

Introduce AoT predictive prefetching #94

Merged
merged 30 commits into from May 30, 2019
Merged

Introduce AoT predictive prefetching #94

merged 30 commits into from May 30, 2019

Conversation

mgechev
Copy link
Member

@mgechev mgechev commented May 26, 2019

This pull request introduces Ahead-of-Time predictive prefetching.

AoT vs. JiT prefetching

The difference is between the time when the prediction instruction is generated. Currently, Guess.js uses JiT predictive prefetching. Based on the current page the user is at, Guess.js queries the model and generate prefetching instructions for the JavaScript bundles associated with the pages that are likely to be visited next.

With AoT predictive prefetching, we query the model at build time. This has several benefits.

Advantages of the AoT model

Instead of shipping the entire model to the browser, now we just need to provide a prefetch function, which takes as arguments a chunk (filename) and a probability for this chunk to be needed. The function prefetches the bundle if its chance to be needed is sufficient given the user's connection speed.

The prefetch function is part of the main application bundle. Each lazy-loaded chunk has a few instructions which trigger the prefetching mechanism for its neighbors.

Example

// main.js
__GUESS__.p = () => /* prefetching logic */;

// a.js
__GUESS__.p(['b.js', 0.1], ['c.js', 0.9]);
/* rest of the a.js chunk */

// b.js
__GUESS__.p(['a.js', 0.1], ['c.js', 0.9]);
/* rest of the b.js chunk */

// c.js
__GUESS__.p(['a.js', 1]);
/* rest of the c.js chunk */

In the example above, when the user visits /a, they'll download main.js together with a.js. a.js will trigger prefetching for b.js and c.js. If the user's connection speed is sufficient, __GUESS__.p will prefetch the chunks; otherwise, it'll ignore the instructions.

Copy link
Contributor

@wardpeet wardpeet left a comment

Choose a reason for hiding this comment

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

Testing it on gatsby right now. I added a few bikeshed comments.

type ConnectionEffectiveType = '4g' | '3g' | '2g' | 'slow-2g';

const support = (feature: string) => {
if (typeof document === 'undefined') {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is copied from https://github.com/GoogleChromeLabs/quicklink/blob/master/src/prefetch.mjs

Unsure if it really matters but it's a few bytes smaller. We can also drop the document check as we do it inside supportedPrefetchStrategy

const support = (feature: string) => {
  const link = document.createElement('link');
  return link.relList && link.relList.supports && link.relList.supports(feature);
}

Copy link
Member Author

Choose a reason for hiding this comment

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

The suggestion sounds good!

I believe the code here is the original source @addyosmani shared before I/O last year.

packages/guess-webpack/src/aot/guess-aot.ts Outdated Show resolved Hide resolved
};

const linkPrefetchStrategy = (url: string) => {
if (typeof document === 'undefined') {
Copy link
Contributor

Choose a reason for hiding this comment

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

you can drop it this as we do it in the supportedPrefetchStrategy

parentElement.appendChild(link);
};

const supportedPrefetchStrategy = support('prefetch')
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const supportedPrefetchStrategy = support('prefetch')
const supportedPrefetchStrategy = typeof document !== 'undefined' && support('prefetch')

@@ -114,9 +115,9 @@ const getEffectiveType = (global: any): ConnectionEffectiveType => {

export const initialize = (
Copy link
Contributor

Choose a reason for hiding this comment

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

would it be better to move to an object as parameters so ordering doesn't matter?

}
});
};

const support = (feature: string) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

should we copy the same changes from the aot runtime here or move the support function into a util file.

@mgechev mgechev merged commit 63f13a2 into master May 30, 2019
@mgechev mgechev deleted the minko/guess-aot branch May 30, 2019 11:27
@innerop
Copy link

innerop commented Jun 3, 2019

@mgechev do you happen have a small but fully functioning AoT example I can use as PoC to get buy-in from others on my team? I'm assuming there is a training step (data from Google Analytics maybe?) and during build time the prefetches are determined based on the trained model. I assume the model can be trained continually? In other words, as user behavior changes/evolves and we have a larger dataset, we can retrain it or resume its training and get the updated prefetch predictions.

Or maybe I have a purely imaginary sense of how this works?

@mgechev
Copy link
Member Author

mgechev commented Jun 4, 2019

Currently, Guess.js uses a simple probability matrix (Markov chain). Everything is static. When users' behavior change, you must rebuilt your application. Here's an example. Find routes.json, which is used instead of GA data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants