diff --git a/src/partialHydration/__tests__/partialHydration.spec.ts b/src/partialHydration/__tests__/partialHydration.spec.ts
index 7972e3dc..709c5b2d 100644
--- a/src/partialHydration/__tests__/partialHydration.spec.ts
+++ b/src/partialHydration/__tests__/partialHydration.spec.ts
@@ -1,4 +1,4 @@
-import partialHydration from '../partialHydration';
+import partialHydration, { partialHydrationClient } from '../partialHydration';
describe('#partialHydration', () => {
it('replaces as expected', async () => {
@@ -110,3 +110,15 @@ describe('#partialHydration', () => {
);
});
});
+
+describe('partialHydrationClient', () => {
+ it('replaces as expected', async () => {
+ expect(
+ (
+ await partialHydrationClient.markup({
+ content: '',
+ })
+ ).code,
+ ).toMatchInlineSnapshot(`""`);
+ });
+});
diff --git a/src/partialHydration/inlineSvelteComponent.ts b/src/partialHydration/inlineSvelteComponent.ts
index 747fd286..45339736 100644
--- a/src/partialHydration/inlineSvelteComponent.ts
+++ b/src/partialHydration/inlineSvelteComponent.ts
@@ -18,13 +18,18 @@ type InputParamsInlinePreprocessedSvelteComponent = {
name?: string;
props?: string;
options?: string;
+ mode?: 'inline' | 'wrapper';
};
export function inlinePreprocessedSvelteComponent({
name = '',
props = '',
options = '',
+ mode = 'wrapper',
}: InputParamsInlinePreprocessedSvelteComponent): string {
+ if (mode === 'inline') {
+ return `<${name} {...${props}}/>`;
+ }
// FIXME: don't output default options into the component to reduce file size.
const hydrationOptionsString =
options.length > 0
diff --git a/src/partialHydration/mountComponentsInHtml.ts b/src/partialHydration/mountComponentsInHtml.ts
index 0ef208d2..2db672fd 100644
--- a/src/partialHydration/mountComponentsInHtml.ts
+++ b/src/partialHydration/mountComponentsInHtml.ts
@@ -1,4 +1,5 @@
import svelteComponent from '../utils/svelteComponent';
+import type { HydrateOptions } from '../utils/types';
export const replaceSpecialCharacters = (str) =>
str
@@ -9,7 +10,7 @@ export const replaceSpecialCharacters = (str) =>
.replace(/'/gim, "'")
.replace(/&/gim, '&');
-export default function mountComponentsInHtml({ page, html, hydrateOptions }): string {
+export default function mountComponentsInHtml({ page, html, isHydrated = false }): string {
let outputHtml = html;
// sometimes svelte adds a class to our inlining.
const matches = outputHtml.matchAll(
@@ -18,8 +19,8 @@ export default function mountComponentsInHtml({ page, html, hydrateOptions }): s
for (const match of matches) {
const hydrateComponentName = match[2];
- let hydrateComponentProps;
- let hydrateComponentOptions;
+ let hydrateComponentProps: any;
+ let hydrateComponentOptions: HydrateOptions;
try {
hydrateComponentProps = JSON.parse(replaceSpecialCharacters(match[3]));
@@ -32,23 +33,11 @@ export default function mountComponentsInHtml({ page, html, hydrateOptions }): s
throw new Error(`Failed to JSON.parse props for ${hydrateComponentName} ${replaceSpecialCharacters(match[4])}`);
}
- if (hydrateOptions) {
- throw new Error(
- `Client side hydrated component is attempting to hydrate another sub component "${hydrateComponentName}." This isn't supported. \n
- Debug: ${JSON.stringify({
- hydrateOptions,
- hydrateComponentName,
- hydrateComponentProps,
- hydrateComponentOptions,
- })}
- `,
- );
- }
-
const hydratedHtml = svelteComponent(hydrateComponentName)({
page,
props: hydrateComponentProps,
hydrateOptions: hydrateComponentOptions,
+ isHydrated,
});
outputHtml = outputHtml.replace(match[0], hydratedHtml);
diff --git a/src/partialHydration/partialHydration.ts b/src/partialHydration/partialHydration.ts
index 45c821fe..a7d64370 100644
--- a/src/partialHydration/partialHydration.ts
+++ b/src/partialHydration/partialHydration.ts
@@ -10,12 +10,12 @@ const extractHydrateOptions = (htmlString) => {
return '';
};
-const createReplacementString = ({ input, name, props }) => {
+const createReplacementString = ({ input, name, props, mode }) => {
const options = extractHydrateOptions(input);
- return inlinePreprocessedSvelteComponent({ name, props, options });
+ return inlinePreprocessedSvelteComponent({ name, props, options, mode });
};
-export const preprocessSvelteContent = (content) => {
+export const preprocessSvelteContent = (content, mode = 'wrapper') => {
// Note: this regex only supports self closing components.
// Slots aren't supported for client hydration either.
const hydrateableComponentPattern = /<([a-zA-Z]+)[^>]+hydrate-client={([^]*?})}[^/>]*\/>/gim;
@@ -23,7 +23,7 @@ export const preprocessSvelteContent = (content) => {
const output = matches.reduce((out, match) => {
const [wholeMatch, name, props] = match;
- const replacement = createReplacementString({ input: wholeMatch, name, props });
+ const replacement = createReplacementString({ input: wholeMatch, name, props, mode });
return out.replace(wholeMatch, replacement);
}, content);
@@ -48,4 +48,10 @@ const partialHydration = {
},
};
+export const partialHydrationClient = {
+ markup: async ({ content }) => {
+ return { code: preprocessSvelteContent(content, 'inline') };
+ },
+};
+
export default partialHydration;
diff --git a/src/rollup/rollupPlugin.ts b/src/rollup/rollupPlugin.ts
index f68454ae..3dc38880 100644
--- a/src/rollup/rollupPlugin.ts
+++ b/src/rollup/rollupPlugin.ts
@@ -15,7 +15,7 @@ import del from 'del';
import { fork, ChildProcess } from 'child_process';
import chokidar from 'chokidar';
-import partialHydration from '../partialHydration/partialHydration';
+import partialHydration, { partialHydrationClient } from '../partialHydration/partialHydration';
import windowsPathFix from '../utils/windowsPathFix';
import { SettingsOptions } from '../utils/types';
@@ -87,11 +87,12 @@ export function transformFn({
type: 'ssr' | 'client';
}) {
const compilerOptions = getCompilerOptions({ type });
+ const hydrationPreprocessor = type === 'ssr' ? partialHydration : partialHydrationClient;
const preprocessors =
svelteConfig && Array.isArray(svelteConfig.preprocess)
- ? [...svelteConfig.preprocess, partialHydration]
- : [partialHydration];
+ ? [...svelteConfig.preprocess, hydrationPreprocessor]
+ : [hydrationPreprocessor];
return async (code, id) => {
const extensions = (svelteConfig && svelteConfig.extensions) || ['.svelte'];
diff --git a/src/utils/Page.ts b/src/utils/Page.ts
index 084de7a5..2a142e9d 100644
--- a/src/utils/Page.ts
+++ b/src/utils/Page.ts
@@ -76,7 +76,7 @@ const buildPage = async (page) => {
await page.runHook('shortcodes', page);
// shortcodes can add svelte components, so we have to process the resulting html accordingly.
- page.layoutHtml = mountComponentsInHtml({ page, html: page.layoutHtml, hydrateOptions: false });
+ page.layoutHtml = mountComponentsInHtml({ page, html: page.layoutHtml });
hydrateComponents(page);
diff --git a/src/utils/svelteComponent.ts b/src/utils/svelteComponent.ts
index 2ddc007b..97b65ef6 100644
--- a/src/utils/svelteComponent.ts
+++ b/src/utils/svelteComponent.ts
@@ -13,7 +13,7 @@ export const getComponentName = (str) => {
const svelteComponent =
(componentName: String, folder: String = 'components') =>
- ({ page, props, hydrateOptions }: ComponentPayload): string => {
+ ({ page, props, hydrateOptions, isHydrated = false }: ComponentPayload): string => {
const { ssr, client } = page.settings.$$internal.findComponent(componentName, folder);
const cleanComponentName = getComponentName(componentName);
@@ -39,11 +39,12 @@ const svelteComponent =
const innerHtml = mountComponentsInHtml({
html: htmlOutput,
page,
- hydrateOptions,
+ isHydrated: isHydrated || (hydrateOptions && hydrateOptions.loading !== 'none'),
});
// hydrateOptions.loading=none for server only rendered injected into html
- if (!hydrateOptions || hydrateOptions.loading === 'none') {
+ if (isHydrated || !hydrateOptions || hydrateOptions.loading === 'none') {
+ // if parent component is hydrated or
// if a component isn't hydrated we don't need to wrap it in a unique div.
return innerHtml;
}
diff --git a/src/utils/types.ts b/src/utils/types.ts
index cdc03738..339a129d 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -183,6 +183,7 @@ export interface ComponentPayload {
page: Page;
props: any;
hydrateOptions?: HydrateOptions;
+ isHydrated?: boolean;
}
export interface RollupDevOptions {