Skip to content

Commit 4c9ed82

Browse files
authored
feat: Add preload() method to lazy (#43)
* feat: Add `preload()` method to `lazy` * test: Add test for preloading lazy imports * docs: Document `lazy().preload()` in the readme
1 parent 0847cef commit 4c9ed82

File tree

4 files changed

+76
-6
lines changed

4 files changed

+76
-6
lines changed

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,9 @@ Make a lazily-loaded version of a Component.
205205
import { lazy, LocationProvider, Router } from 'preact-iso';
206206

207207
// Synchronous, not code-splitted:
208-
// import Home from './routes/home.js';
209-
// import Profile from './routes/profile.js';
208+
import Home from './routes/home.js';
210209

211210
// Asynchronous, code-splitted:
212-
const Home = lazy(() => import('./routes/home.js'));
213211
const Profile = lazy(() => import('./routes/profile.js'));
214212

215213
const App = () => (
@@ -222,6 +220,20 @@ const App = () => (
222220
);
223221
```
224222

223+
The result of `lazy()` also exposes a `preload()` method that can be used to load the component before it's needed for rendering. Entirely optional, but can be useful on focus, mouse over, etc. to start loading the component a bit earlier than it otherwise would be.
224+
225+
```js
226+
const Profile = lazy(() => import('./routes/profile.js'));
227+
228+
function Home() {
229+
return (
230+
<a href="/profile" onMouseOver={() => Profile.preload()}>
231+
Profile Page -- Hover over me to preload the module!
232+
</a>
233+
);
234+
}
235+
```
236+
225237
### `ErrorBoundary`
226238

227239
A simple component to catch errors in the component tree below it.

src/lazy.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { ComponentChildren, VNode } from 'preact';
22

3-
export default function lazy<T>(load: () => Promise<{ default: T } | T>): T;
3+
export default function lazy<T>(load: () => Promise<{ default: T } | T>): T & {
4+
preload: () => Promise<T>;
5+
};
46

57
export function ErrorBoundary(props: { children?: ComponentChildren; onError?: (error: Error) => void }): VNode;

src/lazy.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,25 @@ import { useState, useRef } from 'preact/hooks';
33

44
export default function lazy(load) {
55
let p, c;
6-
return props => {
6+
7+
const loadModule = () =>
8+
load().then(m => (c = (m && m.default) || m));
9+
10+
const LazyComponent = props => {
711
const [, update] = useState(0);
812
const r = useRef(c);
9-
if (!p) p = load().then(m => (c = (m && m.default) || m));
13+
if (!p) p = loadModule();
1014
if (c !== undefined) return h(c, props);
1115
if (!r.current) r.current = p.then(() => update(1));
1216
throw p;
1317
};
18+
19+
LazyComponent.preload = () => {
20+
if (!p) p = loadModule();
21+
return p;
22+
}
23+
24+
return LazyComponent;
1425
}
1526

1627
// See https://github.com/preactjs/preact/blob/88680e91ec0d5fc29d38554a3e122b10824636b6/compat/src/suspense.js#L5

test/lazy.test.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { h, render } from 'preact';
2+
import * as chai from 'chai';
3+
import * as sinon from 'sinon';
4+
import sinonChai from 'sinon-chai';
5+
6+
import { LocationProvider, Router } from '../src/router.js';
7+
import lazy from '../src/lazy.js';
8+
9+
const expect = chai.expect;
10+
chai.use(sinonChai);
11+
12+
describe('lazy', () => {
13+
let scratch;
14+
15+
beforeEach(() => {
16+
if (scratch) {
17+
render(null, scratch);
18+
scratch.remove();
19+
}
20+
scratch = document.createElement('scratch');
21+
document.body.appendChild(scratch);
22+
history.replaceState(null, null, '/');
23+
});
24+
25+
26+
it('should support preloading lazy imports', async () => {
27+
const A = () => <h1>A</h1>;
28+
const loadB = sinon.fake(() => Promise.resolve(() => <h1>B</h1>));
29+
const B = lazy(loadB);
30+
31+
render(
32+
<LocationProvider>
33+
<Router>
34+
<A path="/" />
35+
<B path="/b" />
36+
</Router>
37+
</LocationProvider>,
38+
scratch
39+
);
40+
41+
expect(loadB).not.to.have.been.called;
42+
await B.preload();
43+
expect(loadB).to.have.been.calledOnce;
44+
});
45+
});

0 commit comments

Comments
 (0)