Skip to content

Commit

Permalink
feat: Add preload() method to lazy (#43)
Browse files Browse the repository at this point in the history
* feat: Add `preload()` method to `lazy`

* test: Add test for preloading lazy imports

* docs: Document `lazy().preload()` in the readme
  • Loading branch information
rschristian authored Oct 17, 2024
1 parent 0847cef commit 4c9ed82
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 6 deletions.
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,9 @@ Make a lazily-loaded version of a Component.
import { lazy, LocationProvider, Router } from 'preact-iso';

// Synchronous, not code-splitted:
// import Home from './routes/home.js';
// import Profile from './routes/profile.js';
import Home from './routes/home.js';

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

const App = () => (
Expand All @@ -222,6 +220,20 @@ const App = () => (
);
```

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.

```js
const Profile = lazy(() => import('./routes/profile.js'));

function Home() {
return (
<a href="/profile" onMouseOver={() => Profile.preload()}>
Profile Page -- Hover over me to preload the module!
</a>
);
}
```

### `ErrorBoundary`

A simple component to catch errors in the component tree below it.
Expand Down
4 changes: 3 additions & 1 deletion src/lazy.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ComponentChildren, VNode } from 'preact';

export default function lazy<T>(load: () => Promise<{ default: T } | T>): T;
export default function lazy<T>(load: () => Promise<{ default: T } | T>): T & {
preload: () => Promise<T>;
};

export function ErrorBoundary(props: { children?: ComponentChildren; onError?: (error: Error) => void }): VNode;
15 changes: 13 additions & 2 deletions src/lazy.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,25 @@ import { useState, useRef } from 'preact/hooks';

export default function lazy(load) {
let p, c;
return props => {

const loadModule = () =>
load().then(m => (c = (m && m.default) || m));

const LazyComponent = props => {
const [, update] = useState(0);
const r = useRef(c);
if (!p) p = load().then(m => (c = (m && m.default) || m));
if (!p) p = loadModule();
if (c !== undefined) return h(c, props);
if (!r.current) r.current = p.then(() => update(1));
throw p;
};

LazyComponent.preload = () => {
if (!p) p = loadModule();
return p;
}

return LazyComponent;
}

// See https://github.com/preactjs/preact/blob/88680e91ec0d5fc29d38554a3e122b10824636b6/compat/src/suspense.js#L5
Expand Down
45 changes: 45 additions & 0 deletions test/lazy.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { h, render } from 'preact';
import * as chai from 'chai';
import * as sinon from 'sinon';
import sinonChai from 'sinon-chai';

import { LocationProvider, Router } from '../src/router.js';
import lazy from '../src/lazy.js';

const expect = chai.expect;
chai.use(sinonChai);

describe('lazy', () => {
let scratch;

beforeEach(() => {
if (scratch) {
render(null, scratch);
scratch.remove();
}
scratch = document.createElement('scratch');
document.body.appendChild(scratch);
history.replaceState(null, null, '/');
});


it('should support preloading lazy imports', async () => {
const A = () => <h1>A</h1>;
const loadB = sinon.fake(() => Promise.resolve(() => <h1>B</h1>));
const B = lazy(loadB);

render(
<LocationProvider>
<Router>
<A path="/" />
<B path="/b" />
</Router>
</LocationProvider>,
scratch
);

expect(loadB).not.to.have.been.called;
await B.preload();
expect(loadB).to.have.been.calledOnce;
});
});

0 comments on commit 4c9ed82

Please sign in to comment.