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
81 changes: 55 additions & 26 deletions src/labs/ia-snow/ia-snow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ import { property } from 'lit/decorators.js';
import { customElement } from 'lit/decorators/custom-element.js';
import { state } from 'lit/decorators/state.js';

import Snowflakes, { type SnowflakesParams } from 'magic-snowflakes';

import '@src/elements/ia-button/ia-button';
import type Snowflakes from 'magic-snowflakes';
import type { SnowflakesParams } from 'magic-snowflakes';

import flakeIcon from './flake.svg';
import {
lazyLoadTemplate,
type LazyLoadedTemplate,
} from '@src/util/lazy-load-template';

/**
* An element that shows snowflakes to demo the elements library
Expand All @@ -29,28 +32,7 @@ export class IASnow extends LitElement {

render() {
return html`
<ia-button
@click=${() => {
if (this.snowing) {
this.stopSnowing();
} else {
this.startSnowing();
}
}}
>
${this.snowing ? 'Stop Snowflakes' : 'Start Snowflakes'}
</ia-button>

<ia-button
@click=${() => {
this.stopSnowing();
this.snowflakes?.destroy();
this.snowflakes = undefined;
}}
>
Clear Snowflakes
</ia-button>

${this.startButtonTemplate} ${this.clearButtonTemplate}
<img src=${flakeIcon} alt="Snowflakes icon" />
`;
}
Expand All @@ -63,8 +45,55 @@ export class IASnow extends LitElement {
}
}

private startSnowing() {
// Consider lazy loading external templates if they are large,
// below the fold or not needed immediately. This will reduce the initial
// bundle size.
//
// Note: In this case, ia-button is visible immediately so it's not a great
// example, but it demonstrates the lazy loading pattern.
private get startButtonTemplate(): LazyLoadedTemplate {
return lazyLoadTemplate(
async () => {
await import('@src/elements/ia-button/ia-button');
},
() => html`
<ia-button
@click=${() => {
if (this.snowing) {
this.stopSnowing();
} else {
this.startSnowing();
}
}}
>
${this.snowing ? 'Stop Snowflakes' : 'Start Snowflakes'}
</ia-button>
`,
);
}

private get clearButtonTemplate(): LazyLoadedTemplate {
return lazyLoadTemplate(
async () => {
await import('@src/elements/ia-button/ia-button');
},
() => html`
<ia-button
@click=${() => {
this.snowflakes?.destroy();
}}
>
Clear Snowflakes
</ia-button>
`,
);
}

private async startSnowing() {
if (!this.snowflakes) {
// lazy load dependencies when possible to reduce initial bundle size
const SnowflakesModule = await import('magic-snowflakes');
const Snowflakes = SnowflakesModule.default;
this.snowflakes = new Snowflakes(this.snowConfig);
}
this.snowflakes?.start();
Expand Down
74 changes: 74 additions & 0 deletions src/util/lazy-load-template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { TemplateResult } from 'lit';
import {
Directive,
directive,
DirectiveParameters,
DirectiveResult,
Part,
PartInfo,
} from 'lit/directive.js';

const resolved = new WeakSet();

/**
* A LazyLoadedTemplate is a wrapper for a TemplateResult
* lazy-loaded with the `lazyLoadTemplate` function.
*
* It can be used in an html`` block, eg.
*
* html`
* ${this.radioPlayerTemplate} <-- see `radioPlayerTemplate` example below
* `
*/
export type LazyLoadedTemplate = DirectiveResult<typeof LazyLoadTemplate>;

/**
* This utility let's you asynchronously load a webcomponent.
*
* Derived from an example shown in:
* https://www.youtube.com/watch?v=x9YDQUJx2uw
*
* Example use:
*
* get radioPlayerTemplate(): LazyLoadedTemplate {
* return lazyLoadTemplate(
* async (): Promise<void> => {
* await import('./players/radio-player');
* },
* (): TemplateResult => {
* return html`
* <radio-player
* .metadataResponse=${this.metadataResponse}
* .browserHistoryHandler=${this.browserHistoryHandler}
* >
* </radio-player>
* `;
* },
* );
* }
*
* ...
*
* return html`
* ${this.radioPlayerTemplate}
* `
*/
export class LazyLoadTemplate extends Directive {
constructor(partInfo: PartInfo) {
super(partInfo);
}

update(part: Part, [importPromise, value]: DirectiveParameters<this>) {
if (!resolved.has(part)) {
importPromise();
resolved.add(part);
}
return this.render(importPromise, value);
}

render(_importPromise: () => Promise<void>, value: () => TemplateResult) {
return value();
}
}

export const lazyLoadTemplate = directive(LazyLoadTemplate);