diff options
8 files changed, 41 insertions, 15 deletions
| diff --git a/.changeset/silent-walls-tan.md b/.changeset/silent-walls-tan.md new file mode 100644 index 000000000..1eb3a97a4 --- /dev/null +++ b/.changeset/silent-walls-tan.md @@ -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. diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index 7d7088ad8..da510f2fb 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -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. diff --git a/packages/astro/test/fixtures/react-component/src/components/Hello.jsx b/packages/astro/test/fixtures/react-component/src/components/Hello.jsx index 4b6c416a9..4c241162d 100644 --- a/packages/astro/test/fixtures/react-component/src/components/Hello.jsx +++ b/packages/astro/test/fixtures/react-component/src/components/Hello.jsx @@ -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>;  } diff --git a/packages/astro/test/fixtures/react-component/src/pages/index.astro b/packages/astro/test/fixtures/react-component/src/pages/index.astro index 597a4a546..049b5fb6a 100644 --- a/packages/astro/test/fixtures/react-component/src/pages/index.astro +++ b/packages/astro/test/fixtures/react-component/src/pages/index.astro @@ -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}/> diff --git a/packages/astro/test/fixtures/vue-component/src/components/Counter.vue b/packages/astro/test/fixtures/vue-component/src/components/Counter.vue index 49d2936ed..8cbb0f550 100644 --- a/packages/astro/test/fixtures/vue-component/src/components/Counter.vue +++ b/packages/astro/test/fixtures/vue-component/src/components/Counter.vue @@ -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, diff --git a/packages/astro/test/fixtures/vue-component/src/pages/index.astro b/packages/astro/test/fixtures/vue-component/src/pages/index.astro index eed6624cc..7cae724dc 100644 --- a/packages/astro/test/fixtures/vue-component/src/pages/index.astro +++ b/packages/astro/test/fixtures/vue-component/src/pages/index.astro @@ -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> diff --git a/packages/astro/test/react-component.test.js b/packages/astro/test/react-component.test.js index a05f76cf9..8e90f88ae 100644 --- a/packages/astro/test/react-component.test.js +++ b/packages/astro/test/react-component.test.js @@ -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 () => { diff --git a/packages/astro/test/vue-component.test.js b/packages/astro/test/vue-component.test.js index 46edcfbac..7b226b911 100644 --- a/packages/astro/test/vue-component.test.js +++ b/packages/astro/test/vue-component.test.js @@ -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);  		});  	}); | 
