From e8435c2444698602dbd6e9bf945e7d17db38f862 Mon Sep 17 00:00:00 2001 From: douglasward Date: Wed, 1 Dec 2021 10:02:16 +0100 Subject: [PATCH] allow defining the element used when hydrating svelte components (#221) * allow defining the mounted element for used when hydrating svelte components * make sure component mount element tags match * fix up tests after adding default element div hydrate option * revert bumping of match[0] index as it comes before the added element name match Co-authored-by: Douglas Ward --- .../__tests__/inlineSvelteComponent.spec.ts | 10 ++++----- .../__tests__/partialHydration.spec.ts | 22 +++++++++---------- src/partialHydration/inlineSvelteComponent.ts | 19 +++++++++++----- src/partialHydration/mountComponentsInHtml.ts | 12 +++++----- src/utils/__tests__/svelteComponent.spec.ts | 10 ++++----- src/utils/svelteComponent.ts | 6 ++++- src/utils/types.ts | 1 + 7 files changed, 46 insertions(+), 34 deletions(-) diff --git a/src/partialHydration/__tests__/inlineSvelteComponent.spec.ts b/src/partialHydration/__tests__/inlineSvelteComponent.spec.ts index a09c6cd3..f42e5d1c 100644 --- a/src/partialHydration/__tests__/inlineSvelteComponent.spec.ts +++ b/src/partialHydration/__tests__/inlineSvelteComponent.spec.ts @@ -8,7 +8,7 @@ test('#escapeHtml', () => { }); test('#inlinePreprocessedSvelteComponent', () => { - const options = 'loading=lazy'; + const options = '{"loading":"lazy"}'; expect( inlinePreprocessedSvelteComponent({ name: 'Home', @@ -18,10 +18,10 @@ test('#inlinePreprocessedSvelteComponent', () => { options, }), ).toEqual( - `
`, + `
`, ); expect(inlinePreprocessedSvelteComponent({})).toEqual( - `
`, + `
`, ); }); @@ -38,9 +38,9 @@ test('#inlineSvelteComponent', () => { options, }), ).toEqual( - `
`, + `
`, ); expect(inlineSvelteComponent({})).toEqual( - `
`, + `
`, ); }); diff --git a/src/partialHydration/__tests__/partialHydration.spec.ts b/src/partialHydration/__tests__/partialHydration.spec.ts index a9735a87..bda87588 100644 --- a/src/partialHydration/__tests__/partialHydration.spec.ts +++ b/src/partialHydration/__tests__/partialHydration.spec.ts @@ -9,7 +9,7 @@ describe('#partialHydration', () => { }) ).code, ).toEqual( - `
`, + `
`, ); }); @@ -17,11 +17,11 @@ describe('#partialHydration', () => { expect( ( await partialHydration.markup({ - content: '', + content: '', }) ).code, ).toEqual( - `
`, + `
`, ); }); @@ -29,11 +29,11 @@ describe('#partialHydration', () => { expect( ( await partialHydration.markup({ - content: '', + content: '', }) ).code, ).toEqual( - `
`, + `
`, ); }); @@ -41,11 +41,11 @@ describe('#partialHydration', () => { expect( ( await partialHydration.markup({ - content: '', + content: '', }) ).code, ).toEqual( - `
`, + `
`, ); }); it('eager, root margin, threshold', async () => { @@ -53,11 +53,11 @@ describe('#partialHydration', () => { ( await partialHydration.markup({ content: - '', + '', }) ).code, ).toEqual( - `
`, + `
`, ); }); it('open string', async () => { @@ -102,11 +102,11 @@ describe('#partialHydration', () => { expect( ( await partialHydration.markup({ - content: ``, + content: ``, }) ).code, ).toEqual( - `
`, + `
`, ); }); }); diff --git a/src/partialHydration/inlineSvelteComponent.ts b/src/partialHydration/inlineSvelteComponent.ts index 5aa95ed8..671a63f3 100644 --- a/src/partialHydration/inlineSvelteComponent.ts +++ b/src/partialHydration/inlineSvelteComponent.ts @@ -1,5 +1,8 @@ -const defaultHydrationOptions = { +import { HydrateOptions } from '../utils/types'; + +const defaultHydrationOptions: HydrateOptions = { loading: 'lazy', + element: 'div', }; export function escapeHtml(text: string): string { @@ -22,19 +25,21 @@ export function inlinePreprocessedSvelteComponent({ props = {}, options = '', }: InputParamsInlinePreprocessedSvelteComponent): string { - const hydrationOptions = options.length > 0 ? options : JSON.stringify(defaultHydrationOptions); + const hydrationOptions = + options.length > 0 ? { ...defaultHydrationOptions, ...JSON.parse(options) } : defaultHydrationOptions; + const hydrationOptionsString = JSON.stringify(hydrationOptions); const replacementAttrs = { class: '"ejs-component"', 'data-ejs-component': `"${name}"`, 'data-ejs-props': `{JSON.stringify(${props})}`, - 'data-ejs-options': `{JSON.stringify(${hydrationOptions})}`, + 'data-ejs-options': `{JSON.stringify(${hydrationOptionsString})}`, }; const replacementAttrsString = Object.entries(replacementAttrs).reduce( (out, [key, value]) => `${out} ${key}=${value}`, '', ); - return ``; + return `<${hydrationOptions.element}${replacementAttrsString} />`; } type InputParamsInlineSvelteComponent = { @@ -42,6 +47,7 @@ type InputParamsInlineSvelteComponent = { props?: any; options?: { loading?: string; // todo: enum, can't get it working: 'lazy', 'eager', 'none' + element?: string; // default: 'div' }; }; @@ -50,7 +56,8 @@ export function inlineSvelteComponent({ props = {}, options = {}, }: InputParamsInlineSvelteComponent): string { - const hydrationOptions = Object.keys(options).length > 0 ? options : defaultHydrationOptions; + const hydrationOptions = + Object.keys(options).length > 0 ? { ...defaultHydrationOptions, ...options } : defaultHydrationOptions; const replacementAttrs = { class: '"ejs-component"', @@ -63,5 +70,5 @@ export function inlineSvelteComponent({ '', ); - return `
`; + return `<${hydrationOptions.element}${replacementAttrsString}>`; } diff --git a/src/partialHydration/mountComponentsInHtml.ts b/src/partialHydration/mountComponentsInHtml.ts index 29316569..e1e0bde6 100644 --- a/src/partialHydration/mountComponentsInHtml.ts +++ b/src/partialHydration/mountComponentsInHtml.ts @@ -14,23 +14,23 @@ export default function mountComponentsInHtml({ page, html, hydrateOptions }): s let outputHtml = html; // sometimes svelte adds a class to our inlining. const matches = outputHtml.matchAll( - /
<\/div>/gim, + /<(\S+) class="ejs-component[^]*?" data-ejs-component="([A-Za-z]+)" data-ejs-props="({[^]*?})" data-ejs-options="({[^]*?})"><\/\1>/gim, ); for (const match of matches) { - const hydrateComponentName = match[1]; + const hydrateComponentName = match[2]; let hydrateComponentProps; let hydrateComponentOptions; try { - hydrateComponentProps = JSON.parse(replaceSpecialCharacters(match[2])); + hydrateComponentProps = JSON.parse(replaceSpecialCharacters(match[3])); } catch (e) { - throw new Error(`Failed to JSON.parse props for ${hydrateComponentName} ${match[2]}`); + throw new Error(`Failed to JSON.parse props for ${hydrateComponentName} ${match[3]}`); } try { - hydrateComponentOptions = JSON.parse(replaceSpecialCharacters(match[3])); + hydrateComponentOptions = JSON.parse(replaceSpecialCharacters(match[4])); } catch (e) { - throw new Error(`Failed to JSON.parse props for ${hydrateComponentName} ${match[3]}`); + throw new Error(`Failed to JSON.parse props for ${hydrateComponentName} ${match[4]}`); } if (hydrateOptions) { diff --git a/src/utils/__tests__/svelteComponent.spec.ts b/src/utils/__tests__/svelteComponent.spec.ts index d9b317db..150a2c50 100644 --- a/src/utils/__tests__/svelteComponent.spec.ts +++ b/src/utils/__tests__/svelteComponent.spec.ts @@ -83,7 +83,7 @@ describe('#svelteComponent', () => { render: () => ({ head: '', css: { code: '' }, - html: '
', + html: '
', }), _css: ['', ''], }), @@ -110,7 +110,7 @@ describe('#svelteComponent', () => { ); expect(componentProps.page.componentsToHydrate[0]).toMatchObject({ - hydrateOptions: { loading: 'lazy' }, + hydrateOptions: { loading: 'lazy', element: 'div' }, id: 'SwrzsrVDCd', name: 'datepickerSwrzsrVDCd', prepared: {}, @@ -125,7 +125,7 @@ describe('#svelteComponent', () => { render: () => ({ head: '', css: { code: '' }, - html: '
', + html: '
', }), _css: ['', ''], _cssMap: ['', ''], @@ -189,7 +189,7 @@ describe('#svelteComponent', () => { ); expect(props.page.svelteCss).toEqual([{ css: ['', ''], cssMap: ['', ''] }]); expect(props.page.componentsToHydrate[0]).toMatchObject({ - hydrateOptions: { loading: 'lazy' }, + hydrateOptions: { loading: 'lazy', element: 'div' }, id: 'SwrzsrVDCd', name: 'datepickerSwrzsrVDCd', prepared: {}, @@ -204,7 +204,7 @@ describe('#svelteComponent', () => { render: () => ({ head: '', css: { code: '' }, - html: '
', + html: '
', }), _css: ['', ''], _cssMap: ['', ''], diff --git a/src/utils/svelteComponent.ts b/src/utils/svelteComponent.ts index 9c8b6535..1b1f1d1d 100644 --- a/src/utils/svelteComponent.ts +++ b/src/utils/svelteComponent.ts @@ -61,7 +61,11 @@ const svelteComponent = id, }); - return `
${innerHtml}
`; + return `<${ + hydrateOptions.element + } class="${cleanComponentName.toLowerCase()}-component" id="${uniqueComponentName}">${innerHtml}`; } catch (e) { // console.log(e); page.errors.push(e); diff --git a/src/utils/types.ts b/src/utils/types.ts index 35376c64..22402ac1 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -174,6 +174,7 @@ export type HydrateOptions = { noPrefetch?: boolean; threshold?: number; rootMargin?: string; + element?: string; }; export interface ComponentPayload {