diff --git a/docs/core-concepts/ui-renderers.md b/docs/core-concepts/ui-renderers.md index a83d3f49be..2d91f1811f 100644 --- a/docs/core-concepts/ui-renderers.md +++ b/docs/core-concepts/ui-renderers.md @@ -106,6 +106,7 @@ export default { knownEntrypoint: ['framework'], // optional, entrypoint modules that will be used by compiled source external: ['dep'] // optional, dependencies that should not be built by snowpack polyfills: ['./shadow-dom-polyfill.js'] // optional, module scripts that should be loaded before client hydration. + hydrationPolyfills: ['./hydrate-framework.js'] // optional, polyfills that need to run before hydration ever occurs. }; ``` diff --git a/packages/astro/src/config_manager.ts b/packages/astro/src/config_manager.ts index e4a4fa8e15..b349cb8c73 100644 --- a/packages/astro/src/config_manager.ts +++ b/packages/astro/src/config_manager.ts @@ -16,6 +16,7 @@ interface RendererInstance { knownEntrypoints: string[] | undefined; external: string[] | undefined; polyfills: string[]; + hydrationPolyfills: string[]; } const CONFIG_MODULE_BASE_NAME = '__astro_config.js'; @@ -106,6 +107,7 @@ export class ConfigManager { } const polyfillsNormalized = (raw.polyfills || []).map((p: string) => p.startsWith('.') ? path.join(name, p) : p); + const hydrationPolyfillsNormalized = (raw.hydrationPolyfills || []).map((p: string) => p.startsWith('.') ? path.join(name, p) : p); return { name, @@ -115,7 +117,8 @@ export class ConfigManager { server: path.join(name, raw.server), knownEntrypoints: raw.knownEntrypoints, external: raw.external, - polyfills: polyfillsNormalized + polyfills: polyfillsNormalized, + hydrationPolyfills: hydrationPolyfillsNormalized }; }); @@ -127,7 +130,7 @@ export class ConfigManager { const rendererServerPackages = renderers.map(({ server }) => server); const rendererClientPackages = await Promise.all(renderers.filter(({client}) => client).map(({ client }) => this.resolvePackageUrl(client!))); const rendererPolyfills = await Promise.all(renderers.map(({ polyfills }) => Promise.all(polyfills.map(src => this.resolvePackageUrl(src))))); - + const rendererHydrationPolyfills = await Promise.all(renderers.map(({ hydrationPolyfills }) => Promise.all(hydrationPolyfills.map(src => this.resolvePackageUrl(src))))); const result = /* js */ `${rendererServerPackages.map((pkg, i) => `import __renderer_${i} from "${pkg}";`).join('\n')} @@ -137,7 +140,8 @@ let rendererInstances = [${renderers.map((r, i) => `{ source: ${rendererClientPackages[i] ? `"${rendererClientPackages[i]}"` : 'null'}, renderer: __renderer_${i}, options: ${r.options ? JSON.stringify(r.options) : 'null'}, - polyfills: ${JSON.stringify(rendererPolyfills[i])} + polyfills: ${JSON.stringify(rendererPolyfills[i])}, + hydrationPolyfills: ${JSON.stringify(rendererHydrationPolyfills[i])} }`).join(', ')}]; ${contents} diff --git a/packages/astro/src/internal/__astro_component.ts b/packages/astro/src/internal/__astro_component.ts index 2e98d55dc0..d57deb4620 100644 --- a/packages/astro/src/internal/__astro_component.ts +++ b/packages/astro/src/internal/__astro_component.ts @@ -14,20 +14,23 @@ export interface RendererInstance { renderer: Renderer; options: any; polyfills: string[]; + hydrationPolyfills: string[]; } const astroRendererInstance: RendererInstance = { source: '', renderer: astro as Renderer, options: null, - polyfills: [] + polyfills: [], + hydrationPolyfills: [] }; const astroHtmlRendererInstance: RendererInstance = { source: '', renderer: astroHtml as Renderer, options: null, - polyfills: [] + polyfills: [], + hydrationPolyfills: [] }; let rendererInstances: RendererInstance[] = []; @@ -90,13 +93,18 @@ interface HydrateScriptOptions { async function generateHydrateScript({ instance, astroId, props }: HydrateScriptOptions, { hydrate, componentUrl, componentExport }: Required) { const { source } = instance; - const hydrationSource = source ? ` + let hydrationSource = ''; + if(instance.hydrationPolyfills.length) { + hydrationSource += `await Promise.all([${instance.hydrationPolyfills.map(src => `import("${src}")`).join(', ')}]);\n`; + } + + hydrationSource += source ? ` const [{ ${componentExport.value}: Component }, { default: hydrate }] = await Promise.all([import("${componentUrl}"), import("${source}")]); return (el, children) => hydrate(el)(Component, ${serialize(props)}, children); -`.trim() : ` +` : ` await import("${componentUrl}"); return () => {}; -`.trim() +`; const hydrationScript = `