Fix island deduplication ignoring props. (#2825)

* Fix island deduplication ignoring props.

Re-resolves an issue initially patched in https://github.com/withastro/astro/pull/846 but seemingly lost in the 0.21.0 mega-merge (d84bfe719a).
This change makes the component render step account for all props, even if they don't affect the generated HTML, when deduplicating island mount.

* Fix React component test using different rendered props to test deduplication.

* fix: improve serialization support for non-JSON objects

Co-authored-by: Nate Moore <nate@skypack.dev>
This commit is contained in:
Hlynur Sveinbjornsson 2022-03-18 09:00:14 -04:00 committed by GitHub
parent 0af017b9fc
commit 1cd7184ca6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 41 additions and 15 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fix island deduplication ignoring props.Re-resolves an issue initially patched in https://github.com/withastro/astro/pull/846 but seemingly lost in the 0.21.0 mega-merge (https://github.com/withastro/astro/commit/d84bfe719a546ad855640338d5ed49ad3aa4ccb4).This change makes the component render step account for all props, even if they don't affect the generated HTML, when deduplicating island mounts.

View file

@ -3,7 +3,7 @@ import type { AstroGlobalPartial, SSRResult, SSRElement } from '../../@types/ast
import type { AstroRequest } from '../../core/render/request';
import shorthash from 'shorthash';
import { extractDirectives, generateHydrateScript } from './hydration.js';
import { extractDirectives, generateHydrateScript, serializeProps } from './hydration.js';
import { serializeListValue } from './util.js';
import { escapeHTML, HTMLString, markHTMLString } from './escape.js';
@ -278,8 +278,8 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
return markHTMLString(html.replace(/\<\/?astro-fragment\>/g, ''));
}
// Include componentExport name and componentUrl in hash to dedupe identical islands
const astroId = shorthash.unique(`<!--${metadata.componentExport!.value}:${metadata.componentUrl}-->\n${html}`);
// Include componentExport name, componentUrl, and props in hash to dedupe identical islands
const astroId = shorthash.unique(`<!--${metadata.componentExport!.value}:${metadata.componentUrl}-->\n${html}\n${serializeProps(props)}`);
// Rather than appending this inline in the page, puts this into the `result.scripts` set that will be appended to the head.
// INVESTIGATE: This will likely be a problem in streaming because the `<head>` will be gone at this point.

View file

@ -1,5 +1,5 @@
import React from 'react';
export default function ({ name }) {
return <h2 id="react-h2">Hello {name}!</h2>;
export default function ({ name, unused }) {
return <h2 id={`react-${name}`}>Hello {name}!</h2>;
}

View file

@ -17,7 +17,12 @@ const someProps = {
<!-- Head Stuff -->
</head>
<body>
<Hello name="world" />
<Hello name="static" />
<Hello name="load" client:load />
<!-- Test island deduplication, i.e. same UID as the component above. -->
<Hello name="load" client:load />
<!-- Test island deduplication account for non-render affecting props. -->
<Hello name="load" unused="" client:load />
<Later name="baby" />
<ArrowFunction />
<PropsSpread {...someProps}/>

View file

@ -20,12 +20,17 @@ export default {
start: {
type: String,
required: true
},
stepSize: {
type: String,
default: "1"
}
},
setup(props) {
const count = ref(parseInt(props.start))
const add = () => (count.value = count.value + 1);
const subtract = () => (count.value = count.value - 1);
const stepSize = ref(parseInt(props.stepSize))
const add = () => (count.value = count.value + stepSize.value);
const subtract = () => (count.value = count.value - stepSize.value);
return {
count,
add,

View file

@ -20,6 +20,10 @@ import Counter from '../components/Counter.vue'
<main>
<Counter start="0">SSR Rendered, No Client</Counter>
<Counter start="1" client:load>SSR Rendered, client:load</Counter>
<!-- Test island deduplication, i.e. same UID as the component above. -->
<Counter start="1" client:load>SSR Rendered, client:load</Counter>
<!-- Test island deduplication account for non-render affecting props. -->
<Counter start="1" step-size="2" client:load>SSR Rendered, client:load</Counter>
<Counter start="10" client:idle>SSR Rendered, client:idle</Counter>
<!-- Test that two client:visibles have unique uids -->
<Counter start="100" client:visible>SSR Rendered, client:visible</Counter>

View file

@ -25,10 +25,10 @@ describe('React Components', () => {
const $ = cheerio.load(html);
// test 1: basic component renders
expect($('#react-h2').text()).to.equal('Hello world!');
expect($('#react-static').text()).to.equal('Hello static!');
// test 2: no reactroot
expect($('#react-h2').attr('data-reactroot')).to.equal(undefined);
expect($('#react-static').attr('data-reactroot')).to.equal(undefined);
// test 3: Can use function components
expect($('#arrow-fn-component')).to.have.lengthOf(1);
@ -44,6 +44,13 @@ describe('React Components', () => {
// test 7: Can use Pure components
expect($('#pure')).to.have.lengthOf(1);
// test 8: Check number of islands
expect($('astro-root[uid]')).to.have.lengthOf(5);
// test 9: Check island deduplication
const uniqueRootUIDs = new Set($('astro-root').map((i, el) => $(el).attr('uid')));
expect(uniqueRootUIDs.size).to.equal(4);
});
it('Can load Vue', async () => {

View file

@ -29,17 +29,17 @@ describe('Vue component', () => {
.map((el) => $(el).text());
// test 1: renders all components correctly
expect(allPreValues).to.deep.equal(['0', '1', '10', '100', '1000']);
expect(allPreValues).to.deep.equal(['0', '1', '1', '1', '10', '100', '1000']);
// test 2: renders 3 <astro-root>s
expect($('astro-root')).to.have.lengthOf(4);
expect($('astro-root')).to.have.lengthOf(6);
// test 3: all <astro-root>s have uid attributes
expect($('astro-root[uid]')).to.have.lengthOf(4);
expect($('astro-root[uid]')).to.have.lengthOf(6);
// test 5: all <astro-root>s have unique uid attributes
// test 5: components with identical render output and props have been deduplicated
const uniqueRootUIDs = $('astro-root').map((i, el) => $(el).attr('uid'));
expect(new Set(uniqueRootUIDs).size).to.equal(4);
expect(new Set(uniqueRootUIDs).size).to.equal(5);
});
});