From fd80381db2c8cbc8ec4c2a3b3cd6baa4f090e912 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 1 Jul 2021 10:42:56 -0400 Subject: [PATCH] Adds hydrationPolyfills config for renderers (#594) Some renderers, such as Lit, need special polyfills only for hydration. We have the `polyfills` array, but that is intended for polyfills that always need to run. This adds a second type hydrationPolyfills that only run on elements that are `:load`, `:idle`, etc. --- docs/core-concepts/ui-renderers.md | 1 + packages/astro/src/config_manager.ts | 10 +++++++--- .../astro/src/internal/__astro_component.ts | 18 +++++++++++++----- packages/astro/src/runtime.ts | 4 +++- packages/astro/test/custom-elements.test.js | 2 ++ .../my-component-lib/hydration-polyfill.js | 2 ++ .../custom-elements/my-component-lib/index.js | 3 +++ 7 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 packages/astro/test/fixtures/custom-elements/my-component-lib/hydration-polyfill.js 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 = `