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

Conversation

Projects
None yet
3 participants
@mgechev
Copy link
Member

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.

@mgechev mgechev force-pushed the minko/guess-aot branch from 76f7dec to 0886880 May 26, 2019

mgechev added some commits May 28, 2019

@wardpeet
Copy link
Contributor

left a comment

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') {

This comment has been minimized.

Copy link
@wardpeet

wardpeet May 29, 2019

Contributor

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);
}

This comment has been minimized.

Copy link
@mgechev

mgechev May 29, 2019

Author Member

The suggestion sounds good!

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

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

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

This comment has been minimized.

Copy link
@wardpeet

wardpeet May 29, 2019

Contributor

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

parentElement.appendChild(link);
};

const supportedPrefetchStrategy = support('prefetch')

This comment has been minimized.

Copy link
@wardpeet

wardpeet May 29, 2019

Contributor
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 = (

This comment has been minimized.

Copy link
@wardpeet

wardpeet May 29, 2019

Contributor

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

}
});
};

const support = (feature: string) => {

This comment has been minimized.

Copy link
@wardpeet

wardpeet May 29, 2019

Contributor

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

mgechev and others added some commits May 29, 2019

Update packages/guess-webpack/src/aot/guess-aot.ts
Co-Authored-By: Ward Peeters <ward@coding-tech.com>
Merge branch 'minko/guess-aot' of github.com:guess-js/guess into mink…
…o/guess-aot

* 'minko/guess-aot' of github.com:guess-js/guess:
  Update packages/guess-webpack/src/aot/guess-aot.ts

mgechev added some commits May 30, 2019

@mgechev mgechev merged commit 63f13a2 into master May 30, 2019

2 checks passed

Travis CI - Branch Build Passed
Details
Travis CI - Pull Request Build Passed
Details

@mgechev mgechev deleted the minko/guess-aot branch May 30, 2019

@innerop

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Member Author

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
You can’t perform that action at this time.