Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
});
});