From ab3ed5a6d788ce3d9c79a3b0f73ef740156fc469 Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Wed, 16 Oct 2024 02:56:30 -0500 Subject: [PATCH 1/3] feat: Add `preload()` method to `lazy` --- src/lazy.d.ts | 4 +++- src/lazy.js | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/lazy.d.ts b/src/lazy.d.ts index 829cdf3..24e3b1b 100644 --- a/src/lazy.d.ts +++ b/src/lazy.d.ts @@ -1,5 +1,7 @@ import { ComponentChildren, VNode } from 'preact'; -export default function lazy(load: () => Promise<{ default: T } | T>): T; +export default function lazy(load: () => Promise<{ default: T } | T>): T & { + preload: () => Promise; +}; export function ErrorBoundary(props: { children?: ComponentChildren; onError?: (error: Error) => void }): VNode; diff --git a/src/lazy.js b/src/lazy.js index 9670e8d..564cdae 100644 --- a/src/lazy.js +++ b/src/lazy.js @@ -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 From 84e4efdb397c0307fb1c4fdea74203fb3eebba2c Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Wed, 16 Oct 2024 19:36:19 -0500 Subject: [PATCH 2/3] test: Add test for preloading lazy imports --- test/lazy.test.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 test/lazy.test.js diff --git a/test/lazy.test.js b/test/lazy.test.js new file mode 100644 index 0000000..d16015f --- /dev/null +++ b/test/lazy.test.js @@ -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 = () =>

A

; + const loadB = sinon.fake(() => Promise.resolve(() =>

B

)); + const B = lazy(loadB); + + render( + + + + + + , + scratch + ); + + expect(loadB).not.to.have.been.called; + await B.preload(); + expect(loadB).to.have.been.calledOnce; + }); +}); From 254385ce94d9ad8100cbf3732cfa293d6f325853 Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Thu, 17 Oct 2024 02:23:50 -0500 Subject: [PATCH 3/3] docs: Document `lazy().preload()` in the readme --- README.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 152515d..e7bcbe5 100644 --- a/README.md +++ b/README.md @@ -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 = () => ( @@ -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 ( + Profile.preload()}> + Profile Page -- Hover over me to preload the module! + + ); +} +``` + ### `ErrorBoundary` A simple component to catch errors in the component tree below it.