Skip to content

Commit

Permalink
Fix: Svelte SDK: Awaiter (#3640)
Browse files Browse the repository at this point in the history
## Description

- add test for SSR'ing lazy-loadable components
- tweak Sveltekit e2e test to do the above
  • Loading branch information
samijaber authored Oct 18, 2024
1 parent 1806a7c commit a971542
Show file tree
Hide file tree
Showing 11 changed files with 361 additions and 68 deletions.
49 changes: 47 additions & 2 deletions packages/sdks-tests/src/e2e-tests/dynamic-loading.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { expect } from '@playwright/test';
import { test } from '../helpers/index.js';

const checkSupportsDynamicLoading = (packageName: string) => {
test.skip(packageName !== 'sveltekit');
};

test.describe('Dynamic loading of custom components', () => {
test('LazyComponent only loads when requested', async ({ page, packageName }) => {
test.skip(packageName !== 'sveltekit');
checkSupportsDynamicLoading(packageName);
await page.goto('/dynamic-loading');

let lazyComponentRequested = false;
Expand All @@ -28,7 +32,7 @@ test.describe('Dynamic loading of custom components', () => {
page,
packageName,
}) => {
test.skip(packageName !== 'sveltekit');
checkSupportsDynamicLoading(packageName);
await page.goto('/dynamic-loading');

let notLazyComponentRequested = false;
Expand All @@ -53,3 +57,44 @@ test.describe('Dynamic loading of custom components', () => {
expect(notLazyComponentRequested).toBe(true);
});
});

test.describe('Eager dynamic loading of custom components', () => {
test('EagerLazyComponent loads immediately even if not requested', async ({
page,
packageName,
}) => {
checkSupportsDynamicLoading(packageName);
await page.goto('/eager-dynamic-loading');

let eagerLazyComponentRequested = false;
let eagerLazyComponentRendered = false;

page.on('request', request => {
if (request.url().includes('LazyComponent')) {
eagerLazyComponentRequested = true;
}
});

page.on('requestfinished', request => {
void (async () => {
if (request.resourceType() === 'document' || request.resourceType() === 'script') {
const response = await request.response();
const responseBody = await response?.text();

if (responseBody?.includes('lazy component loaded')) {
// component is SSR'd
eagerLazyComponentRendered = true;
}
}
})();
});

await page.waitForLoadState('networkidle');

expect(eagerLazyComponentRequested).toBe(true);
expect(eagerLazyComponentRendered).toBe(true);

const eagerLazyComponent = page.locator('text=lazy component loaded');
await expect(eagerLazyComponent).toBeVisible();
});
});
65 changes: 65 additions & 0 deletions packages/sdks-tests/src/specs/eager-dynamic-loading.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
export const EAGER_DYNAMIC_LOADING_CUSTOM_COMPONENTS = {
ownerId: 'ad30f9a246614faaa6a03374f83554c9',
lastUpdateBy: null,
createdDate: 1727698733481,
id: 'ecb200c7c9954212a276b1071a376f18',
'@version': 4,
name: 'fsafsggxz',
modelId: '17c6065109ef4062ba083f5741f4ee6a',
published: 'published',
meta: {
lastPreviewUrl:
'http://localhost:5173/fsafsggxz?builder.space=ad30f9a246614faaa6a03374f83554c9&builder.user.permissions=read%2Ccreate%2Cpublish%2CeditCode%2CeditDesigns%2Cadmin%2CeditLayouts%2CeditLayers&builder.user.role.name=Admin&builder.user.role.id=admin&builder.cachebust=true&builder.preview=page&builder.noCache=true&builder.allowTextEdit=true&__builder_editing__=true&builder.overrides.page=ecb200c7c9954212a276b1071a376f18&builder.overrides.ecb200c7c9954212a276b1071a376f18=ecb200c7c9954212a276b1071a376f18&builder.overrides.page:/fsafsggxz=ecb200c7c9954212a276b1071a376f18&builder.options.locale=Default',
kind: 'page',
hasLinks: false,
componentsUsed: {
LazyComponent: 1,
},
},
priority: -718,
stage: 'b3ee01559a244a078973f545ad475eba',
query: [
{
'@type': '@builder.io/core:Query',
property: 'urlPath',
operator: 'is',
value: '/fsafsggxz',
},
],
data: {
title: 'fsafsggxz',
themeId: false,
blocks: [
{
'@type': '@builder.io/sdk:Element',
'@version': 2,
id: 'builder-5a8cdbbf69f14c79a4fa71438c8fd4cf',
component: {
name: 'LazyComponent',
options: {},
},
responsiveStyles: {
large: {
display: 'flex',
flexDirection: 'column',
position: 'relative',
flexShrink: '0',
boxSizing: 'border-box',
marginTop: '20px',
},
},
},
],
},
metrics: {
clicks: 0,
impressions: 0,
},
variations: {},
lastUpdated: 1727704258246,
firstPublished: 1727704258239,
testRatio: 1,
createdBy: 'RuGeCLr9ryVt1xRazFYc72uWwIK2',
lastUpdatedBy: 'RuGeCLr9ryVt1xRazFYc72uWwIK2',
folders: [],
};
2 changes: 2 additions & 0 deletions packages/sdks-tests/src/specs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import { SYMBOL_WITH_REPEAT_INPUT_BINDING } from './symbol-with-repeat-input-bin
import { CUSTOM_COMPONENT_CHILDREN_SLOT_PLACEMENT } from './children-slot-placement.js';
import { DYNAMIC_LOADING_CUSTOM_COMPONENTS } from './dynamic-loading.js';
import { SSR_BINDING_CONTENT } from './ssr-binding.js';
import { EAGER_DYNAMIC_LOADING_CUSTOM_COMPONENTS } from './eager-dynamic-loading.js';

function isBrowser(): boolean {
return typeof window !== 'undefined' && typeof document !== 'undefined';
Expand Down Expand Up @@ -144,6 +145,7 @@ export const PAGES = {
'/symbol-with-repeat-input-binding': SYMBOL_WITH_REPEAT_INPUT_BINDING,
'/children-slot-placement': CUSTOM_COMPONENT_CHILDREN_SLOT_PLACEMENT,
'/dynamic-loading': DYNAMIC_LOADING_CUSTOM_COMPONENTS,
'/eager-dynamic-loading': EAGER_DYNAMIC_LOADING_CUSTOM_COMPONENTS,
'/ssr-binding': SSR_BINDING_CONTENT,
} as const;

Expand Down
10 changes: 5 additions & 5 deletions packages/sdks/e2e/sveltekit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
"@sdk/tests": "workspace:*"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.27.7",
"svelte": "^4.2.8",
"svelte-check": "^3.6.2",
"@sveltejs/adapter-auto": "^3.2.5",
"@sveltejs/kit": "^2.7.1",
"svelte": "^4.2.19",
"svelte-check": "^4.0.5",
"tslib": "^2.4.1",
"typescript": "^5.1.6",
"vite": "^4.0.0"
"vite": "^5.4.9"
},
"nx": {
"targets": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ export async function load({ url }) {
_processContentResult,
});

return { props };
return {
props,
};
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,12 @@
<script lang="ts">
import { Content } from '@builder.io/sdk-svelte';
import NotLazyComponent from '../../components/NotLazyComponent.svelte';
// this data comes from the function in `+page.server.ts`, which runs on the server only
export let data;
const customComponents = [
{
name: 'LazyComponent',
component: {
load: () => import('../../components/LazyComponent.svelte'),
}
},
{
name: 'NotLazyComponent',
component: NotLazyComponent
}
]
</script>

<main>
{#if data.props}
{#key data.props}
<Content {...data.props} {customComponents} />
<Content {...data.props} customComponents={data.customComponents} />
{/key}
{:else}
Content Not Found
Expand Down
46 changes: 46 additions & 0 deletions packages/sdks/e2e/sveltekit/src/routes/[...catchall]/+page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { browser } from '$app/environment';
import NotLazyComponent from '../../components/NotLazyComponent.svelte';

/**
* Vite/Rollup does not allow arbitrary paths to be dynamic imports.
* https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations
*
* Imports must start with `./` or `../`, so we recommend placing all components in a specific directory,
* and using that to lazy-load the components into Builder.
*/
const getLazyLoadedComponentImport = (path: string) =>
import(`../../components/${path}.svelte`);

/**
* This helper function guarantees that:
* 1. On the server, the component is loaded eagerly.
* 2. On the client, the component is loaded lazily.
*/
const getComponentLoader = async (path: string) => {
if (browser) {
return () => getLazyLoadedComponentImport(path);
} else {
const compImport = await getLazyLoadedComponentImport(path);
return () => compImport;
}
};

/** @type {import('./$types').PageLoad} */
export async function load({ data }) {
const lazyImport = await getComponentLoader('LazyComponent');
return {
...data,
customComponents: [
{
name: 'LazyComponent',
component: {
load: lazyImport,
},
},
{
name: 'NotLazyComponent',
component: NotLazyComponent,
},
],
};
}
2 changes: 1 addition & 1 deletion packages/sdks/e2e/sveltekit/svelte.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/kit/vite';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
const config = {
Expand Down
16 changes: 8 additions & 8 deletions packages/sdks/overrides/svelte/src/components/awaiter.svelte
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
<script lang="ts">
export let load: () => Promise<any>;
import type { SvelteComponent } from 'svelte';
export let load: () => Promise<{ default: typeof SvelteComponent<any> }>;
export let fallback: any;
export let props: any;
export let attributes: any;
const componentImport = typeof load === 'string' ? import(load) : load();
</script>

{#await load()}
{#await componentImport}
{#if fallback}
<svelte:component this={fallback} />
{/if}
{:then { default: Component }}
<svelte:component
this={Component}
{...props}
attributes={attributes}
>
{:then { default: Component }}
<svelte:component this={Component} {...props} {attributes}>
<slot />
</svelte:component>
{/await}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import type { SvelteComponent } from 'svelte';
export type ComponentReference =
| {
/**
* A dynamic import function pointing to the component.
* Either:
* - A dynamic import function pointing to the component.
* Example: `() => import('../components/my-component.svelte')`
* @returns A Promise that resolves to the loaded component.
* - A string representing the path to the component.
* Example: `'../components/my-component.svelte'`
*/
load: () => Promise<{ default: typeof SvelteComponent<any> }>;
load: string | (() => Promise<{ default: typeof SvelteComponent<any> }>);
/**
* An optional fallback component to render while the dynamic component is loading.
*/
Expand Down
Loading

0 comments on commit a971542

Please sign in to comment.