Skip to content

Commit

Permalink
Fix island deduplication ignoring props. (withastro#2825)
Browse files Browse the repository at this point in the history
* Fix island deduplication ignoring props.

Re-resolves an issue initially patched in withastro#846 but seemingly lost in the 0.21.0 mega-merge (withastro@d84bfe7).
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 <[email protected]>
  • Loading branch information
hlynursmari1 and Nate Moore authored Mar 18, 2022
1 parent d13ce50 commit 7d01dbd
Show file tree
Hide file tree
Showing 7 changed files with 36 additions and 15 deletions.
6 changes: 3 additions & 3 deletions src/runtime/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/react-component/src/components/Hello.jsx
Original file line number Diff line number Diff line change
@@ -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>;
}
7 changes: 6 additions & 1 deletion test/fixtures/react-component/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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}/>
Expand Down
9 changes: 7 additions & 2 deletions test/fixtures/vue-component/src/components/Counter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/vue-component/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
11 changes: 9 additions & 2 deletions test/react-component.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 () => {
Expand Down
10 changes: 5 additions & 5 deletions test/vue-component.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

Expand Down

0 comments on commit 7d01dbd

Please sign in to comment.