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.
This commit is contained in:
Matthew Phillips 2021-07-01 10:42:56 -04:00 committed by GitHub
parent 0de30ef01a
commit fd80381db2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 31 additions and 9 deletions

View file

@ -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.
};
```

View file

@ -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}

View file

@ -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<AstroComponentProps>) {
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 = `<script type="module">
import setup from '/_astro_frontend/hydrate/${hydrate}.js';

View file

@ -364,7 +364,7 @@ async function createSnowpack(astroConfig: AstroConfig, options: CreateSnowpackO
// Make sure that Snowpack builds our renderer plugins
const rendererInstances = await configManager.buildRendererInstances();
const knownEntrypoints: string[] = ['astro/dist/internal/__astro_component.js'];
const knownEntrypoints: string[] = ['astro/dist/internal/__astro_component.js', 'astro/dist/internal/element-registry.js'];
for (const renderer of rendererInstances) {
knownEntrypoints.push(renderer.server);
if(renderer.client) {
@ -373,6 +373,8 @@ async function createSnowpack(astroConfig: AstroConfig, options: CreateSnowpackO
if (renderer.knownEntrypoints) {
knownEntrypoints.push(...renderer.knownEntrypoints);
}
knownEntrypoints.push(...renderer.polyfills);
knownEntrypoints.push(...renderer.hydrationPolyfills);
}
const external = snowpackExternals.concat([]);
for(const renderer of rendererInstances) {

View file

@ -49,6 +49,7 @@ CustomElements('Polyfills are added before the hydration script', async ({ runti
assert.equal($('script[type=module]').length, 2);
assert.equal($('script[type=module]').attr('src'), '/_snowpack/link/packages/astro/test/fixtures/custom-elements/my-component-lib/polyfill.js');
assert.match($($('script[type=module]').get(1)).html(), new RegExp('/_snowpack/link/packages/astro/test/fixtures/custom-elements/my-component-lib/hydration-polyfill.js'));
});
CustomElements('Polyfills are added even if not hydrating', async ({ runtime }) => {
@ -60,6 +61,7 @@ CustomElements('Polyfills are added even if not hydrating', async ({ runtime })
assert.equal($('script[type=module]').length, 1);
assert.equal($('script[type=module]').attr('src'), '/_snowpack/link/packages/astro/test/fixtures/custom-elements/my-component-lib/polyfill.js');
assert.not.match($($('script[type=module]').get(1)).html(), new RegExp('/_snowpack/link/packages/astro/test/fixtures/custom-elements/my-component-lib/hydration-polyfill.js'));
});
CustomElements('Custom elements not claimed by renderer are rendered as regular HTML', async ({ runtime }) => {

View file

@ -0,0 +1,2 @@
globalThis.somePolyfillHere = '';

View file

@ -4,5 +4,8 @@ export default {
server: './server',
polyfills: [
'./polyfill.js'
],
hydrationPolyfills: [
'./hydration-polyfill.js'
]
};