diff --git a/.changeset/olive-laws-work.md b/.changeset/olive-laws-work.md new file mode 100644 index 000000000000..483c759033ee --- /dev/null +++ b/.changeset/olive-laws-work.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fix markdown page charset to be utf-8 by default (same as Astro 2) diff --git a/packages/astro/src/runtime/server/render/page.ts b/packages/astro/src/runtime/server/render/page.ts index 2f4e87f5fc73..8bc5366cfb34 100644 --- a/packages/astro/src/runtime/server/render/page.ts +++ b/packages/astro/src/runtime/server/render/page.ts @@ -63,6 +63,11 @@ export async function renderPage( body = encoder.encode(body); headers.set('Content-Length', body.byteLength.toString()); } + // TODO: Revisit if user should manually set charset by themselves in Astro 4 + // This code preserves the existing behaviour for markdown pages since Astro 2 + if (route?.component.endsWith('.md')) { + headers.set('Content-Type', 'text/html; charset=utf-8'); + } const response = new Response(body, { ...init, headers }); return response; } diff --git a/packages/astro/test/astro-basic.test.js b/packages/astro/test/astro-basic.test.js index 3a75c1acc968..37a5693cf315 100644 --- a/packages/astro/test/astro-basic.test.js +++ b/packages/astro/test/astro-basic.test.js @@ -3,23 +3,31 @@ import * as cheerio from 'cheerio'; import { loadFixture } from './test-utils.js'; describe('Astro basics', () => { + /** @type {import('./test-utils').Fixture} */ let fixture; - let previewServer; before(async () => { fixture = await loadFixture({ root: './fixtures/astro-basic/', }); - await fixture.build(); - previewServer = await fixture.preview(); - }); - - // important: close preview server (free up port and connection) - after(async () => { - await previewServer.stop(); }); describe('build', () => { + let previewServer; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/astro-basic/', + }); + await fixture.build(); + previewServer = await fixture.preview(); + }); + + // important: close preview server (free up port and connection) + after(async () => { + await previewServer.stop(); + }); + it('Can load page', async () => { const html = await fixture.readFile(`/index.html`); const $ = cheerio.load(html); @@ -108,39 +116,87 @@ describe('Astro basics', () => { const $ = cheerio.load(html); expect($('#rendered-order').text()).to.eq('Rendered order: A, B'); }); - }); - it('Supports void elements whose name is a string (#2062)', async () => { - const html = await fixture.readFile('/input/index.html'); - const $ = cheerio.load(html); + it('renders markdown in utf-8 by default', async () => { + const html = await fixture.readFile('/chinese-encoding-md/index.html'); + const $ = cheerio.load(html); + expect($('h1').text()).to.equal('我的第一篇博客文章'); + }); + + it('renders MDX in utf-8 by default', async () => { + const html = await fixture.readFile('/chinese-encoding-mdx/index.html'); + const $ = cheerio.load(html); + expect($('h1').text()).to.equal('我的第一篇博客文章'); + }); + + it('Supports void elements whose name is a string (#2062)', async () => { + const html = await fixture.readFile('/input/index.html'); + const $ = cheerio.load(html); + + // + expect($('body > :nth-child(1)').prop('outerHTML')).to.equal(''); - // - expect($('body > :nth-child(1)').prop('outerHTML')).to.equal(''); + // + expect($('body > :nth-child(2)').prop('outerHTML')).to.equal(''); - // - expect($('body > :nth-child(2)').prop('outerHTML')).to.equal(''); + // + expect($('body > :nth-child(3)').prop('outerHTML')).to.equal(''); - // - expect($('body > :nth-child(3)').prop('outerHTML')).to.equal(''); + // + expect($('body > :nth-child(4)').prop('outerHTML')).to.equal( + '' + ); - // - expect($('body > :nth-child(4)').prop('outerHTML')).to.equal( - '' - ); + // textarea + expect($('body > :nth-child(5)').prop('outerHTML')).to.equal(''); + }); + + describe('preview', () => { + it('returns 200 for valid URLs', async () => { + const result = await fixture.fetch('/'); + expect(result.status).to.equal(200); + }); - // textarea - expect($('body > :nth-child(5)').prop('outerHTML')).to.equal(''); + it('returns 404 for invalid URLs', async () => { + const result = await fixture.fetch('/bad-url'); + expect(result.status).to.equal(404); + }); + }); }); - describe('preview', () => { - it('returns 200 for valid URLs', async () => { - const result = await fixture.fetch('/'); - expect(result.status).to.equal(200); + describe('development', () => { + /** @type {import('./test-utils').DevServer} */ + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + after(async () => { + await devServer.stop(); + }); + + it('Renders markdown in utf-8 by default', async () => { + const res = await fixture.fetch('/chinese-encoding-md'); + expect(res.status).to.equal(200); + const html = await res.text(); + const $ = cheerio.load(html); + expect($('h1').text()).to.equal('我的第一篇博客文章'); + const isUtf8 = + res.headers.get('content-type').includes('charset=utf-8') || + html.includes(''); + expect(isUtf8).to.be.true; }); - it('returns 404 for invalid URLs', async () => { - const result = await fixture.fetch('/bad-url'); - expect(result.status).to.equal(404); + it('Renders MDX in utf-8 by default', async () => { + const res = await fixture.fetch('/chinese-encoding-mdx'); + expect(res.status).to.equal(200); + const html = await res.text(); + const $ = cheerio.load(html); + expect($('h1').text()).to.equal('我的第一篇博客文章'); + const isUtf8 = + res.headers.get('content-type').includes('charset=utf-8') || + html.includes(''); + expect(isUtf8).to.be.true; }); }); }); diff --git a/packages/astro/test/fixtures/astro-basic/astro.config.mjs b/packages/astro/test/fixtures/astro-basic/astro.config.mjs index 1b2eb163d604..00158161f35d 100644 --- a/packages/astro/test/fixtures/astro-basic/astro.config.mjs +++ b/packages/astro/test/fixtures/astro-basic/astro.config.mjs @@ -1,9 +1,10 @@ import { defineConfig } from 'astro/config'; import preact from '@astrojs/preact'; +import mdx from '@astrojs/mdx'; // https://astro.build/config export default defineConfig({ - integrations: [preact()], + integrations: [preact(), mdx()], // make sure CLI flags have precedence server: () => ({ port: 4321 }) }); diff --git a/packages/astro/test/fixtures/astro-basic/package.json b/packages/astro/test/fixtures/astro-basic/package.json index ee968d78cc81..c2e0be656281 100644 --- a/packages/astro/test/fixtures/astro-basic/package.json +++ b/packages/astro/test/fixtures/astro-basic/package.json @@ -3,6 +3,7 @@ "version": "0.0.0", "private": true, "dependencies": { + "@astrojs/mdx": "workspace:*", "@astrojs/preact": "workspace:*", "astro": "workspace:*", "preact": "^10.17.1" diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/chinese-encoding-md.md b/packages/astro/test/fixtures/astro-basic/src/pages/chinese-encoding-md.md new file mode 100644 index 000000000000..572b3c370b15 --- /dev/null +++ b/packages/astro/test/fixtures/astro-basic/src/pages/chinese-encoding-md.md @@ -0,0 +1,3 @@ +# 我的第一篇博客文章 + +发表于:2022-07-01 diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/chinese-encoding-mdx.mdx b/packages/astro/test/fixtures/astro-basic/src/pages/chinese-encoding-mdx.mdx new file mode 100644 index 000000000000..572b3c370b15 --- /dev/null +++ b/packages/astro/test/fixtures/astro-basic/src/pages/chinese-encoding-mdx.mdx @@ -0,0 +1,3 @@ +# 我的第一篇博客文章 + +发表于:2022-07-01 diff --git a/packages/astro/test/units/render/head.test.js b/packages/astro/test/units/render/head.test.js index d2580e30de5b..3bb0fc84f924 100644 --- a/packages/astro/test/units/render/head.test.js +++ b/packages/astro/test/units/render/head.test.js @@ -90,7 +90,7 @@ describe('core/render', () => { const PageModule = createAstroModule(Page); const ctx = await createRenderContext({ - route: { type: 'page', pathname: '/index' }, + route: { type: 'page', pathname: '/index', component: 'src/pages/index.astro' }, request: new Request('http://example.com/'), links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }], mod: PageModule, @@ -171,7 +171,7 @@ describe('core/render', () => { const PageModule = createAstroModule(Page); const ctx = await createRenderContext({ - route: { type: 'page', pathname: '/index' }, + route: { type: 'page', pathname: '/index', component: 'src/pages/index.astro' }, request: new Request('http://example.com/'), links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }], env, @@ -218,7 +218,7 @@ describe('core/render', () => { const PageModule = createAstroModule(Page); const ctx = await createRenderContext({ - route: { type: 'page', pathname: '/index' }, + route: { type: 'page', pathname: '/index', component: 'src/pages/index.astro' }, request: new Request('http://example.com/'), links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }], env, diff --git a/packages/astro/test/units/render/jsx.test.js b/packages/astro/test/units/render/jsx.test.js index 1464b5b0ce5b..65437cfc854a 100644 --- a/packages/astro/test/units/render/jsx.test.js +++ b/packages/astro/test/units/render/jsx.test.js @@ -45,7 +45,7 @@ describe('core/render', () => { const mod = createAstroModule(Page); const ctx = await createRenderContext({ - route: { type: 'page', pathname: '/index' }, + route: { type: 'page', pathname: '/index', component: 'src/pages/index.mdx' }, request: new Request('http://example.com/'), env, mod, @@ -91,7 +91,7 @@ describe('core/render', () => { const mod = createAstroModule(Page); const ctx = await createRenderContext({ - route: { type: 'page', pathname: '/index' }, + route: { type: 'page', pathname: '/index', component: 'src/pages/index.mdx' }, request: new Request('http://example.com/'), env, mod, @@ -117,7 +117,7 @@ describe('core/render', () => { const mod = createAstroModule(Page); const ctx = await createRenderContext({ - route: { type: 'page', pathname: '/index' }, + route: { type: 'page', pathname: '/index', component: 'src/pages/index.mdx' }, request: new Request('http://example.com/'), env, mod, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d662e17b2d0..46af50cf1d6e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1747,6 +1747,9 @@ importers: packages/astro/test/fixtures/astro-basic: dependencies: + '@astrojs/mdx': + specifier: workspace:* + version: link:../../../../integrations/mdx '@astrojs/preact': specifier: workspace:* version: link:../../../../integrations/preact