diff --git a/.changeset/two-hounds-sort.md b/.changeset/two-hounds-sort.md new file mode 100644 index 000000000..b6be5ea04 --- /dev/null +++ b/.changeset/two-hounds-sort.md @@ -0,0 +1,20 @@ +--- +'astro': minor +'@astrojs/markdown-component': minor +'@astrojs/markdown-remark': minor +--- + +The use of components and JSX expressions in Markdown are no longer supported by default. + +For long term support, migrate to the `@astrojs/mdx` integration for MDX support (including `.mdx` pages!). + +Not ready to migrate to MDX? Add the legacy flag to your Astro config to re-enable the previous Markdown support. + +```js +// https://astro.build/config +export default defineConfig({ + legacy: { + astroFlavoredMarkdown: true, + } +}); +``` diff --git a/packages/astro/e2e/fixtures/preact-compat-component/astro.config.mjs b/packages/astro/e2e/fixtures/preact-compat-component/astro.config.mjs index 7d2c8a855..2cd377763 100644 --- a/packages/astro/e2e/fixtures/preact-compat-component/astro.config.mjs +++ b/packages/astro/e2e/fixtures/preact-compat-component/astro.config.mjs @@ -3,5 +3,8 @@ import preact from '@astrojs/preact'; // https://astro.build/config export default defineConfig({ + legacy: { + astroFlavoredMarkdown: true, + }, integrations: [preact({ compat: true })], }); diff --git a/packages/astro/e2e/fixtures/preact-component/astro.config.mjs b/packages/astro/e2e/fixtures/preact-component/astro.config.mjs index 7a8aef521..bcaa451eb 100644 --- a/packages/astro/e2e/fixtures/preact-component/astro.config.mjs +++ b/packages/astro/e2e/fixtures/preact-component/astro.config.mjs @@ -4,5 +4,8 @@ import mdx from '@astrojs/mdx'; // https://astro.build/config export default defineConfig({ + legacy: { + astroFlavoredMarkdown: true, + }, integrations: [preact(), mdx()], }); diff --git a/packages/astro/e2e/fixtures/react-component/astro.config.mjs b/packages/astro/e2e/fixtures/react-component/astro.config.mjs index 5c044b69d..badddf1d3 100644 --- a/packages/astro/e2e/fixtures/react-component/astro.config.mjs +++ b/packages/astro/e2e/fixtures/react-component/astro.config.mjs @@ -4,5 +4,8 @@ import mdx from '@astrojs/mdx'; // https://astro.build/config export default defineConfig({ + legacy: { + astroFlavoredMarkdown: true, + }, integrations: [react(), mdx()], }); diff --git a/packages/astro/e2e/fixtures/solid-component/astro.config.mjs b/packages/astro/e2e/fixtures/solid-component/astro.config.mjs index f527c69b4..35d38c8f1 100644 --- a/packages/astro/e2e/fixtures/solid-component/astro.config.mjs +++ b/packages/astro/e2e/fixtures/solid-component/astro.config.mjs @@ -4,5 +4,8 @@ import solid from '@astrojs/solid-js'; // https://astro.build/config export default defineConfig({ + legacy: { + astroFlavoredMarkdown: true, + }, integrations: [solid(), mdx()], }); diff --git a/packages/astro/e2e/fixtures/svelte-component/astro.config.mjs b/packages/astro/e2e/fixtures/svelte-component/astro.config.mjs index bc5c6c9bb..99f557d43 100644 --- a/packages/astro/e2e/fixtures/svelte-component/astro.config.mjs +++ b/packages/astro/e2e/fixtures/svelte-component/astro.config.mjs @@ -4,5 +4,8 @@ import mdx from '@astrojs/mdx'; // https://astro.build/config export default defineConfig({ + legacy: { + astroFlavoredMarkdown: true, + }, integrations: [svelte(), mdx()], }); diff --git a/packages/astro/e2e/fixtures/tailwindcss/src/pages/markdown-page.md b/packages/astro/e2e/fixtures/tailwindcss/src/pages/markdown-page.md deleted file mode 100644 index e4c6b6bc9..000000000 --- a/packages/astro/e2e/fixtures/tailwindcss/src/pages/markdown-page.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Markdown + Tailwind" -setup: | - import Button from '../components/Button.astro'; - import Complex from '../components/Complex.astro'; ---- - -
See https://example.com for more.
`); - }); - - it('autolinks URLs starting with "www." in plain text', async () => { - const { code } = await renderMarkdown(`See www.example.com for more.`, {}); - - chai - .expect(code.trim()) - .to.equal(`See www.example.com for more.
`); - }); - - it('does not autolink URLs in code blocks', async () => { - const { code } = await renderMarkdown( - 'See `https://example.com` or `www.example.com` for more.', - {} - ); - - chai - .expect(code.trim()) - .to.equal( - `See https://example.com
or ` +
- `www.example.com
for more.
See https://example.com for more.
`); + }); + + it('autolinks URLs starting with "www." in plain text', async () => { + const { code } = await renderMarkdown(`See www.example.com for more.`, {}); + + chai + .expect(code.trim()) + .to.equal(`See www.example.com for more.
`); + }); + + it('does not autolink URLs in code blocks', async () => { + const { code } = await renderMarkdown( + 'See `https://example.com` or `www.example.com` for more.', + {} ); + + chai + .expect(code.trim()) + .to.equal( + `See https://example.com
or ` +
+ `www.example.com
for more.
{ - const { code } = await renderMarkdown( - `See [http://example.com](http://example.com) or ` + - `https://example.com`, - {} - ); - - chai - .expect(code.replace(/\n/g, '')) - .to.equal( - `See http://example.com or ` + - `https://example.com
` + it('does not autolink URLs in code blocks', async () => { + const { code } = await renderAstroMd( + 'See `https://example.com` or `www.example.com` for more.', + {} ); - }); - - it('does not autolink URLs starting with "www." when nested inside links', async () => { - const { code } = await renderMarkdown( - `See [www.example.com](https://www.example.com) or ` + - `www.example.com`, - {} - ); - - chai - .expect(code.replace(/\n/g, '')) - .to.equal( - `See www.example.com or ` + - `www.example.com
` + + chai + .expect(code.trim()) + .to.equal( + `See
` + ); + }); + + it('does not autolink URLs in fenced code blocks', async () => { + const { code } = await renderAstroMd( + 'Example:\n```\nGo to https://example.com or www.example.com now.\n```' ); - }); - - it('does not autolink URLs when nested several layers deep inside links', async () => { - const { code } = await renderMarkdown( - `**Visit _our www.example.com or ` + - `http://localhost pages_ for more!**`, - {} - ); - - chai - .expect(code.replace(/\n/g, '')) - .to.equal( - `` + - `Visit our www.example.com or http://localhost pages for more!` + - `` + + chai + .expect(code) + .to.contain(`https://example.com
or ` + + `www.example.com
for more.{ + const { code } = await renderAstroMd( + `See [http://example.com](http://example.com) or ` + + `https://example.com` ); - }); + + chai + .expect(code.replace(/\n/g, '')) + .to.equal( + `See http://example.com or ` + + `https://example.com
` + ); + }); + + it('does not autolink URLs starting with "www." when nested inside links', async () => { + const { code } = await renderAstroMd( + `See [www.example.com](https://www.example.com) or ` + + `www.example.com` + ); + + chai + .expect(code.replace(/\n/g, '')) + .to.equal( + `See www.example.com or ` + + `www.example.com
` + ); + }); + + it('does not autolink URLs when nested several layers deep inside links', async () => { + const { code } = await renderAstroMd( + `**Visit _our www.example.com or ` + + `http://localhost pages_ for more!**` + ); + + chai + .expect(code.replace(/\n/g, '')) + .to.equal( + `` + + `Visit our www.example.com or http://localhost pages for more!` + + `` + ); + }); + }) }); diff --git a/packages/markdown/remark/test/components.test.js b/packages/markdown/remark/test/components.test.js index 4e06d7301..e60469426 100644 --- a/packages/markdown/remark/test/components.test.js +++ b/packages/markdown/remark/test/components.test.js @@ -2,32 +2,34 @@ import { renderMarkdown } from '../dist/index.js'; import chai from 'chai'; describe('components', () => { + const renderAstroMd = (text) => renderMarkdown(text, { isAstroFlavoredMd: true }); + it('should be able to serialize string', async () => { - const { code } = await renderMarkdown(``, {}); + const { code } = await renderAstroMd(` `); chai.expect(code).to.equal(` `); }); it('should be able to serialize boolean attribute', async () => { - const { code } = await renderMarkdown(` `, {}); + const { code } = await renderAstroMd(` `); chai.expect(code).to.equal(` `); }); it('should be able to serialize array', async () => { - const { code } = await renderMarkdown(` `, {}); + const { code } = await renderAstroMd(` `); chai.expect(code).to.equal(` `); }); it('should be able to serialize object', async () => { - const { code } = await renderMarkdown(` `, {}); + const { code } = await renderAstroMd(` `); chai.expect(code).to.equal(` `); }); it('should be able to serialize empty attribute', async () => { - const { code } = await renderMarkdown(` `, {}); + const { code } = await renderAstroMd(` `); chai.expect(code).to.equal(` `); }); @@ -35,25 +37,25 @@ describe('components', () => { // Notable omission: shorthand attribute it('should be able to serialize spread attribute', async () => { - const { code } = await renderMarkdown(` `, {}); + const { code } = await renderAstroMd(` `); chai.expect(code).to.equal(` `); }); it('should allow client:* directives', async () => { - const { code } = await renderMarkdown(` `, {}); + const { code } = await renderAstroMd(` `); chai.expect(code).to.equal(` `); }); it('should normalize children', async () => { - const { code } = await renderMarkdown(` Hello world! `, {}); + const { code } = await renderAstroMd(`Hello world! `); chai.expect(code).to.equal(`Hello world! `); }); it('should be able to nest components', async () => { - const { code } = await renderMarkdown( + const { code } = await renderAstroMd( ``, {} ); @@ -64,7 +66,7 @@ describe('components', () => { }); it('should allow markdown without many spaces', async () => { - const { code } = await renderMarkdown( + const { code } = await renderAstroMd( ` Hello world! # Hello world! `, diff --git a/packages/markdown/remark/test/expressions.test.js b/packages/markdown/remark/test/expressions.test.js index 828f70561..b2cb1a73d 100644 --- a/packages/markdown/remark/test/expressions.test.js +++ b/packages/markdown/remark/test/expressions.test.js @@ -1,21 +1,23 @@ import { renderMarkdown } from '../dist/index.js'; -import chai, { expect } from 'chai'; +import chai from 'chai'; describe('expressions', () => { + const renderAstroMd = (text, opts) => renderMarkdown(text, { isAstroFlavoredMd: true, ...opts }); + it('should be able to serialize bare expression', async () => { - const { code } = await renderMarkdown(`{a}`, {}); + const { code } = await renderAstroMd(`{a}`, {}); chai.expect(code).to.equal(`{a}`); }); it('should be able to serialize expression inside component', async () => { - const { code } = await renderMarkdown(`{a} `, {}); + const { code } = await renderAstroMd(`{a} `, {}); chai.expect(code).to.equal(`{a} `); }); it('should be able to serialize expression inside markdown', async () => { - const { code } = await renderMarkdown(`# {frontmatter.title}`, {}); + const { code } = await renderAstroMd(`# {frontmatter.title}`, {}); chai .expect(code) @@ -23,7 +25,7 @@ describe('expressions', () => { }); it('should be able to serialize complex expression inside markdown', async () => { - const { code } = await renderMarkdown(`# Hello {frontmatter.name}`, {}); + const { code } = await renderAstroMd(`# Hello {frontmatter.name}`, {}); chai .expect(code) @@ -31,7 +33,7 @@ describe('expressions', () => { }); it('should be able to serialize complex expression with markup inside markdown', async () => { - const { code } = await renderMarkdown(`# Hello {frontmatter.name}`, {}); + const { code } = await renderAstroMd(`# Hello {frontmatter.name}`, {}); chai .expect(code) @@ -41,7 +43,7 @@ describe('expressions', () => { }); it('should be able to avoid evaluating JSX-like expressions in an inline code & generate a slug for id', async () => { - const { code } = await renderMarkdown(`# \`{frontmatter.title}\``, {}); + const { code } = await renderAstroMd(`# \`{frontmatter.title}\``, {}); chai .expect(code) @@ -49,7 +51,7 @@ describe('expressions', () => { }); it('should be able to avoid evaluating JSX-like expressions in inline codes', async () => { - const { code } = await renderMarkdown(`# \`{ foo }\` is a shorthand for \`{ foo: foo }\``, {}); + const { code } = await renderAstroMd(`# \`{ foo }\` is a shorthand for \`{ foo: foo }\``, {}); chai .expect(code) @@ -59,7 +61,7 @@ describe('expressions', () => { }); it('should be able to avoid evaluating JSX-like expressions & escape HTML tag characters in inline codes', async () => { - const { code } = await renderMarkdown( + const { code } = await renderAstroMd( `###### \`{}\` is equivalent to \`Record\` (at TypeScript v{frontmatter.version})`, {} ); @@ -72,7 +74,7 @@ describe('expressions', () => { }); it('should be able to encode ampersand characters in code blocks', async () => { - const { code } = await renderMarkdown( + const { code } = await renderAstroMd( 'The ampersand in ` ` must be encoded in code blocks.', {} ); @@ -85,7 +87,7 @@ describe('expressions', () => { }); it('should be able to encode ampersand characters in fenced code blocks', async () => { - const { code } = await renderMarkdown(` + const { code } = await renderAstroMd(` \`\`\`md The ampersand in \` \` must be encoded in code blocks. \`\`\` @@ -95,7 +97,7 @@ describe('expressions', () => { }); it('should be able to serialize function expression', async () => { - const { code } = await renderMarkdown( + const { code } = await renderAstroMd( `{frontmatter.list.map(item => {item}
)}`, {} ); @@ -104,13 +106,13 @@ describe('expressions', () => { }); it('should unwrap HTML comments in inline code blocks', async () => { - const { code } = await renderMarkdown(`\`{/**/}\``); + const { code } = await renderAstroMd(`\`{/**/}\``); chai.expect(code).to.equal(''); }); it('should unwrap HTML comments in code fences', async () => { - const { code } = await renderMarkdown( + const { code } = await renderAstroMd( ` \`\`\` diff --git a/packages/markdown/remark/test/strictness.test.js b/packages/markdown/remark/test/strictness.test.js index 5ba9c215a..c70872701 100644 --- a/packages/markdown/remark/test/strictness.test.js +++ b/packages/markdown/remark/test/strictness.test.js @@ -1,9 +1,11 @@ import { renderMarkdown } from '../dist/index.js'; import chai from 'chai'; -describe('strictness', () => { +describe('strictness in Astro-flavored markdown', () => { + const renderAstroMd = (text, opts) => renderMarkdown(text, { isAstroFlavoredMd: true, ...opts }); + it('should allow self-closing HTML tags (void elements)', async () => { - const { code } = await renderMarkdown( + const { code } = await renderAstroMd( `Use self-closing void elements
<!-- HTML comment -->
like wordbreak and images: `, {} ); @@ -17,25 +19,25 @@ describe('strictness', () => { }); it('should allow attribute names starting with ":" after element names', async () => { - const { code } = await renderMarkdown(` Test`, {}); + const { code } = await renderAstroMd(`Test`, {}); chai.expect(code.trim()).to.equal(`Test`); }); it('should allow attribute names starting with ":" after local element names', async () => { - const { code } = await renderMarkdown(`x `, {}); + const { code } = await renderAstroMd(`x `, {}); chai.expect(code.trim()).to.equal(`x `); }); it('should allow attribute names starting with ":" after attribute names', async () => { - const { code } = await renderMarkdown(``, {}); + const { code } = await renderAstroMd(``, {}); chai.expect(code.trim()).to.equal(``); }); it('should allow attribute names starting with ":" after local attribute names', async () => { - const { code } = await renderMarkdown( + const { code } = await renderAstroMd( ``, {} ); @@ -44,19 +46,19 @@ describe('strictness', () => { }); it('should allow attribute names starting with ":" after attribute values', async () => { - const { code } = await renderMarkdown(``, {}); + const { code } = await renderAstroMd(``, {}); chai.expect(code.trim()).to.equal(``); }); it('should allow attribute names starting with "@" after element names', async () => { - const { code } = await renderMarkdown(``, {}); + const { code } = await renderAstroMd(``, {}); chai.expect(code.trim()).to.equal(``); }); it('should allow attribute names starting with "@" after local element names', async () => { - const { code } = await renderMarkdown( + const { code } = await renderAstroMd( `Test `, {} ); @@ -65,7 +67,7 @@ describe('strictness', () => { }); it('should allow attribute names starting with "@" after attribute names', async () => { - const { code } = await renderMarkdown( + const { code } = await renderAstroMd( ``, {} ); @@ -74,7 +76,7 @@ describe('strictness', () => { }); it('should allow attribute names starting with "@" after local attribute names', async () => { - const { code } = await renderMarkdown( + const { code } = await renderAstroMd( ``, {} ); @@ -83,7 +85,7 @@ describe('strictness', () => { }); it('should allow attribute names starting with "@" after attribute values', async () => { - const { code } = await renderMarkdown( + const { code } = await renderAstroMd( ``, {} ); @@ -92,7 +94,7 @@ describe('strictness', () => { }); it('should allow attribute names containing dots', async () => { - const { code } = await renderMarkdown(``, {}); + const { code } = await renderAstroMd(``, {}); chai.expect(code.trim()).to.equal(``); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6611b3bf..f2c67e34b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1714,19 +1714,23 @@ importers: packages/astro/test/fixtures/slots-preact: specifiers: + '@astrojs/mdx': workspace:* '@astrojs/preact': workspace:* astro: workspace:* dependencies: + '@astrojs/mdx': link:../../../../integrations/mdx '@astrojs/preact': link:../../../../integrations/preact astro: link:../../.. packages/astro/test/fixtures/slots-react: specifiers: + '@astrojs/mdx': workspace:* '@astrojs/react': workspace:* astro: workspace:* react: ^18.1.0 react-dom: ^18.1.0 dependencies: + '@astrojs/mdx': link:../../../../integrations/mdx '@astrojs/react': link:../../../../integrations/react astro: link:../../.. react: 18.2.0 @@ -1734,25 +1738,31 @@ importers: packages/astro/test/fixtures/slots-solid: specifiers: + '@astrojs/mdx': workspace:* '@astrojs/solid-js': workspace:* astro: workspace:* dependencies: + '@astrojs/mdx': link:../../../../integrations/mdx '@astrojs/solid-js': link:../../../../integrations/solid astro: link:../../.. packages/astro/test/fixtures/slots-svelte: specifiers: + '@astrojs/mdx': workspace:* '@astrojs/svelte': workspace:* astro: workspace:* dependencies: + '@astrojs/mdx': link:../../../../integrations/mdx '@astrojs/svelte': link:../../../../integrations/svelte astro: link:../../.. packages/astro/test/fixtures/slots-vue: specifiers: + '@astrojs/mdx': workspace:* '@astrojs/vue': workspace:* astro: workspace:* dependencies: + '@astrojs/mdx': link:../../../../integrations/mdx '@astrojs/vue': link:../../../../integrations/vue astro: link:../../..