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