Skip to content

Commit

Permalink
Merge branch 'master' into ms/add-typing
Browse files Browse the repository at this point in the history
  • Loading branch information
stramel authored Nov 26, 2019
2 parents 7f1e6ca + 20a4b9d commit a2468cb
Show file tree
Hide file tree
Showing 12 changed files with 5,689 additions and 689 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

# production
/build
/dist

# misc
.DS_Store
Expand Down
49 changes: 41 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ and then use them in your components. Examples for each hook and utility can be

### Network

`useNetworkStatus` React hook for getting network status (effective connection type)
`useNetworkStatus` React hook for adapting based on network status (effective connection type)

```js
import React from 'react';
Expand Down Expand Up @@ -68,9 +68,17 @@ const MyComponent = () => {
};
```

This hook accepts an optional `initialEffectiveConnectionType` string argument, which can be used to provide a `effectiveConnectionType` state value when the user's browser does not support the relevant [NetworkInformation API](https://wicg.github.io/netinfo/). Passing an initial value can also prove useful for server-side rendering, where the developer can pass an [ECT Client Hint](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/client-hints#ect) to detect the effective network connection type.

```js
// Inside of a functional React component
const initialEffectiveConnectionType = '4g';
const { effectiveConnectionType } = useNetworkStatus(initialEffectiveConnectionType);
```

### Save Data

`useSaveData` Utility for getting Save Data whether it's Lite mode enabled or not
`useSaveData` utility for adapting based on the user's browser Data Saver preferences.

```js
import React from 'react';
Expand All @@ -87,9 +95,17 @@ const MyComponent = () => {
};
```

This hook accepts an optional `initialSaveDataStatus` boolean argument, which can be used to provide a `saveData` state value when the user's browser does not support the relevant [NetworkInformation API](https://wicg.github.io/netinfo/). Passing an initial value can also prove useful for server-side rendering, where the developer can pass a server [Save-Data Client Hint](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/client-hints#save-data) that has been converted to a boolean to detect the user's data saving preference.

```js
// Inside of a functional React component
const initialSaveDataStatus = true;
const { saveData } = useSaveData(initialSaveDataStatus);
```

### CPU Cores / Hardware Concurrency

`useHardwareConcurrency` Utility for getting the number of logical CPU processor cores of the user's device
`useHardwareConcurrency` utility for adapting to the number of logical CPU processor cores on the user's device.

```js
import React from 'react';
Expand All @@ -108,7 +124,7 @@ const MyComponent = () => {

### Memory

`useMemoryStatus` Utility for getting memory status of the device
`useMemoryStatus` utility for adapting based on the user's device memory (RAM)

```js
import React from 'react';
Expand All @@ -125,11 +141,19 @@ const MyComponent = () => {
};
```

This hook accepts an optional `initialMemoryStatus` object argument, which can be used to provide a `deviceMemory` state value when the user's browser does not support the relevant [DeviceMemory API](https://github.com/w3c/device-memory). Passing an initial value can also prove useful for server-side rendering, where the developer can pass a server [Device-Memory Client Hint](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/client-hints#save-data) to detect the memory capacity of the user's device.

```js
// Inside of a functional React component
const initialMemoryStatus = { deviceMemory: 4 };
const { deviceMemory } = useMemoryStatus(initialMemoryStatus);
```

### Adaptive Code-loading & Code-splitting

#### Code-loading

Deliver a light, interactive core experience to users and progressively add high-end-only features on top, if a users hardware can handle it. Below is an example using the Network Status hook:
Deliver a light, interactive core experience to users and progressively add high-end-only features on top, if a user's hardware can handle it. Below is an example using the Network Status hook:

```js
import React, { Suspense, lazy } from 'react';
Expand Down Expand Up @@ -185,16 +209,21 @@ import React, { Suspense } from 'react';

const Component = React.lazy(() => {
const effectiveType = navigator.connection ? navigator.connection.effectiveType : null

let module;
switch (effectiveType) {
case "3g":
return import(/* webpackChunkName: "light" */ "./light.js");
module = import(/* webpackChunkName: "light" */ "./Light.js");
break;
case "4g":
return import(/* webpackChunkName: "full" */ "./full.js");
module = import(/* webpackChunkName: "full" */ "./Full.js");
break;
default:
return import(/* webpackChunkName: "full" */ "./full.js")
module = import(/* webpackChunkName: "full" */ "./Full.js");
break;
}

return module;
});

function App() {
Expand Down Expand Up @@ -255,6 +284,10 @@ export default App;

* [React Dixie Mesh - memory considerate loading](https://github.com/GoogleChromeLabs/adaptive-loading/tree/master/react-dixie-memory-considerate-loading) ([Live](https://adaptive-loading.web.app/react-dixie-memory-considerate-loading/))

### Hybrid

* [React Youtube - adaptive loading with mix of network, memory and hardware concurrency](https://github.com/GoogleChromeLabs/adaptive-loading/tree/master/react-youtube-adaptive-loading) ([Live](https://adaptive-loading.web.app/react-youtube-adaptive-loading/))


## References

Expand Down
27 changes: 26 additions & 1 deletion hardware-concurrency/hardware-concurrency.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,33 @@ afterEach(function() {
});

describe('useHardwareConcurrency', () => {
const navigator = window.navigator;

afterEach(() => {
if (!window.navigator) window.navigator = navigator;
});

test(`should return "true" for unsupported case`, () => {
Object.defineProperty(window, 'navigator', {
value: undefined,
configurable: true,
writable: true
});

const { useHardwareConcurrency } = require('./');
const { result } = renderHook(() => useHardwareConcurrency());

expect(result.current.unsupported).toBe(true);
});

test(`should return window.navigator.hardwareConcurrency`, () => {
const { useHardwareConcurrency } = require('./');
const { result } = renderHook(() => useHardwareConcurrency());
expect(result.current.numberOfLogicalProcessors).toBe(window.navigator.hardwareConcurrency);

expect(result.current.numberOfLogicalProcessors).toBe(
window.navigator.hardwareConcurrency
);
expect(result.current.unsupported).toBe(false);
});

test('should return 4 for device of hardwareConcurrency = 4', () => {
Expand All @@ -38,6 +61,7 @@ describe('useHardwareConcurrency', () => {
const { result } = renderHook(() => useHardwareConcurrency());

expect(result.current.numberOfLogicalProcessors).toEqual(4);
expect(result.current.unsupported).toBe(false);
});

test('should return 2 for device of hardwareConcurrency = 2', () => {
Expand All @@ -50,5 +74,6 @@ describe('useHardwareConcurrency', () => {
const { result } = renderHook(() => useHardwareConcurrency());

expect(result.current.numberOfLogicalProcessors).toEqual(2);
expect(result.current.unsupported).toBe(false);
});
});
5 changes: 4 additions & 1 deletion hardware-concurrency/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@

let initialHardwareConcurrency;
if (typeof navigator !== 'undefined' && 'hardwareConcurrency' in navigator) {
initialHardwareConcurrency = { numberOfLogicalProcessors: navigator.hardwareConcurrency };
initialHardwareConcurrency = {
unsupported: false,
numberOfLogicalProcessors: navigator.hardwareConcurrency
};
} else {
initialHardwareConcurrency = { unsupported: true };
}
Expand Down
21 changes: 14 additions & 7 deletions memory/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,28 @@ if (typeof navigator !== 'undefined' && 'deviceMemory' in navigator) {
} else {
unsupported = true;
}
let initialMemoryStatus;
let memoryStatus;
if (!unsupported) {
const performanceMemory = 'memory' in performance ? performance.memory : null;
initialMemoryStatus = {
memoryStatus = {
unsupported,
deviceMemory: navigator.deviceMemory,
totalJSHeapSize: performanceMemory ? performanceMemory.totalJSHeapSize : null,
totalJSHeapSize: performanceMemory
? performanceMemory.totalJSHeapSize
: null,
usedJSHeapSize: performanceMemory ? performanceMemory.usedJSHeapSize : null,
jsHeapSizeLimit: performanceMemory ? performanceMemory.jsHeapSizeLimit : null
jsHeapSizeLimit: performanceMemory
? performanceMemory.jsHeapSizeLimit
: null
};
} else {
initialMemoryStatus = { unsupported };
memoryStatus = { unsupported };
}

const useMemoryStatus = () => {
return { ...initialMemoryStatus };
const useMemoryStatus = initialMemoryStatus => {
return unsupported && initialMemoryStatus
? { ...memoryStatus, ...initialMemoryStatus }
: { ...memoryStatus };
};

export { useMemoryStatus };
66 changes: 65 additions & 1 deletion memory/memory.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ afterEach(function() {
});

const getMemoryStatus = currentResult => ({
unsupported: false,
deviceMemory: currentResult.deviceMemory,
totalJSHeapSize: currentResult.totalJSHeapSize,
usedJSHeapSize: currentResult.usedJSHeapSize,
Expand All @@ -36,6 +37,21 @@ describe('useMemoryStatus', () => {
expect(result.current.unsupported).toBe(true);
});

test('should return initialMemoryStatus for unsupported case', () => {
const mockInitialMemoryStatus = {
deviceMemory: 4
};
const { deviceMemory } = mockInitialMemoryStatus;

const { useMemoryStatus } = require('./');
const { result } = renderHook(() =>
useMemoryStatus(mockInitialMemoryStatus)
);

expect(result.current.unsupported).toBe(true);
expect(result.current.deviceMemory).toEqual(deviceMemory);
});

test('should return mockMemory status', () => {
const mockMemoryStatus = {
deviceMemory: 4,
Expand All @@ -55,6 +71,54 @@ describe('useMemoryStatus', () => {
const { useMemoryStatus } = require('./');
const { result } = renderHook(() => useMemoryStatus());

expect(getMemoryStatus(result.current)).toEqual(mockMemoryStatus);
expect(getMemoryStatus(result.current)).toEqual({
...mockMemoryStatus,
unsupported: false
});
});

test('should return mockMemory status without performance memory data', () => {
const mockMemoryStatus = {
deviceMemory: 4
};

global.navigator.deviceMemory = mockMemoryStatus.deviceMemory;
delete global.window.performance.memory;

const { useMemoryStatus } = require('./');
const { result } = renderHook(() => useMemoryStatus());

expect(result.current.deviceMemory).toEqual(mockMemoryStatus.deviceMemory);
expect(result.current.unsupported).toEqual(false);
});

test('should not return initialMemoryStatus for supported case', () => {
const mockMemoryStatus = {
deviceMemory: 4,
totalJSHeapSize: 60,
usedJSHeapSize: 40,
jsHeapSizeLimit: 50
};
const mockInitialMemoryStatus = {
deviceMemory: 4
};

global.navigator.deviceMemory = mockMemoryStatus.deviceMemory;

global.window.performance.memory = {
totalJSHeapSize: mockMemoryStatus.totalJSHeapSize,
usedJSHeapSize: mockMemoryStatus.usedJSHeapSize,
jsHeapSizeLimit: mockMemoryStatus.jsHeapSizeLimit
};

const { useMemoryStatus } = require('./');
const { result } = renderHook(() =>
useMemoryStatus(mockInitialMemoryStatus)
);

expect(getMemoryStatus(result.current)).toEqual({
...mockMemoryStatus,
unsupported: false
});
});
});
17 changes: 10 additions & 7 deletions network/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,29 @@ import { useState, useEffect } from 'react';

let unsupported;

const useNetworkStatus = () => {
const useNetworkStatus = initialEffectiveConnectionType => {
if ('connection' in navigator && 'effectiveType' in navigator.connection) {
unsupported = false;
} else {
unsupported = true;
}

const initialNetworkStatus = !unsupported ? {
effectiveConnectionType: navigator.connection.effectiveType
} : {
unsupported
};
const initialNetworkStatus = {
unsupported,
effectiveConnectionType: unsupported
? initialEffectiveConnectionType
: navigator.connection.effectiveType
};

const [networkStatus, setNetworkStatus] = useState(initialNetworkStatus);

useEffect(() => {
if (!unsupported) {
const navigatorConnection = navigator.connection;
const updateECTStatus = () => {
setNetworkStatus({ effectiveConnectionType: navigatorConnection.effectiveType });
setNetworkStatus({
effectiveConnectionType: navigatorConnection.effectiveType
});
};
navigatorConnection.addEventListener('change', updateECTStatus);
return () => {
Expand Down
Loading

0 comments on commit a2468cb

Please sign in to comment.