googlechromelabs / adaptive-loading Goto Github PK
View Code? Open in Web Editor NEWDemos for Adaptive Loading - differentially deliver fast, lighter experiences for users on slow networks & devices
License: Apache License 2.0
Demos for Adaptive Loading - differentially deliver fast, lighter experiences for users on slow networks & devices
License: Apache License 2.0
Some sites are experimenting with fetching more (or less) results from an API depending on the quality of the user's network connection.
It would be great if we had a demo highlighting how to do this with React. Perhaps create-react-app fetching search results from a server?. e.g
4G: 50 results
3G: 15 results
2G: 5 results
URL: https://anton-karlovskiy-adaptive-loading-microsite.glitch.me/demos
The current demos page is great, but we can probably make a few tweaks to improve it. For example, right now a developer can click one of the demos to view it live but they cannot jump directly into the source for the demo. We can allow this by adding two lines below each demo thumbnail:
"Live Demo"
"Source"
and link these up appropriately.
eBay are experimenting with adaptive loading where, similar to some of our demos, they only load the code necessary for zooming into an image if a user is on a fast connection. There is a screenshot of what this looks like below.
I would like for us to take this page and re-implement a simplified version of it. e.g maybe we just implement what is in the viewport...but with better stars :)
This would use the network-aware code-splitting pattern to decide whether or not to load the ability to zoom in on a product image. As this is a demo, I would like to see if we can use a heavier zooming component to stress just how much could be theoretically saved.
eBay's zooming looks like this on desktop:
I would recommend trying eBay's page out on mobile and desktop (emulated) so you see how they currently function.
References:
https://adaptive-loading.web.app/cra-network-aware-code-splitting/
https://adaptive-loading.web.app/react-shrine-network-aware-code-splitting/
Description:
There's a neat YouTube demo implemented using React which allows you to see the homepage of videos, the watch page and search page.
I would like for us to make it a little smarter by:
πΉ [P0] Video watch page: instead of loading the full embed-video component, we: check if you are on a slow network OR a low-memory device OR have low hardware concurrency and serve down a lighter custom element for the player instead (we may need to wrap it a little for use in React). We can use a similar pattern to network aware code-splitting, but add checks for memory and concurrency in the same boolean check within React.lazy()
.
π [P0] Routing: Fix routing so that the links to https://marvtron.github.io/watch?v=GKXS_YA9s7E (etc) take you to https://marvtron.github.io/youtube-react/watch?v=GKXS_YA9s7E. Accessing https://marvtron.github.io/youtube-react/watch?v=GKXS_YA9s7E directly should load the video watch page.
π [P1] On slow network or low-memory or low hardware-concurrency, don't load the comments widget and limit (by half) the number of results returned for the recommended videos:
π§ [P0] I would like us to add the following code to the index so we can quickly show on the watch page which components are important vs not.
<script>
window.debug = () => {
document.querySelector('.video-container').style.border = '15px solid green';
document.querySelector('.related-videos').style.border = '15px solid red';
document.querySelector("#root > div > div.watch-grid > div:nth-child(5)").style.border = '15px solid red';
};
</script>
π¨[P2] Take a quick look at whether render-blocking CSS can be optimized (e.g do we need all of semantic-ui?). I was reading through https://marvtron.github.io/youtube-react/static/css/2.20edb33d.chunk.css and at the very least, I think we don't need to load in the import for the Lato font. YouTube just uses Roboto if installed and I'm cool with us falling back to Arial without loading a Web Font (font-family: roboto, arial, sans-serif;
)
I would say this is probably higher priority than ##63 for this week but could be looked at after #86 (#86, #87, #83, then #63).
I believe our Twitter Data Saver demo also included support for embedded videos in Tweets: https://github.com/GoogleChromeLabs/adaptive-loading/blob/master/react-twitter-save-data-loading(hook)/src/components/Tweet/Video.js
If we have time, I would like for us to enhance the current Hooks demo so that the 4th sample tweet is https://twitter.com/mrdoob/status/1131817655134896128. This would allow us to demonstrate what a LQIP/blurred image for a video entry would look like (just an image) when data saver is on and we can then load the video on tap the way Twitter's current experience works.
The video for this tweet is attached π¦
URL: https://anton-karlovskiy-adaptive-loading-microsite.glitch.me/resources
Can we take the existing browser compatibility and references sections from https://github.com/GoogleChromeLabs/react-adaptive-hooks/blob/master/README.md and incorporate them into the resources section?
We can also link up to the recent write-up from Instagram about how they are using adaptive loading: https://medium.com/@roderickhsiao/sophisticated-adaptive-loading-strategies-7118341fcf91
@addyosmani
I think device-class aware loading
should be renamed to device-class aware code-splitting
.
I think we might need to rename like resource loading
, code-splitting
, data-fetching
. That is, just loading
β resource loading
since I think it sounds like loading covers more range including resource loading
.
cra-memory-considerate-loading
-> cra-memory-considerate-code-splitting
or cra-memory-considerate-resource-loading
because both mode are used here.
FYI: I found this issue while writing Recipes
section in hooks/README.
Above hooks cause re-rendering by useState inside useEffect for no good reason.
So I think we should:
Facebook currently use adaptive loading patterns to conditionally turn on or off animations, in particular for devices that are considered low-end. It would be useful for us to build a proof-of-concept of this for developers.
The demo should treat animation as a progressive enhancement. The core user-experience can fully function without animations applied, but on a higher-end device we will switch them on. Signals we may choose to use here can start off with memory and hardware concurrency. We may explore adding other signals in the future, but given we have Hooks authored for a good number of them, this shouldn't take too much additional work.
When we build out the demo, we should implement a simple Animation on/off toggle in order to give developers a convenient way to 'emulate' what the low-end experience would be. DevTools does not currently support emulating low-memory situations, so to avoid developers having to test on a real-phone, this seems like a reasonable debugging capability to add.
The animations we choose to show off should be obvious to the end user. Something simple and subtle may not be quite as impactful at showing how much less work this pattern enables, so we should select an animation that's just a little more advanced.
Looked through a few different examples. Two that I'm thinking of us trying out: https://next-motion.heruc.now.sh/ This is built using Next.js and has a tutorial for how it was built https://reacttricks.com/animating-next-page-transitions-with-framer-motion/. I'm wondering if using our Hooks and an HOC would give us the ability to turn off the animations when changing routes. It feels like it could be a little more real-world.
We did previously attempt to implement a version of this pattern, but decided the early demo we selected had an animation style that was far too subtle for developers to notice. We think the above might be a better fit.
A new day and a new demo π :)
Demo type: Network Status (ECT)
Toggle: Yes
Toggle label: "Slow network on/off"
Default state: Toggle off
Libraries involved: Lottie Web https://airbnb.io/lottie/
Lottie is a library for Android, iOS, Web, and Windows that parses Adobe After Effects animations exported as json with Bodymovin and renders them natively on mobile and on the web
An advantage of using Lottie is saving space. The fact that JSON format uses much less space than PNG sequences has a direct impact on the app download and installation time.
While Lottie is great for desktop and high-end devices, it can have a large JSON payload size. Here's an example where the animation is 265KB of JSON and the Lottie library itself adds another 62KB:
Let's create a demo using https://codepen.io/airnan/full/MPmQQB as a base where we by default we show a screenshot of the animation (first frame). When the toggle is set to on, we will load Lottie and the animation JSON for a nicer experience.
Lottie Web itself and the JSON should only be dynamically loaded as needed, we should avoid including them in the base bundle if you're only loading the image.
Credits:
We should add a link to "Markus Magnusson's Halloween Smashdown" that links to the original CodePen and include the author's name for credit.
Goal
Combine the Network Information API and dynamic imports in order to dynamically load a lightweight or heavy version of a component based on the user's effective connection type.
Background
The Network Information API summarizes the performance of a users network connection. This allows us to customize how we deliver experiences based on how slow or fast a connection is.
One of the signals NetInfo provides us is ECT - the Effective Connection Type. It doesn't inform if you are connected to WiFi or are on a cellular connection - it's more useful. ECT analyzes the latency of the current connection and determines which network profile it resembles the most. If you are on slow coffeeshop WiFi but your effective speed is 2G, ECT will report this as the best approximation of your effective connection speed. Valid values for ECT are 4g, 3g, 2g, and slow-2g. I typically bucket these into fast (4G), medium (3G) and slow (2G, slow-2G).
One could serve a light component on 2G, slow-2G, medium on 3G and a heavy component on 4G. Loosely, this should work:
import React, { Suspense } from 'react';
import './App.css';
const Sample = React.lazy(() => {
return new Promise(resolve => {
navigator.connection ? resolve(navigator.connection.effectiveType) : resolve(null)
}).then((effectiveType) => {
switch (effectiveType) {
case "4g":
return import(/* webpackChunkName: "heavy" */ "./Heavy.js");
break;
case "3g":
return import(/* webpackChunkName: "medium" */ "./Medium.js");
break;
case "2g":
return import(/* webpackChunkName: "light" */ "./Light.js");
break;
default:
return import(/* webpackChunkName: "medium" */ "./Medium.js")
}
});
});
function App() {
return (
<div className="App">
<header className="App-header">
<Suspense fallback={<div>Loading...</div>}>
<Sample />
</Suspense>
</header>
</div>
);
}
export default App;
Sample
We should demonstrate how the above pattern can be used with create-react-app in order to implement network-aware code-splitting.
hardwareConcurrency is read-only property returning the number of logical processors available to run threads on the user's computer. We currently do not have a React hook authored for HC.
This should be relatively straight-forward to author however.
This is a follow-up to #38, which explores using Client Hints for adaptive code loading. Rather than just using CH for deciding whether low or high quality resources should be sent down to the client, we will use them to decide what JavaScript bundles should be served.
The idea we want to enable is the following:
What will differ between this example and others is that we really want to stress the difference possible between a lightweight version vs. heavy. A heavy version could include more than one component pulling in a number of different JavaScript dependencies.
Let's brainstorm what the best demo for this should be.
URL: https://anton-karlovskiy-adaptive-loading-microsite.glitch.me/react-hooks
We should update this page to include most of the README content from the upstream react-adaptive-hooks project. A developer should be able to land on this page, see how to install the hooks and be able to read about the patterns that are available. The existing README content can mostly be copy/pasted. We should not paste in the links to demos as the demos page handles this well already.
Actual changes we might want to make: adding performant syntax-highlighting so that it's easy to read the code-snippets.
Our testing should cover:
We've currently got a CRA-based network-aware loading example in master. It would be great to land a more complete app using this approach (React Movie) as another example under react-movie-network-aware-components
.
Similar to before, this version will use the Network Information API to determine whether to load a low-fidelity or high-fidelity image based on movie posters. Given that our version of React Movie is a rewrite of a fork, we should be respectful of the license and make sure our README links back to the original while keeping our README format consistent for subdirectories.
Assigning to Anton.
2 Demos:
Reference:
https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/save-data/
Demo outline:
Rather than re-using one of the existing demos, I would like us to try replicating something that already exists for this one. How about we re-create the Twitter Timeline (with fake data, e.g content from Unsplash) using react-window and duplicate the Data Saver effect Twitter uses for their official feature:
https://www.wikihow.com/Turn-on-the-Data-Saver-on-Twitter
In terms of implementation:
Keep in mind, for now, we will only be focusing on adaptive resource-loading, but not adaptive code loading (just trying to switch how images are loaded for this demo).
We have been landing quite a few new Node.js based demos lately and have not been updating the README to index them in a single place (so folks know where to go check out the live demo links). Let's update the main project README to account for them.
In both the current CRA demos, we reference static URLs from when the demos were on Glitch. We probably want to update these to point to local URLs instead. The assets are already there.
The Media Capabilities API allows websites to get more information about the decoding abilities of the device/system/browser. This enables web developers to make optimal decisions when selecting media streams for the user.
Would this be of interest in this stage of the project?
Our React Hooks currently return a number of different kinds of values:
string
of 2g/3g/4g etc.integer
for the number of coresboolean
of whether you are overloadedfloat
value . (e.g 0.75)string
for light/heavyOf these values, memory stands out as a hook where we (project authors) are making a decision about what is overloaded. For all the other hooks, we just give developers the value and let them make a decision themselves.
For consistency, we should rewrite the memory hook to just provide a value from navigator.deviceMemory
and optionally provide access to performance memory data, but assume the deviceMemory value is the main thing developers are after here.
Theme inspiration: https://material.io/design/
Current icon (need to figure out what we're actually going to use):
https://www.iconfinder.com/icons/4272271/analytics_comparison_data_icon
Wdyt?
I think it would be useful for us to implement a slimmed down demo of a page really using <model-viewer>
and show how we can fallback to an image if you are on a memory-constrained device π
Dixie Mech is one such page and I think is simple enough that coming up with a reduced demo would not take us too long.
Original: https://dixiemech.com/gmkdracula (we should link to the original lower down the page)
Demo to evolve: https://adaptive-loading.web.app/cra-memory-considerate-loading/ (we can keep this one and treat Dixie as another memory-considerate demo)
Notes: To view the model you need to wait a second for the animation then click on it to see the model-viewer. I'd say for a slimmed down demo, we could just show the model-viewer on page load without the animations. Focus on implementing just the top part of the page that's in view?
3D <model-viewer>
code (for licensing reasons, let's just use the below and not self-host):
<model-viewer src="https://ephektz.com/assets/dboard.glb" background-color="#2b2b2b" exposure="0.3" id="boardEl" ar="" camera-orbit="0deg 0deg auto" style="position: absolute; width: 100%; height: 80%; top: 10%; opacity: 1;"></model-viewer>
Static screenshot for fallback:
I will also try reaching out to see if they are okay with this usage.
A new task high up in priority is a PR that updates the adaptive-loading demos to use the new package of hooks from npm (where possible)
HI folks,
This is a great project. I'm starting to get into the details of this solution, as companies are starting to ask for it.
Reading the docs, it seems like you are using the UA string to obtain some device info. Now that there's an ongoing proposal to deprecate and freeze the UA, are you thinking on alternative ways of obtaining this info?
Seems like the most obvious one would be using client-hints (which you have already discussed here). The current limitation being that you can't get these signals in the first page load (although that might change).
I'm wondering if you guys are contemplating this scenario, and have any thoughts or recommendations around it.
We currently have a hook for window.performance.memory but do not leverage navigator.deviceMemory.
Device Memory is a read-only property of the navigator interface returns the approximate amount of device memory in gigabytes. window.performance.memory provides quantized scripting memory usage numbers.
Rather than authoring a new hook, another option would be exposing deviceMemory via the existing hook in https://github.com/GoogleChromeLabs/adaptive-loading/blob/master/hooks/memory/index.js
After completing a successful build and deploy, all of the current apps appear to function correctly when loaded from /app, except for React Movie and React Shrine. You can test this here:
https://us-central1-adaptive-loading.cloudfunctions.net/app/react-shrine-network-aware-code-splitting/
https://us-central1-adaptive-loading.cloudfunctions.net/app/react-movie-network-aware-components/
The debug messages in console suggest that resources cannot be found, although they appear to be getting built correctly locally in functions/builds before the deploy is done. This makes me think the issue may be something else.
@anton-karlovskiy could you debug this issue please? Happy for you to use env-aware for staging tests.
Now that https://github.com/GoogleChromeLabs/react-adaptive-hooks support setting initial values (e.g initialEffectiveConnectionType
), I think there would be value in showing how Client Hints can provide an initial value on the server and this can then be updated, if necessary, on the client-side via the JS APIs exposed in Hooks.
Stack requirement:
We should build an app that uses Next.js as it has to keep SSR in mind and would be a good "stress test" this all works together.
Signal wise, I think it's fine to rely on Network and Device Memory. We would want this demo to be responsive and render cleanly on both mobile and desktop. Output should have at least a 90/100 on Lighthouse for performance.
What are we building?
A clean, nice-looking TV show browser. TMDB's API provides the data to allow us to build this. It's very similar to our existing TMDB client built using CRA, except will focus on shows rather than movies. There are a few existing Next.js open-source apps we could look to for reference here.
Reference
https://topheman-movie-browser.herokuapp.com/ is from this year and is open-source https://github.com/topheman/nextjs-movie-browser.
https://github.com/timneutkens/next-episode is a Next.js TV show app that might be a good fit here. It's 2 years old so likely requires an upgrade to get it working with the latest version of Next.
I really like how next-episode looks. It would be good to evaluate if it makes more sense to fork it and update or try improving the design of nextjs-movie-browser. We could also choose to write something from scratch if there are too many "extras" in either implementation.
A minority of our demos leverage live data from a third-party API. We should avoid storing API keys directly in the source as this is against Google best practices. We should fix this after Chrome Dev Summit.
Do not embed API keys directly in code. API keys that are embedded in code can be accidentally exposed to the public. For example, you may forget to remove the keys from code that you share. Instead of embedding your API keys in your applications, store them in environment variables or in files outside of your application's source tree.
The best practice here is to leverage application and API key restrictions. By adding restrictions, we can reduce the impact of a compromised API key and can set what referrers or IPs can use the key:
Client hints are a set of opt-in HTTP request headers that give us insight into these aspects of the userβs device and the network theyβre connected to. By tapping into this information server side, we can change how we deliver content based on device and/or network conditions. This can help us to create more inclusive user experiences.
Examples of signals they provide include:
I would like for us to work on a Node/Express prototype using Client Hints to demonstrate how to apply adaptive-loading on the server. As most of the work we've done in the repo to date is about adapting how we fetch resources or code client-side, it would be valuable to highlight how decisions can be made before you send many of your payloads over the network in the first place.
One caveat with Client hints is that they donβt kick in on the navigation request the first time a user visits your site and are of course, only available in a limited number of browsers. However, if you persist hints with Accept-CH-Lifetime, this information will be available on the navigation request. We can document any limitations in a README as we work through this.
In terms of approach, I'm comfortable with us initially experimenting with the image serving use-case (using Client Hints to send down the most appropriate image based on ECT, DPR, memory). I feel this will be focused enough problem that we can work out just how much we want to explore the Service Worker path etc. before we start looking at adaptive code-serving from the server.
For the initial implementation, I would like for us to not factor in the Save Data header. We can explore some dedicated examples around it at a later date.
Reference:
Another approach developers can take to discover how powerful a device is is by client-side benchmarking. The idea is that you load up a simple benchmark in a Web Worker that stress-tests how powerful the CPU is and can provide a very "loose" approximate of how powerful the device is.
For now, I don't think we need to build a demo around this, but I would like us to be able to create a benchmark that runs after page load that we could deploy to Firebase and just test using a few devices on WebPageTest.
For now, this is a placeholder issue. I'll add more details shortly.
After reviewing our current hooks, I noticed that there were opportunities to simplify how they are used after being imported.
Currently, we export unit-test specific values in addition to the main values developers care about. I think such information (just for tests) can be duplicated as needed as constant values in those tests, but we can avoid exporting them if it makes it easier for developers to use the hooks.
A few examples below:
Current:
import React from 'react';
import { useHardwareConcurrency } from './hardware-concurrency';
const MyComponent = () => {
const { hardwareConcurrency: { numberOfLogicalProcessors } } = useHardwareConcurrency();
return (
<div>
{ numberOfLogicalProcessors <= 4 ? <img src='...' /> : <video src='...' /> }
</div>
);
};
Expected:
import React from 'react';
import { useHardwareConcurrency } from './hardware-concurrency';
const MyComponent = () => {
const { numberOfLogicalProcessors } = useHardwareConcurrency();
return (
<div>
{ numberOfLogicalProcessors <= 4 ? <img src='...' /> : <video src='...' /> }
</div>
);
};
Current:
import React from 'react';
import { useMemoryStatus } from './memory';
const MyComponent = () => {
const { memoryStatus: { overLoaded } } = useMemoryStatus();
return (
<div>
{ overLoaded ? <img src='...' /> : <video src='...' /> }
</div>
);
};
Expected:
import React from 'react';
import { useMemoryStatus } from './memory';
const MyComponent = () => {
const { overLoaded } = useMemoryStatus();
return (
<div>
{ overLoaded ? <img src='...' /> : <video src='...' /> }
</div>
);
};
Current:
import React from 'react';
import { useBatteryStatus } from './battery';
const MyComponent = () => {
const { batteryStatus: { level } } = useBatteryStatus();
return (
<div>
{ level > 0.75 ? <video src='...' /> : <img src='...' /> }
</div>
);
};
Expected:
import React from 'react';
import { useBatteryStatus } from './battery';
const MyComponent = () => {
const { level } = useBatteryStatus();
return (
<div>
{ level > 0.75 ? <video src='...' /> : <img src='...' /> }
</div>
);
};
Would it be possible for us to simplify the output of the hooks to something like the above?
As the microsite project is coming along well so far, I would like for us to explore deploying it to http://adaptive-loading.web.app/ as the root/main page on the domain. This would allow us to get a better idea of how the implementation performs on production and consider if any further performance optimizations are needed on the server-side.
Similar to ECT, we could also experiment with conditional loading (light vs. heavy work) based on memory signals like performance.memory. A React hook could pass along details from performance.memory for other components to use.
console.log(performance.memory)
// Would show, for example
//{
// jsHeapSizeLimit: 767557632,
// totalJSHeapSize: 58054528,
// usedJSHeapSize: 42930044
//}
/*
usedJsHeapSize is the total amount of memory being used by JS objects including V8 internal objects.
totalJsHeapSize is current size of the JS heap including free space not occupied by any JS objects. This means that usedJsHeapSize can not be greater than totalJsHeapSize. Note that it is not necessarily that there has ever been totalJsHeapSize of alive JS objects.
*/
We would of course need to figure out the best way to use these the usedJsHeapSize
signal.
(There is also the Device Memory API, but I haven't found it particularly useful in distinguishing between low and high-memory devices in testing https://developers.google.com/web/updates/2017/12/device-memory).
It would be great if there was a way for us to easily deploy all of the examples. There are a few directions we could take this with a script:
cd
into each directory, run npm run build
and when complete, deploy everything to GitHub Pages.I'm sure there are other good options out there too.... :)
The originally React Shrine application implemented code-splitting for a number of views. We could augment the implementation there to use network-aware code-splitting such that:
Assigned to Anton
A sample that would highlight how to use create-react-app and the Network Information API to perform connection-aware loading of content. If a user is on 2G, lower fidelity resources would be served down. On a faster effective connection type, we might serve down hi-fi resources.
This is a practical implementation of https://addyosmani.com/blog/adaptive-serving/, https://web.dev/adaptive-serving-based-on-network-quality/ to React.
Idea: If the users battery level is low, conditionally load lighter/static experiences less likely to deplete energy quickly.
Demo of the Battery Status API: https://googlechrome.github.io/samples/battery-status/
Details + FF deprecation: https://www.chromestatus.com/feature/4537134732017664
While going through the demos, I had some minor feedback on tweaks we could make :)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.