diff options
Diffstat (limited to '')
61 files changed, 527 insertions, 75 deletions
diff --git a/packages/integrations/cloudflare/test/basics.test.js b/packages/integrations/cloudflare/test/basics.test.js index 9aa78f98e..c27b6be6c 100644 --- a/packages/integrations/cloudflare/test/basics.test.js +++ b/packages/integrations/cloudflare/test/basics.test.js @@ -14,7 +14,7 @@ describe('Basic app', () => { }); await fixture.build(); - cli = runCLI('./fixtures/basics/', { silent: true, port: 8789 }); + cli = await runCLI('./fixtures/basics/', { silent: true, port: 8789 }); await cli.ready; }); @@ -23,7 +23,7 @@ describe('Basic app', () => { }); it('can render', async () => { - let res = await fetch(`http://localhost:8789/`); + let res = await fetch(`http://127.0.0.1:8789/`); expect(res.status).to.equal(200); let html = await res.text(); let $ = cheerio.load(html); diff --git a/packages/integrations/cloudflare/test/cf.test.js b/packages/integrations/cloudflare/test/cf.test.js index f8ab9c02f..ec0e52c97 100644 --- a/packages/integrations/cloudflare/test/cf.test.js +++ b/packages/integrations/cloudflare/test/cf.test.js @@ -17,7 +17,7 @@ describe('Cf metadata and caches', () => { }); await fixture.build(); - cli = runCLI('./fixtures/cf/', { silent: false, port: 8788 }); + cli = await runCLI('./fixtures/cf/', { silent: false, port: 8788 }); await cli.ready; }); @@ -26,12 +26,12 @@ describe('Cf metadata and caches', () => { }); it('Load cf and caches API', async () => { - let res = await fetch(`http://localhost:8788/`); + let res = await fetch(`http://127.0.0.1:8788/`); expect(res.status).to.equal(200); let html = await res.text(); let $ = cheerio.load(html); - // console.log($('#cf').text(), html); - expect($('#cf').text()).to.contain('city'); + + expect($('#hasRuntime').text()).to.equal('true'); expect($('#hasCache').text()).to.equal('true'); }); }); diff --git a/packages/integrations/cloudflare/test/fixtures/cf/src/pages/index.astro b/packages/integrations/cloudflare/test/fixtures/cf/src/pages/index.astro index c9e67bb05..6ba48e803 100644 --- a/packages/integrations/cloudflare/test/fixtures/cf/src/pages/index.astro +++ b/packages/integrations/cloudflare/test/fixtures/cf/src/pages/index.astro @@ -8,7 +8,7 @@ const runtime = getRuntime(Astro.request); </head> <body> <h1>Testing</h1> - <div id="cf">{JSON.stringify(runtime.cf)}</div> + <div id="hasRuntime">{!!runtime.cf?.colo}</div> <div id="hasCache">{!!runtime.caches}</div> </body> </html> diff --git a/packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro b/packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro index 320e8e162..aa73ab8ea 100644 --- a/packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro +++ b/packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro @@ -8,8 +8,8 @@ const env = runtime.env; </head> <body> <h1>Testing</h1> - <div id="cf">{JSON.stringify(runtime.cf)}</div> <div id="env">{JSON.stringify(env)}</div> + <div id="hasRuntime">{!!runtime.cf?.colo}</div> <div id="hasCache">{!!runtime.caches}</div> </body> </html> diff --git a/packages/integrations/cloudflare/test/runtime.test.js b/packages/integrations/cloudflare/test/runtime.test.js index 243c1dd67..be14718e8 100644 --- a/packages/integrations/cloudflare/test/runtime.test.js +++ b/packages/integrations/cloudflare/test/runtime.test.js @@ -17,7 +17,7 @@ describe('Runtime Locals', () => { }); await fixture.build(); - cli = runCLI('./fixtures/runtime/', { silent: true, port: 8793 }); + cli = await runCLI('./fixtures/runtime/', { silent: true, port: 8793 }); await cli.ready; }); @@ -26,13 +26,13 @@ describe('Runtime Locals', () => { }); it('has CF and Caches', async () => { - let res = await fetch(`http://localhost:8793/`); + let res = await fetch(`http://127.0.0.1:8793/`); expect(res.status).to.equal(200); let html = await res.text(); let $ = cheerio.load(html); - expect($('#cf').text()).to.contain('city'); expect($('#env').text()).to.contain('SECRET_STUFF'); expect($('#env').text()).to.contain('secret'); + expect($('#hasRuntime').text()).to.contain('true'); expect($('#hasCache').text()).to.equal('true'); }); }); diff --git a/packages/integrations/cloudflare/test/test-utils.js b/packages/integrations/cloudflare/test/test-utils.js index 90147a7f6..36515f831 100644 --- a/packages/integrations/cloudflare/test/test-utils.js +++ b/packages/integrations/cloudflare/test/test-utils.js @@ -1,5 +1,6 @@ import { spawn } from 'node:child_process'; import { fileURLToPath } from 'node:url'; +import kill from 'kill-port'; import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js'; export { fixLineEndings } from '../../../astro/test/test-utils.js'; @@ -21,22 +22,39 @@ const wranglerPath = fileURLToPath( ); /** - * @returns {WranglerCLI} + * @returns {Promise<WranglerCLI>} */ -export function runCLI(basePath, { silent, port = 8787 }) { +export async function runCLI(basePath, { silent, port }) { + // Hack: force existing process on port to be killed + try { + await kill(port, 'tcp'); + } catch { + // Will throw if port is not in use, but that's fine + } + const script = fileURLToPath(new URL(`${basePath}/dist/_worker.js`, import.meta.url)); - const p = spawn('node', [wranglerPath, 'dev', '-l', script, '--port', port]); + const p = spawn('node', [ + wranglerPath, + 'dev', + script, + '--port', + port, + '--log-level', + 'info', + '--persist-to', + `${basePath}/.wrangler/state`, + ]); p.stderr.setEncoding('utf-8'); p.stdout.setEncoding('utf-8'); - const timeout = 10000; + const timeout = 20_000; const ready = new Promise(async (resolve, reject) => { - const failed = setTimeout( - () => reject(new Error(`Timed out starting the wrangler CLI`)), - timeout - ); + const failed = setTimeout(() => { + p.kill(); + reject(new Error(`Timed out starting the wrangler CLI`)); + }, timeout); (async function () { for (const msg of p.stderr) { @@ -50,7 +68,7 @@ export function runCLI(basePath, { silent, port = 8787 }) { if (!silent) { console.log(msg); } - if (msg.includes(`Listening on`)) { + if (msg.includes(`[mf:inf] Ready on`)) { break; } } diff --git a/packages/integrations/cloudflare/test/with-solid-js.test.js b/packages/integrations/cloudflare/test/with-solid-js.test.js index 90c1c0722..c091d04b3 100644 --- a/packages/integrations/cloudflare/test/with-solid-js.test.js +++ b/packages/integrations/cloudflare/test/with-solid-js.test.js @@ -14,7 +14,7 @@ describe('With SolidJS', () => { }); await fixture.build(); - cli = runCLI('./fixtures/with-solid-js/', { silent: true, port: 8790 }); + cli = await runCLI('./fixtures/with-solid-js/', { silent: true, port: 8790 }); await cli.ready; }); @@ -23,7 +23,7 @@ describe('With SolidJS', () => { }); it('renders the solid component', async () => { - let res = await fetch(`http://localhost:8790/`); + let res = await fetch(`http://127.0.0.1:8790/`); expect(res.status).to.equal(200); let html = await res.text(); let $ = cheerio.load(html); diff --git a/packages/integrations/cloudflare/test/wrangler.toml b/packages/integrations/cloudflare/test/wrangler.toml index 6e2d864b0..2c1acb55a 100644 --- a/packages/integrations/cloudflare/test/wrangler.toml +++ b/packages/integrations/cloudflare/test/wrangler.toml @@ -1,4 +1,6 @@ # for tests only +send_metrics = false + [vars] SECRET_STUFF = "secret" diff --git a/packages/integrations/node/CHANGELOG.md b/packages/integrations/node/CHANGELOG.md index 01e61307f..95b8e8ad1 100644 --- a/packages/integrations/node/CHANGELOG.md +++ b/packages/integrations/node/CHANGELOG.md @@ -39,6 +39,15 @@ - Updated dependencies [[`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81), [`76ddef19c`](https://github.com/withastro/astro/commit/76ddef19ccab6e5f7d3a5740cd41acf10e334b38), [`9b4f70a62`](https://github.com/withastro/astro/commit/9b4f70a629f55e461759ba46f68af7097a2e9215), [`3fdf509b2`](https://github.com/withastro/astro/commit/3fdf509b2731a9b2f972d89291e57cf78d62c769), [`2f951cd40`](https://github.com/withastro/astro/commit/2f951cd403dfcc2c3ca6aae618ae3e1409516e32), [`c022a4217`](https://github.com/withastro/astro/commit/c022a4217a805d223c1494e9eda4e48bbf810388), [`67becaa58`](https://github.com/withastro/astro/commit/67becaa580b8f787df58de66b7008b7098f1209c), [`bc37331d8`](https://github.com/withastro/astro/commit/bc37331d8154e3e95a8df9131e4e014e78a7a9e7), [`dfc2d93e3`](https://github.com/withastro/astro/commit/dfc2d93e3c645995379358fabbdfa9aab99f43d8), [`3dc1ca2fa`](https://github.com/withastro/astro/commit/3dc1ca2fac8d9965cc5085a5d09e72ed87b4281a), [`1be84dfee`](https://github.com/withastro/astro/commit/1be84dfee3ce8e6f5cc624f99aec4e980f6fde37), [`35f01df79`](https://github.com/withastro/astro/commit/35f01df797d23315f2bee2fc3fd795adb0559c58), [`3fdf509b2`](https://github.com/withastro/astro/commit/3fdf509b2731a9b2f972d89291e57cf78d62c769), [`78de801f2`](https://github.com/withastro/astro/commit/78de801f21fd4ca1653950027d953bf08614566b), [`59d6e569f`](https://github.com/withastro/astro/commit/59d6e569f63e175c97e82e94aa7974febfb76f7c), [`7723c4cc9`](https://github.com/withastro/astro/commit/7723c4cc93298c2e6530e55da7afda048f22cf81), [`fb5cd6b56`](https://github.com/withastro/astro/commit/fb5cd6b56dc27a71366ed5e1ab8bfe9b8f96bac5), [`631b9c410`](https://github.com/withastro/astro/commit/631b9c410d5d66fa384674027ba95d69ebb5063f)]: - astro@3.0.0-beta.0 +## 5.3.4 + +### Patch Changes + +- [#8084](https://github.com/withastro/astro/pull/8084) [`560e45924`](https://github.com/withastro/astro/commit/560e45924622141206ff5b47d134cb343d6d2a71) Thanks [@hbgl](https://github.com/hbgl)! - Stream request body instead of buffering it in memory. + +- Updated dependencies [[`c19987df0`](https://github.com/withastro/astro/commit/c19987df0be3520cf774476cea270c03edd08354), [`560e45924`](https://github.com/withastro/astro/commit/560e45924622141206ff5b47d134cb343d6d2a71), [`afc45af20`](https://github.com/withastro/astro/commit/afc45af2022f7c43fbb6c5c04983695f3819e47e), [`d1f7143f9`](https://github.com/withastro/astro/commit/d1f7143f9caf2ffa0e87cc55c0e05339d3501db3), [`3e46634fd`](https://github.com/withastro/astro/commit/3e46634fd540e5b967d2e5c9abd6235452cee2f2), [`a12027b6a`](https://github.com/withastro/astro/commit/a12027b6af411be39700919ca47e240a335e9887)]: + - astro@2.10.8 + ## 5.3.3 ### Patch Changes diff --git a/packages/integrations/node/package.json b/packages/integrations/node/package.json index 999ce294a..c3ef49637 100644 --- a/packages/integrations/node/package.json +++ b/packages/integrations/node/package.json @@ -48,6 +48,7 @@ "chai": "^4.3.7", "cheerio": "1.0.0-rc.12", "mocha": "^9.2.2", - "node-mocks-http": "^1.12.2" + "node-mocks-http": "^1.13.0", + "undici": "^5.22.1" } } diff --git a/packages/integrations/node/test/api-route.test.js b/packages/integrations/node/test/api-route.test.js index 7fbd95776..c830eee2d 100644 --- a/packages/integrations/node/test/api-route.test.js +++ b/packages/integrations/node/test/api-route.test.js @@ -1,6 +1,7 @@ import nodejs from '../dist/index.js'; import { loadFixture, createRequestAndResponse } from './test-utils.js'; import { expect } from 'chai'; +import crypto from 'node:crypto'; describe('API routes', () => { /** @type {import('./test-utils').Fixture} */ @@ -22,9 +23,11 @@ describe('API routes', () => { url: '/recipes', }); - handler(req, res); + req.once('async_iterator', () => { + req.send(JSON.stringify({ id: 2 })); + }); - req.send(JSON.stringify({ id: 2 })); + handler(req, res); let [buffer] = await done; @@ -43,11 +46,47 @@ describe('API routes', () => { url: '/binary', }); + req.once('async_iterator', () => { + req.send(Buffer.from(new Uint8Array([1, 2, 3, 4, 5]))); + }); + handler(req, res); - req.send(Buffer.from(new Uint8Array([1, 2, 3, 4, 5]))); let [out] = await done; let arr = Array.from(new Uint8Array(out.buffer)); expect(arr).to.deep.equal([5, 4, 3, 2, 1]); }); + + it('Can post large binary data', async () => { + const { handler } = await import('./fixtures/api-route/dist/server/entry.mjs'); + + let { req, res, done } = createRequestAndResponse({ + method: 'POST', + url: '/hash', + }); + + handler(req, res); + + let expectedDigest = null; + req.once('async_iterator', () => { + // Send 256MB of garbage data in 256KB chunks. This should be fast (< 1sec). + let remainingBytes = 256 * 1024 * 1024; + const chunkSize = 256 * 1024; + + const hash = crypto.createHash('sha256'); + while (remainingBytes > 0) { + const size = Math.min(remainingBytes, chunkSize); + const chunk = Buffer.alloc(size, Math.floor(Math.random() * 256)); + hash.update(chunk); + req.emit('data', chunk); + remainingBytes -= size; + } + + req.emit('end'); + expectedDigest = hash.digest(); + }); + + let [out] = await done; + expect(new Uint8Array(out.buffer)).to.deep.equal(expectedDigest); + }); }); diff --git a/packages/integrations/node/test/fixtures/api-route/src/pages/hash.ts b/packages/integrations/node/test/fixtures/api-route/src/pages/hash.ts new file mode 100644 index 000000000..fbf44c547 --- /dev/null +++ b/packages/integrations/node/test/fixtures/api-route/src/pages/hash.ts @@ -0,0 +1,16 @@ +import crypto from 'node:crypto'; + +export async function post({ request }: { request: Request }) { + const hash = crypto.createHash('sha256'); + + const iterable = request.body as unknown as AsyncIterable<Uint8Array>; + for await (const chunk of iterable) { + hash.update(chunk); + } + + return new Response(hash.digest(), { + headers: { + 'Content-Type': 'application/octet-stream' + } + }); +} diff --git a/packages/integrations/node/test/fixtures/prerender-404/package.json b/packages/integrations/node/test/fixtures/prerender-404-500/package.json index dfd109c91..f962fe991 100644 --- a/packages/integrations/node/test/fixtures/prerender-404/package.json +++ b/packages/integrations/node/test/fixtures/prerender-404-500/package.json @@ -1,7 +1,8 @@ { - "name": "@test/nodejs-prerender-404", + "name": "@test/nodejs-prerender-404-500", "version": "0.0.0", "private": true, + "type": "module", "dependencies": { "astro": "workspace:*", "@astrojs/node": "workspace:*" diff --git a/packages/integrations/node/test/fixtures/prerender-404-500/src/external-stylesheet.css b/packages/integrations/node/test/fixtures/prerender-404-500/src/external-stylesheet.css new file mode 100644 index 000000000..5f331948a --- /dev/null +++ b/packages/integrations/node/test/fixtures/prerender-404-500/src/external-stylesheet.css @@ -0,0 +1,3 @@ +body { + background-color: ivory; +} diff --git a/packages/integrations/node/test/fixtures/prerender-404-500/src/nondeterminism-404.ts b/packages/integrations/node/test/fixtures/prerender-404-500/src/nondeterminism-404.ts new file mode 100644 index 000000000..1795c26b0 --- /dev/null +++ b/packages/integrations/node/test/fixtures/prerender-404-500/src/nondeterminism-404.ts @@ -0,0 +1,17 @@ +// This module is only used by the prerendered 404.astro. +// It exhibits different behavior if it's called more than once, +// which is detected by a test and interpreted as a failure. + +let usedOnce = false +let dynamicMessage = "Page was not prerendered" + +export default function () { + if (usedOnce === false) { + usedOnce = true + return "Page does not exist" + } + + dynamicMessage += "+" + + return dynamicMessage +} diff --git a/packages/integrations/node/test/fixtures/prerender-404-500/src/nondeterminism-500.ts b/packages/integrations/node/test/fixtures/prerender-404-500/src/nondeterminism-500.ts new file mode 100644 index 000000000..8f8024a60 --- /dev/null +++ b/packages/integrations/node/test/fixtures/prerender-404-500/src/nondeterminism-500.ts @@ -0,0 +1,17 @@ +// This module is only used by the prerendered 500.astro. +// It exhibits different behavior if it's called more than once, +// which is detected by a test and interpreted as a failure. + +let usedOnce = false +let dynamicMessage = "Page was not prerendered" + +export default function () { + if (usedOnce === false) { + usedOnce = true + return "Something went wrong" + } + + dynamicMessage += "+" + + return dynamicMessage +} diff --git a/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/404.astro b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/404.astro new file mode 100644 index 000000000..37fd1c1d3 --- /dev/null +++ b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/404.astro @@ -0,0 +1,5 @@ +--- +import message from "../nondeterminism-404" +export const prerender = true; +--- +{message()} diff --git a/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/500.astro b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/500.astro new file mode 100644 index 000000000..ef91ad0ff --- /dev/null +++ b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/500.astro @@ -0,0 +1,6 @@ +--- +import "../external-stylesheet.css" +import message from "../nondeterminism-500" +export const prerender = true +--- +<h1>{message()}</h1> diff --git a/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/fivehundred.astro b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/fivehundred.astro new file mode 100644 index 000000000..99d103567 --- /dev/null +++ b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/fivehundred.astro @@ -0,0 +1,4 @@ +--- +return new Response(null, { status: 500 }) +--- +<p>This html will not be served</p> diff --git a/packages/integrations/node/test/fixtures/prerender-404/src/pages/static.astro b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/static.astro index af6bad2fb..af6bad2fb 100644 --- a/packages/integrations/node/test/fixtures/prerender-404/src/pages/static.astro +++ b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/static.astro diff --git a/packages/integrations/node/test/fixtures/prerender-404/src/pages/404.astro b/packages/integrations/node/test/fixtures/prerender-404/src/pages/404.astro deleted file mode 100644 index 230402bbc..000000000 --- a/packages/integrations/node/test/fixtures/prerender-404/src/pages/404.astro +++ /dev/null @@ -1,5 +0,0 @@ ---- -export const prerender = true; ---- - -Page does not exist diff --git a/packages/integrations/node/test/prerender-404.test.js b/packages/integrations/node/test/prerender-404-500.test.js index 3a39a9470..8816ebe4c 100644 --- a/packages/integrations/node/test/prerender-404.test.js +++ b/packages/integrations/node/test/prerender-404-500.test.js @@ -9,10 +9,11 @@ import * as cheerio from 'cheerio'; async function load() { const mod = await import( - `./fixtures/prerender-404/dist/server/entry.mjs?dropcache=${Date.now()}` + `./fixtures/prerender-404-500/dist/server/entry.mjs?dropcache=${Date.now()}` ); return mod; } + describe('Prerender 404', () => { /** @type {import('./test-utils').Fixture} */ let fixture; @@ -24,8 +25,12 @@ describe('Prerender 404', () => { process.env.PRERENDER = true; fixture = await loadFixture({ + // inconsequential config that differs between tests + // to bust cache and prevent modules and their state + // from being reused + site: 'https://test.dev/', base: '/some-base', - root: './fixtures/prerender-404/', + root: './fixtures/prerender-404-500/', output: 'server', adapter: nodejs({ mode: 'standalone' }), }); @@ -51,13 +56,53 @@ describe('Prerender 404', () => { }); it('Can handle prerendered 404', async () => { - const res = await fetch(`http://${server.host}:${server.port}/some-base/missing`); - const html = await res.text(); - const $ = cheerio.load(html); + const url = `http://${server.host}:${server.port}/some-base/missing`; + const res1 = await fetch(url); + const res2 = await fetch(url); + const res3 = await fetch(url); + + expect(res1.status).to.equal(404); + expect(res2.status).to.equal(404); + expect(res3.status).to.equal(404); + + const html1 = await res1.text(); + const html2 = await res2.text(); + const html3 = await res3.text(); + + expect(html1).to.equal(html2); + expect(html2).to.equal(html3); + + const $ = cheerio.load(html1); - expect(res.status).to.equal(404); expect($('body').text()).to.equal('Page does not exist'); }); + + it(' Can handle prerendered 500 called indirectly', async () => { + const url = `http://${server.host}:${server.port}/some-base/fivehundred`; + const response1 = await fetch(url); + const response2 = await fetch(url); + const response3 = await fetch(url); + + expect(response1.status).to.equal(500); + + const html1 = await response1.text(); + const html2 = await response2.text(); + const html3 = await response3.text(); + + expect(html1).to.contain('Something went wrong'); + + expect(html1).to.equal(html2); + expect(html2).to.equal(html3); + }); + + it('prerendered 500 page includes expected styles', async () => { + const response = await fetch(`http://${server.host}:${server.port}/some-base/fivehundred`); + const html = await response.text(); + const $ = cheerio.load(html); + + // length will be 0 if the stylesheet does not get included + expect($('link[rel=stylesheet]')).to.have.a.lengthOf(1); + }); }); describe('Without base', async () => { @@ -66,12 +111,16 @@ describe('Prerender 404', () => { process.env.PRERENDER = true; fixture = await loadFixture({ - root: './fixtures/prerender-404/', + // inconsequential config that differs between tests + // to bust cache and prevent modules and their state + // from being reused + site: 'https://test.info/', + root: './fixtures/prerender-404-500/', output: 'server', adapter: nodejs({ mode: 'standalone' }), }); await fixture.build(); - const { startServer } = await await load(); + const { startServer } = await load(); let res = startServer(); server = res.server; }); @@ -92,11 +141,24 @@ describe('Prerender 404', () => { }); it('Can handle prerendered 404', async () => { - const res = await fetch(`http://${server.host}:${server.port}/missing`); - const html = await res.text(); - const $ = cheerio.load(html); + const url = `http://${server.host}:${server.port}/some-base/missing`; + const res1 = await fetch(url); + const res2 = await fetch(url); + const res3 = await fetch(url); + + expect(res1.status).to.equal(404); + expect(res2.status).to.equal(404); + expect(res3.status).to.equal(404); + + const html1 = await res1.text(); + const html2 = await res2.text(); + const html3 = await res3.text(); + + expect(html1).to.equal(html2); + expect(html2).to.equal(html3); + + const $ = cheerio.load(html1); - expect(res.status).to.equal(404); expect($('body').text()).to.equal('Page does not exist'); }); }); @@ -112,13 +174,17 @@ describe('Hybrid 404', () => { process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = false; fixture = await loadFixture({ + // inconsequential config that differs between tests + // to bust cache and prevent modules and their state + // from being reused + site: 'https://test.com/', base: '/some-base', - root: './fixtures/prerender-404/', + root: './fixtures/prerender-404-500/', output: 'hybrid', adapter: nodejs({ mode: 'standalone' }), }); await fixture.build(); - const { startServer } = await await load(); + const { startServer } = await load(); let res = startServer(); server = res.server; }); @@ -139,11 +205,24 @@ describe('Hybrid 404', () => { }); it('Can handle prerendered 404', async () => { - const res = await fetch(`http://${server.host}:${server.port}/some-base/missing`); - const html = await res.text(); - const $ = cheerio.load(html); + const url = `http://${server.host}:${server.port}/some-base/missing`; + const res1 = await fetch(url); + const res2 = await fetch(url); + const res3 = await fetch(url); + + expect(res1.status).to.equal(404); + expect(res2.status).to.equal(404); + expect(res3.status).to.equal(404); + + const html1 = await res1.text(); + const html2 = await res2.text(); + const html3 = await res3.text(); + + expect(html1).to.equal(html2); + expect(html2).to.equal(html3); + + const $ = cheerio.load(html1); - expect(res.status).to.equal(404); expect($('body').text()).to.equal('Page does not exist'); }); }); @@ -153,12 +232,16 @@ describe('Hybrid 404', () => { process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = false; fixture = await loadFixture({ - root: './fixtures/prerender-404/', + // inconsequential config that differs between tests + // to bust cache and prevent modules and their state + // from being reused + site: 'https://test.net/', + root: './fixtures/prerender-404-500/', output: 'hybrid', adapter: nodejs({ mode: 'standalone' }), }); await fixture.build(); - const { startServer } = await await load(); + const { startServer } = await load(); let res = startServer(); server = res.server; }); @@ -179,11 +262,24 @@ describe('Hybrid 404', () => { }); it('Can handle prerendered 404', async () => { - const res = await fetch(`http://${server.host}:${server.port}/missing`); - const html = await res.text(); - const $ = cheerio.load(html); + const url = `http://${server.host}:${server.port}/missing`; + const res1 = await fetch(url); + const res2 = await fetch(url); + const res3 = await fetch(url); + + expect(res1.status).to.equal(404); + expect(res2.status).to.equal(404); + expect(res3.status).to.equal(404); + + const html1 = await res1.text(); + const html2 = await res2.text(); + const html3 = await res3.text(); + + expect(html1).to.equal(html2); + expect(html2).to.equal(html3); + + const $ = cheerio.load(html1); - expect(res.status).to.equal(404); expect($('body').text()).to.equal('Page does not exist'); }); }); diff --git a/packages/integrations/react/CHANGELOG.md b/packages/integrations/react/CHANGELOG.md index cf056a58f..146d36427 100644 --- a/packages/integrations/react/CHANGELOG.md +++ b/packages/integrations/react/CHANGELOG.md @@ -23,6 +23,12 @@ - [`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Remove support for Node 16. The lowest supported version by Astro and all integrations is now v18.14.1. As a reminder, Node 16 will be deprecated on the 11th September 2023. +## 2.2.2 + +### Patch Changes + +- [#8075](https://github.com/withastro/astro/pull/8075) [`da517d405`](https://github.com/withastro/astro/commit/da517d4055825ee1b630cd4a6983818d6120a7b7) Thanks [@SudoCat](https://github.com/SudoCat)! - fix a bug where react identifierPrefix was set to null for client:only components causing React.useId to generate ids prefixed with null + ## 2.2.1 ### Patch Changes diff --git a/packages/integrations/react/README.md b/packages/integrations/react/README.md index 48c45881f..8009972b3 100644 --- a/packages/integrations/react/README.md +++ b/packages/integrations/react/README.md @@ -61,6 +61,46 @@ To use your first React component in Astro, head to our [UI framework documentat - 💧 client-side hydration options, and - 🤝 opportunities to mix and nest frameworks together +## Options + +### Children parsing + +Children passed into a React component from an Astro component are parsed as plain strings, not React nodes. + +For example, the `<ReactComponent />` below will only receive a single child element: + +```astro +--- +import ReactComponent from './ReactComponent'; +--- + +<ReactComponent> + <div>one</div> + <div>two</div> +</ReactComponent> +``` + +If you are using a library that _expects_ more than one child element element to be passed, for example so that it can slot certain elements in different places, you might find this to be a blocker. + +You can set the experimental flag `experimentalReactChildren` to tell Astro to always pass children to React as React vnodes. There is some runtime cost to this, but it can help with compatibility. + +You can enable this option in the configuration for the React integration: + +```js +// astro.config.mjs +import { defineConfig } from 'astro/config'; +import react from '@astrojs/react'; + +export default defineConfig({ + // ... + integrations: [ + react({ + experimentalReactChildren: true, + }), + ], +}); +``` + ## Troubleshooting For help, check out the `#support` channel on [Discord](https://astro.build/chat). Our friendly Support Squad members are here to help! diff --git a/packages/integrations/react/package.json b/packages/integrations/react/package.json index 87fd46f94..302ac4ae0 100644 --- a/packages/integrations/react/package.json +++ b/packages/integrations/react/package.json @@ -45,7 +45,8 @@ }, "dependencies": { "@astrojs/internal-helpers": "0.2.0-beta.1", - "@vitejs/plugin-react": "^4.0.3" + "@vitejs/plugin-react": "^4.0.3", + "ultrahtml": "^1.2.0" }, "devDependencies": { "@types/react": "^17.0.62", @@ -53,7 +54,10 @@ "astro": "workspace:*", "astro-scripts": "workspace:*", "react": "^18.1.0", - "react-dom": "^18.1.0" + "react-dom": "^18.1.0", + "chai": "^4.3.7", + "cheerio": "1.0.0-rc.12", + "vite": "^4.4.6" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21", diff --git a/packages/integrations/react/server.js b/packages/integrations/react/server.js index 8c02c4b26..c2400accb 100644 --- a/packages/integrations/react/server.js +++ b/packages/integrations/react/server.js @@ -2,6 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom/server'; import StaticHtml from './static-html.js'; import { incrementId } from './context.js'; +import opts from 'astro:react:opts'; const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase()); const reactTypeof = Symbol.for('react.element'); @@ -85,7 +86,10 @@ async function renderToStaticMarkup(Component, props, { default: children, ...sl ...slots, }; const newChildren = children ?? props.children; - if (newChildren != null) { + if (children && opts.experimentalReactChildren) { + const convert = await import('./vnode-children.js').then((mod) => mod.default); + newProps.children = convert(children); + } else if (newChildren != null) { newProps.children = React.createElement(StaticHtml, { hydrate: needsHydration(metadata), value: newChildren, diff --git a/packages/integrations/react/src/index.ts b/packages/integrations/react/src/index.ts index f5332e2ed..a318bb4c2 100644 --- a/packages/integrations/react/src/index.ts +++ b/packages/integrations/react/src/index.ts @@ -2,9 +2,13 @@ import type { AstroIntegration } from 'astro'; import { version as ReactVersion } from 'react-dom'; import react, { type Options as ViteReactPluginOptions } from '@vitejs/plugin-react'; import { appendForwardSlash } from '@astrojs/internal-helpers/path'; +import type * as vite from 'vite'; const FAST_REFRESH_PREAMBLE = react.preambleCode; + + + function getRenderer() { return { name: '@astrojs/react', @@ -17,7 +21,29 @@ function getRenderer() { }; } -function getViteConfiguration({ include, exclude }: Options = {}) { +function optionsPlugin(experimentalReactChildren: boolean): vite.Plugin { + const virtualModule = 'astro:react:opts'; + const virtualModuleId = '\0' + virtualModule; + return { + name: '@astrojs/react:opts', + resolveId(id) { + if (id === virtualModule) { + return virtualModuleId; + } + }, + load(id) { + if (id === virtualModuleId) { + return { + code: `export default { + experimentalReactChildren: ${JSON.stringify(experimentalReactChildren)} + }`, + }; + } + }, + }; +} + +function getViteConfiguration(experimentalReactChildren: boolean, { include, exclude }: Options = {}) { return { optimizeDeps: { include: [ @@ -35,7 +61,10 @@ function getViteConfiguration({ include, exclude }: Options = {}) { : '@astrojs/react/server-v17.js', ], }, - plugins: [react({ include, exclude })], + plugins: [ + react({ include, exclude }), + optionsPlugin(experimentalReactChildren) + ], resolve: { dedupe: ['react', 'react-dom', 'react-dom/server'], }, @@ -55,17 +84,22 @@ function getViteConfiguration({ include, exclude }: Options = {}) { }; } -export type Options = Pick<ViteReactPluginOptions, 'include' | 'exclude'>; +export type ReactIntegrationOptions = Pick<ViteReactPluginOptions, 'include' | 'exclude'> & { + experimentalReactChildren: boolean; +}; export default function ({ include, exclude, -}: Pick<ViteReactPluginOptions, 'include' | 'exclude'> = {}): AstroIntegration { + experimentalReactChildren +}: ReactIntegrationOptions = { + experimentalReactChildren: false +}): AstroIntegration { return { name: '@astrojs/react', hooks: { 'astro:config:setup': ({ config, command, addRenderer, updateConfig, injectScript }) => { addRenderer(getRenderer()); - updateConfig({ vite: getViteConfiguration({ include, exclude }) }); + updateConfig({ vite: getViteConfiguration(experimentalReactChildren, { include, exclude }) }); if (command === 'dev') { const preamble = FAST_REFRESH_PREAMBLE.replace( `__BASE__`, diff --git a/packages/astro/test/fixtures/react-component/astro.config.mjs b/packages/integrations/react/test/fixtures/react-component/astro.config.mjs index 53d0bd03b..cd54d60f8 100644 --- a/packages/astro/test/fixtures/react-component/astro.config.mjs +++ b/packages/integrations/react/test/fixtures/react-component/astro.config.mjs @@ -4,5 +4,7 @@ import vue from '@astrojs/vue'; // https://astro.build/config export default defineConfig({ - integrations: [react(), vue()], -});
\ No newline at end of file + integrations: [react({ + experimentalReactChildren: true, + }), vue()], +}); diff --git a/packages/astro/test/fixtures/react-component/package.json b/packages/integrations/react/test/fixtures/react-component/package.json index cf7b2b057..cf7b2b057 100644 --- a/packages/astro/test/fixtures/react-component/package.json +++ b/packages/integrations/react/test/fixtures/react-component/package.json diff --git a/packages/astro/test/fixtures/react-component/src/components/ArrowFunction.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/ArrowFunction.jsx index 16fac5bb6..16fac5bb6 100644 --- a/packages/astro/test/fixtures/react-component/src/components/ArrowFunction.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/ArrowFunction.jsx diff --git a/packages/astro/test/fixtures/react-component/src/components/CloneElement.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/CloneElement.jsx index 809ac4aa4..809ac4aa4 100644 --- a/packages/astro/test/fixtures/react-component/src/components/CloneElement.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/CloneElement.jsx diff --git a/packages/astro/test/fixtures/react-component/src/components/ForgotImport.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/ForgotImport.jsx index 9ee27faca..9ee27faca 100644 --- a/packages/astro/test/fixtures/react-component/src/components/ForgotImport.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/ForgotImport.jsx diff --git a/packages/astro/test/fixtures/react-component/src/components/GetSearch.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/GetSearch.jsx index d3fee2f9a..d3fee2f9a 100644 --- a/packages/astro/test/fixtures/react-component/src/components/GetSearch.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/GetSearch.jsx diff --git a/packages/astro/test/fixtures/react-component/src/components/Goodbye.vue b/packages/integrations/react/test/fixtures/react-component/src/components/Goodbye.vue index 430dfdb71..430dfdb71 100644 --- a/packages/astro/test/fixtures/react-component/src/components/Goodbye.vue +++ b/packages/integrations/react/test/fixtures/react-component/src/components/Goodbye.vue diff --git a/packages/astro/test/fixtures/react-component/src/components/Hello.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/Hello.jsx index 4c241162d..4c241162d 100644 --- a/packages/astro/test/fixtures/react-component/src/components/Hello.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/Hello.jsx diff --git a/packages/astro/test/fixtures/react-component/src/components/ImportsThrowsAnError.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/ImportsThrowsAnError.jsx index d6ff21dc3..d6ff21dc3 100644 --- a/packages/astro/test/fixtures/react-component/src/components/ImportsThrowsAnError.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/ImportsThrowsAnError.jsx diff --git a/packages/astro/test/fixtures/react-component/src/components/LazyComponent.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/LazyComponent.jsx index b43aa36be..b43aa36be 100644 --- a/packages/astro/test/fixtures/react-component/src/components/LazyComponent.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/LazyComponent.jsx diff --git a/packages/astro/test/fixtures/react-component/src/components/PragmaComment.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/PragmaComment.jsx index d8ea77810..d8ea77810 100644 --- a/packages/astro/test/fixtures/react-component/src/components/PragmaComment.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/PragmaComment.jsx diff --git a/packages/astro/test/fixtures/react-component/src/components/PragmaCommentTypeScript.tsx b/packages/integrations/react/test/fixtures/react-component/src/components/PragmaCommentTypeScript.tsx index 9f2256fbf..9f2256fbf 100644 --- a/packages/astro/test/fixtures/react-component/src/components/PragmaCommentTypeScript.tsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/PragmaCommentTypeScript.tsx diff --git a/packages/astro/test/fixtures/react-component/src/components/PropsSpread.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/PropsSpread.jsx index 044c2a019..044c2a019 100644 --- a/packages/astro/test/fixtures/react-component/src/components/PropsSpread.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/PropsSpread.jsx diff --git a/packages/astro/test/fixtures/react-component/src/components/Pure.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/Pure.jsx index 6fae8613b..6fae8613b 100644 --- a/packages/astro/test/fixtures/react-component/src/components/Pure.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/Pure.jsx diff --git a/packages/astro/test/fixtures/react-component/src/components/Research.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/Research.jsx index 9ab83e5f3..9ab83e5f3 100644 --- a/packages/astro/test/fixtures/react-component/src/components/Research.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/Research.jsx diff --git a/packages/astro/test/fixtures/react-component/src/components/Suspense.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/Suspense.jsx index 87dc82625..87dc82625 100644 --- a/packages/astro/test/fixtures/react-component/src/components/Suspense.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/Suspense.jsx diff --git a/packages/astro/test/fixtures/react-component/src/components/ThrowsAnError.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/ThrowsAnError.jsx index cf970e38c..cf970e38c 100644 --- a/packages/astro/test/fixtures/react-component/src/components/ThrowsAnError.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/ThrowsAnError.jsx diff --git a/packages/astro/test/fixtures/react-component/src/components/TypeScriptComponent.tsx b/packages/integrations/react/test/fixtures/react-component/src/components/TypeScriptComponent.tsx index bde96da84..bde96da84 100644 --- a/packages/astro/test/fixtures/react-component/src/components/TypeScriptComponent.tsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/TypeScriptComponent.tsx diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/WithChildren.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/WithChildren.jsx new file mode 100644 index 000000000..500c0c694 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/WithChildren.jsx @@ -0,0 +1,10 @@ +import React from 'react'; + +export default function ({ children }) { + return ( + <div> + <div className="with-children">{children}</div> + <div className="with-children-count">{children.length}</div> + </div> + ); +} diff --git a/packages/astro/test/fixtures/react-component/src/components/WithId.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/WithId.jsx index 0abe91c72..0abe91c72 100644 --- a/packages/astro/test/fixtures/react-component/src/components/WithId.jsx +++ b/packages/integrations/react/test/fixtures/react-component/src/components/WithId.jsx diff --git a/packages/integrations/react/test/fixtures/react-component/src/pages/children.astro b/packages/integrations/react/test/fixtures/react-component/src/pages/children.astro new file mode 100644 index 000000000..59595c266 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/pages/children.astro @@ -0,0 +1,14 @@ +--- +import WithChildren from '../components/WithChildren'; +--- + +<html> + <head> + <!-- Head Stuff --> + </head> + <body> + <WithChildren> + <div>child 1</div><div>child 2</div> + </WithChildren> + </body> +</html> diff --git a/packages/astro/test/fixtures/react-component/src/pages/error-rendering.astro b/packages/integrations/react/test/fixtures/react-component/src/pages/error-rendering.astro index 6984a6da5..6984a6da5 100644 --- a/packages/astro/test/fixtures/react-component/src/pages/error-rendering.astro +++ b/packages/integrations/react/test/fixtures/react-component/src/pages/error-rendering.astro diff --git a/packages/astro/test/fixtures/react-component/src/pages/index.astro b/packages/integrations/react/test/fixtures/react-component/src/pages/index.astro index 3afd8233f..3afd8233f 100644 --- a/packages/astro/test/fixtures/react-component/src/pages/index.astro +++ b/packages/integrations/react/test/fixtures/react-component/src/pages/index.astro diff --git a/packages/astro/test/fixtures/react-component/src/pages/pragma-comment.astro b/packages/integrations/react/test/fixtures/react-component/src/pages/pragma-comment.astro index b3ddba639..b3ddba639 100644 --- a/packages/astro/test/fixtures/react-component/src/pages/pragma-comment.astro +++ b/packages/integrations/react/test/fixtures/react-component/src/pages/pragma-comment.astro diff --git a/packages/astro/test/fixtures/react-component/src/pages/suspense.astro b/packages/integrations/react/test/fixtures/react-component/src/pages/suspense.astro index 5a9d15644..5a9d15644 100644 --- a/packages/astro/test/fixtures/react-component/src/pages/suspense.astro +++ b/packages/integrations/react/test/fixtures/react-component/src/pages/suspense.astro diff --git a/packages/astro/test/fixtures/react-component/src/skipped-pages/forgot-import.astro b/packages/integrations/react/test/fixtures/react-component/src/skipped-pages/forgot-import.astro index de5d319d9..de5d319d9 100644 --- a/packages/astro/test/fixtures/react-component/src/skipped-pages/forgot-import.astro +++ b/packages/integrations/react/test/fixtures/react-component/src/skipped-pages/forgot-import.astro diff --git a/packages/astro/test/fixtures/react-component/src/skipped-pages/window.astro b/packages/integrations/react/test/fixtures/react-component/src/skipped-pages/window.astro index e780f3c44..e780f3c44 100644 --- a/packages/astro/test/fixtures/react-component/src/skipped-pages/window.astro +++ b/packages/integrations/react/test/fixtures/react-component/src/skipped-pages/window.astro diff --git a/packages/astro/test/react-component.test.js b/packages/integrations/react/test/react-component.test.js index a6bb8cfae..43df1d9e4 100644 --- a/packages/astro/test/react-component.test.js +++ b/packages/integrations/react/test/react-component.test.js @@ -1,13 +1,13 @@ import { expect } from 'chai'; import { load as cheerioLoad } from 'cheerio'; -import { isWindows, loadFixture } from './test-utils.js'; +import { isWindows, loadFixture } from '../../../astro/test/test-utils.js'; let fixture; describe('React Components', () => { before(async () => { fixture = await loadFixture({ - root: './fixtures/react-component/', + root: new URL('./fixtures/react-component/', import.meta.url), }); }); @@ -51,7 +51,9 @@ describe('React Components', () => { // test 10: Should properly render children passed as props const islandsWithChildren = $('.with-children'); expect(islandsWithChildren).to.have.lengthOf(2); - expect($(islandsWithChildren[0]).html()).to.equal($(islandsWithChildren[1]).html()); + expect($(islandsWithChildren[0]).html()).to.equal( + $(islandsWithChildren[1]).find('astro-slot').html() + ); // test 11: Should generate unique React.useId per island const islandsWithId = $('.react-use-id'); @@ -99,12 +101,18 @@ describe('React Components', () => { const $ = cheerioLoad(html); expect($('#cloned').text()).to.equal('Cloned With Props'); }); + + it('Children are parsed as React components, can be manipulated', async () => { + const html = await fixture.readFile('/children/index.html'); + const $ = cheerioLoad(html); + expect($('.with-children-count').text()).to.equal('2'); + }); }); if (isWindows) return; describe('dev', () => { - /** @type {import('./test-utils').Fixture} */ + /** @type {import('../../../astro/test/test-utils.js').Fixture} */ let devServer; before(async () => { diff --git a/packages/integrations/react/vnode-children.js b/packages/integrations/react/vnode-children.js new file mode 100644 index 000000000..9c7abe644 --- /dev/null +++ b/packages/integrations/react/vnode-children.js @@ -0,0 +1,37 @@ +import { parse, walkSync, DOCUMENT_NODE, ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'; +import { createElement, Fragment } from 'react'; + +export default function convert(children) { + const nodeMap = new WeakMap(); + let doc = parse(children.toString().trim()); + let root = createElement(Fragment, { children: [] }); + + walkSync(doc, (node, parent, index) => { + let newNode = {}; + if (node.type === DOCUMENT_NODE) { + nodeMap.set(node, root); + } else if (node.type === ELEMENT_NODE) { + const { class: className, ...props } = node.attributes; + newNode = createElement(node.name, { ...props, className, children: [] }); + nodeMap.set(node, newNode); + if (parent) { + const newParent = nodeMap.get(parent); + newParent.props.children[index] = newNode; + } + } else if (node.type === TEXT_NODE) { + newNode = node.value.trim(); + if (newNode.trim()) { + if (parent) { + const newParent = nodeMap.get(parent); + if (parent.children.length === 1) { + newParent.props.children[0] = newNode; + } else { + newParent.props.children[index] = newNode; + } + } + } + } + }); + + return root.props.children; +} diff --git a/packages/integrations/sitemap/CHANGELOG.md b/packages/integrations/sitemap/CHANGELOG.md index c6c64db1e..32c54dcc8 100644 --- a/packages/integrations/sitemap/CHANGELOG.md +++ b/packages/integrations/sitemap/CHANGELOG.md @@ -6,6 +6,12 @@ - [`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Remove support for Node 16. The lowest supported version by Astro and all integrations is now v18.14.1. As a reminder, Node 16 will be deprecated on the 11th September 2023. +## 2.0.2 + +### Patch Changes + +- [#8063](https://github.com/withastro/astro/pull/8063) [`bee284cb7`](https://github.com/withastro/astro/commit/bee284cb7741ee594e8b38b1a618763e9058740b) Thanks [@martrapp](https://github.com/martrapp)! - docs: fix github search link in README.md + ## 2.0.1 ### Patch Changes diff --git a/packages/integrations/svelte/client.js b/packages/integrations/svelte/client.js index 658ddb02f..0d07ff2ba 100644 --- a/packages/integrations/svelte/client.js +++ b/packages/integrations/svelte/client.js @@ -1,5 +1,8 @@ const noop = () => {}; +let originalConsoleWarning; +let consoleFilterRefs = 0; + export default (target) => { return (Component, props, slotted, { client }) => { if (!target.hasAttribute('ssr')) return; @@ -7,7 +10,10 @@ export default (target) => { for (const [key, value] of Object.entries(slotted)) { slots[key] = createSlotDefinition(key, value); } + try { + if (import.meta.env.DEV) useConsoleFilter(); + new Component({ target, props: { @@ -18,7 +24,10 @@ export default (target) => { hydrate: client !== 'only', $$inline: true, }); - } catch (e) {} + } catch (e) { + } finally { + if (import.meta.env.DEV) finishUsingConsoleFilter(); + } }; }; @@ -51,3 +60,52 @@ function createSlotDefinition(key, children) { noop, ]; } + +/** + * Reduces console noise by filtering known non-problematic warnings. + * + * Performs reference counting to allow parallel usage from async code. + * + * To stop filtering, please ensure that there always is a matching call + * to `finishUsingConsoleFilter` afterwards. + */ +function useConsoleFilter() { + consoleFilterRefs++; + + if (!originalConsoleWarning) { + originalConsoleWarning = console.warn; + try { + console.warn = filteredConsoleWarning; + } catch (error) { + // If we're unable to hook `console.warn`, just accept it + } + } +} + +/** + * Indicates that the filter installed by `useConsoleFilter` + * is no longer needed by the calling code. + */ +function finishUsingConsoleFilter() { + consoleFilterRefs--; + + // Note: Instead of reverting `console.warning` back to the original + // when the reference counter reaches 0, we leave our hook installed + // to prevent potential race conditions once `check` is made async +} + +/** + * Hook/wrapper function for the global `console.warning` function. + * + * Ignores known non-problematic errors while any code is using the console filter. + * Otherwise, simply forwards all arguments to the original function. + */ +function filteredConsoleWarning(msg, ...rest) { + if (consoleFilterRefs > 0 && typeof msg === 'string') { + // Astro passes a `class` prop to the Svelte component, which + // outputs the following warning, which we can safely filter out. + const isKnownSvelteError = msg.endsWith("was created with unknown prop 'class'"); + if (isKnownSvelteError) return; + } + originalConsoleWarning(msg, ...rest); +} diff --git a/packages/integrations/vercel/src/image/build-service.ts b/packages/integrations/vercel/src/image/build-service.ts index 973ceb22a..63a37a5fe 100644 --- a/packages/integrations/vercel/src/image/build-service.ts +++ b/packages/integrations/vercel/src/image/build-service.ts @@ -3,7 +3,7 @@ import { isESMImportedImage, sharedValidateOptions } from './shared'; const service: ExternalImageService = { validateOptions: (options, serviceOptions) => - sharedValidateOptions(options, serviceOptions, 'production'), + sharedValidateOptions(options, serviceOptions.service.config, 'production'), getHTMLAttributes(options) { const { inputtedWidth, ...props } = options; diff --git a/packages/integrations/vercel/src/image/dev-service.ts b/packages/integrations/vercel/src/image/dev-service.ts index d812efb93..72eb7ca0b 100644 --- a/packages/integrations/vercel/src/image/dev-service.ts +++ b/packages/integrations/vercel/src/image/dev-service.ts @@ -4,7 +4,7 @@ import { sharedValidateOptions } from './shared'; const service: LocalImageService = { validateOptions: (options, serviceOptions) => - sharedValidateOptions(options, serviceOptions, 'development'), + sharedValidateOptions(options, serviceOptions.service.config, 'development'), getHTMLAttributes(options, serviceOptions) { const { inputtedWidth, ...props } = options; diff --git a/packages/integrations/vercel/src/image/shared.ts b/packages/integrations/vercel/src/image/shared.ts index 2947b92d8..ad6b45bd0 100644 --- a/packages/integrations/vercel/src/image/shared.ts +++ b/packages/integrations/vercel/src/image/shared.ts @@ -80,10 +80,10 @@ export function getImageConfig( export function sharedValidateOptions( options: ImageTransform, - serviceOptions: Record<string, any>, + serviceConfig: Record<string, any>, mode: 'development' | 'production' ) { - const vercelImageOptions = serviceOptions as VercelImageConfig; + const vercelImageOptions = serviceConfig as VercelImageConfig; if ( mode === 'development' && |