diff options
Diffstat (limited to 'packages/integrations/mdx')
19 files changed, 308 insertions, 55 deletions
diff --git a/packages/integrations/mdx/CHANGELOG.md b/packages/integrations/mdx/CHANGELOG.md index e4742f9ea..1b3baaeec 100644 --- a/packages/integrations/mdx/CHANGELOG.md +++ b/packages/integrations/mdx/CHANGELOG.md @@ -1,5 +1,28 @@ # @astrojs/mdx +## 4.0.0-alpha.2 + +### Patch Changes + +- [#11861](https://github.com/withastro/astro/pull/11861) [`3ab3b4e`](https://github.com/withastro/astro/commit/3ab3b4efbcdd2aabea5f949deedf51a5acefae59) Thanks [@bluwy](https://github.com/bluwy)! - Updates `@astrojs/markdown-remark` and handle its breaking changes + +- Updated dependencies [[`3ab3b4e`](https://github.com/withastro/astro/commit/3ab3b4efbcdd2aabea5f949deedf51a5acefae59), [`560ef15`](https://github.com/withastro/astro/commit/560ef15ad23bd137b56ef1048eb2df548b99fdce), [`3ab3b4e`](https://github.com/withastro/astro/commit/3ab3b4efbcdd2aabea5f949deedf51a5acefae59)]: + - @astrojs/markdown-remark@6.0.0-alpha.1 + +## 4.0.0-alpha.1 + +### Minor Changes + +- [#11741](https://github.com/withastro/astro/pull/11741) [`6617491`](https://github.com/withastro/astro/commit/6617491c3bc2bde87f7867d7dec2580781852cfc) Thanks [@bluwy](https://github.com/bluwy)! - Updates adapter server entrypoint to use `@astrojs/mdx/server.js` + + This is an internal change. Handling JSX in your `.mdx` files has been moved from Astro internals and is now the responsibility of this integration. You should not notice a change in your project, and no update to your code is required. + +## 4.0.0-alpha.0 + +- Updated dependencies [[`b6fbdaa`](https://github.com/withastro/astro/commit/b6fbdaa94a9ecec706a99e1938fbf5cd028c72e0), [`89bab1e`](https://github.com/withastro/astro/commit/89bab1e70786123fbe933a9d7a1b80c9334dcc5f), [`d74617c`](https://github.com/withastro/astro/commit/d74617cbd3278feba05909ec83db2d73d57a153e), [`83a2a64`](https://github.com/withastro/astro/commit/83a2a648418ad30f4eb781d1c1b5f2d8a8ac846e), [`e90f559`](https://github.com/withastro/astro/commit/e90f5593d23043579611452a84b9e18ad2407ef9), [`2df49a6`](https://github.com/withastro/astro/commit/2df49a6fb4f6d92fe45f7429430abe63defeacd6), [`8a53517`](https://github.com/withastro/astro/commit/8a5351737d6a14fc55f1dafad8f3b04079e81af6)]: + - astro@5.0.0-alpha.0 + - @astrojs/markdown-remark@6.0.0-alpha.0 + ## 3.1.9 ### Patch Changes diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json index cac6e8f4c..1783f3e89 100644 --- a/packages/integrations/mdx/package.json +++ b/packages/integrations/mdx/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/mdx", "description": "Add support for MDX pages in your Astro site", - "version": "3.1.9", + "version": "4.0.0-beta.2", "type": "module", "types": "./dist/index.d.ts", "author": "withastro", @@ -20,6 +20,7 @@ "homepage": "https://docs.astro.build/en/guides/integrations-guide/mdx/", "exports": { ".": "./dist/index.js", + "./server.js": "./dist/server.js", "./package.json": "./package.json" }, "files": [ @@ -38,7 +39,6 @@ "acorn": "^8.14.0", "es-module-lexer": "^1.5.4", "estree-util-visit": "^2.0.0", - "gray-matter": "^4.0.3", "hast-util-to-html": "^9.0.3", "kleur": "^4.1.5", "rehype-raw": "^7.0.0", @@ -49,7 +49,7 @@ "vfile": "^6.0.3" }, "peerDependencies": { - "astro": "^4.8.0" + "astro": "^5.0.0-alpha.0" }, "devDependencies": { "@types/estree": "^1.0.6", @@ -70,7 +70,7 @@ "remark-toc": "^9.0.0", "shiki": "^1.22.2", "unified": "^11.0.5", - "vite": "^5.4.10" + "vite": "6.0.0-beta.2" }, "engines": { "node": "^18.17.1 || ^20.3.0 || >=21.0.0" diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts index de29003ff..fb6766e5f 100644 --- a/packages/integrations/mdx/src/index.ts +++ b/packages/integrations/mdx/src/index.ts @@ -8,13 +8,12 @@ import type { ContentEntryType, HookParameters, } from 'astro'; -import astroJSXRenderer from 'astro/jsx/renderer.js'; import type { Options as RemarkRehypeOptions } from 'remark-rehype'; import type { PluggableList } from 'unified'; import type { OptimizeOptions } from './rehype-optimize-static.js'; -import { ignoreStringPlugins, parseFrontmatter } from './utils.js'; +import { ignoreStringPlugins, safeParseFrontmatter } from './utils.js'; import { vitePluginMdxPostprocess } from './vite-plugin-mdx-postprocess.js'; -import { vitePluginMdx } from './vite-plugin-mdx.js'; +import { type VitePluginMdxOptions, vitePluginMdx } from './vite-plugin-mdx.js'; export type MdxOptions = Omit<typeof markdownConfigDefaults, 'remarkPlugins' | 'rehypePlugins'> & { extendMarkdownConfig: boolean; @@ -37,14 +36,14 @@ type SetupHookParams = HookParameters<'astro:config:setup'> & { export function getContainerRenderer(): ContainerRenderer { return { name: 'astro:jsx', - serverEntrypoint: 'astro/jsx/server.js', + serverEntrypoint: '@astrojs/mdx/server.js', }; } export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroIntegration { // @ts-expect-error Temporarily assign an empty object here, which will be re-assigned by the // `astro:config:done` hook later. This is so that `vitePluginMdx` can get hold of a reference earlier. - let mdxOptions: MdxOptions = {}; + let vitePluginMdxOptions: VitePluginMdxOptions = {}; return { name: '@astrojs/mdx', @@ -53,17 +52,20 @@ export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroI const { updateConfig, config, addPageExtension, addContentEntryType, addRenderer } = params as SetupHookParams; - addRenderer(astroJSXRenderer); + addRenderer({ + name: 'astro:jsx', + serverEntrypoint: '@astrojs/mdx/server.js', + }); addPageExtension('.mdx'); addContentEntryType({ extensions: ['.mdx'], async getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) { - const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl)); + const parsed = safeParseFrontmatter(contents, fileURLToPath(fileUrl)); return { - data: parsed.data, - body: parsed.content, - slug: parsed.data.slug, - rawData: parsed.matter, + data: parsed.frontmatter, + body: parsed.content.trim(), + slug: parsed.frontmatter.slug, + rawData: parsed.rawFrontmatter, }; }, contentModuleTypes: await fs.readFile( @@ -77,7 +79,7 @@ export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroI updateConfig({ vite: { - plugins: [vitePluginMdx(mdxOptions), vitePluginMdxPostprocess(config)], + plugins: [vitePluginMdx(vitePluginMdxOptions), vitePluginMdxPostprocess(config)], }, }); }, @@ -96,10 +98,13 @@ export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroI }); // Mutate `mdxOptions` so that `vitePluginMdx` can reference the actual options - Object.assign(mdxOptions, resolvedMdxOptions); + Object.assign(vitePluginMdxOptions, { + mdxOptions: resolvedMdxOptions, + srcDir: config.srcDir, + }); // @ts-expect-error After we assign, we don't need to reference `mdxOptions` in this context anymore. // Re-assign it so that the garbage can be collected later. - mdxOptions = {}; + vitePluginMdxOptions = {}; }, }, }; diff --git a/packages/integrations/mdx/src/plugins.ts b/packages/integrations/mdx/src/plugins.ts index 082e8f6fd..77c76243c 100644 --- a/packages/integrations/mdx/src/plugins.ts +++ b/packages/integrations/mdx/src/plugins.ts @@ -83,7 +83,7 @@ function getRehypePlugins(mdxOptions: MdxOptions): PluggableList { } rehypePlugins.push( - // Render info from `vfile.data.astro.data.frontmatter` as JS + // Render info from `vfile.data.astro.frontmatter` as JS rehypeApplyFrontmatterExport, // Analyze MDX nodes and attach to `vfile.data.__astroMetadata` rehypeAnalyzeAstroMetadata, diff --git a/packages/integrations/mdx/src/rehype-apply-frontmatter-export.ts b/packages/integrations/mdx/src/rehype-apply-frontmatter-export.ts index 1b981a68e..5880c30b3 100644 --- a/packages/integrations/mdx/src/rehype-apply-frontmatter-export.ts +++ b/packages/integrations/mdx/src/rehype-apply-frontmatter-export.ts @@ -1,23 +1,35 @@ -import { InvalidAstroDataError } from '@astrojs/markdown-remark'; -import { safelyGetAstroData } from '@astrojs/markdown-remark/dist/internal.js'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { isFrontmatterValid } from '@astrojs/markdown-remark'; +import type { Root, RootContent } from 'hast'; import type { VFile } from 'vfile'; import { jsToTreeNode } from './utils.js'; +// Passed metadata to help determine adding charset utf8 by default +declare module 'vfile' { + interface DataMap { + applyFrontmatterExport?: { + srcDir?: URL; + }; + } +} + +const exportConstPartialTrueRe = /export\s+const\s+partial\s*=\s*true/; + export function rehypeApplyFrontmatterExport() { - return function (tree: any, vfile: VFile) { - const astroData = safelyGetAstroData(vfile.data); - if (astroData instanceof InvalidAstroDataError) + return function (tree: Root, vfile: VFile) { + const frontmatter = vfile.data.astro?.frontmatter; + if (!frontmatter || !isFrontmatterValid(frontmatter)) throw new Error( // Copied from Astro core `errors-data` // TODO: find way to import error data from core '[MDX] A remark or rehype plugin attempted to inject invalid frontmatter. Ensure "astro.frontmatter" is set to a valid JSON object that is not `null` or `undefined`.', ); - const { frontmatter } = astroData; - const exportNodes = [ + const extraChildren: RootContent[] = [ jsToTreeNode(`export const frontmatter = ${JSON.stringify(frontmatter)};`), ]; if (frontmatter.layout) { - exportNodes.unshift( + extraChildren.unshift( jsToTreeNode( // NOTE: Use `__astro_*` import names to prevent conflicts with user code /** @see 'vite-plugin-markdown' for layout props reference */ @@ -41,7 +53,61 @@ export default function ({ children }) { };`, ), ); + } else if (shouldAddCharset(tree, vfile)) { + extraChildren.unshift({ + type: 'mdxJsxFlowElement', + name: 'meta', + attributes: [ + { + type: 'mdxJsxAttribute', + name: 'charset', + value: 'utf-8', + }, + ], + children: [], + }); } - tree.children = exportNodes.concat(tree.children); + tree.children = extraChildren.concat(tree.children); }; } + +/** + * If this is a page (e.g. in src/pages), has no layout frontmatter (handled before calling this function), + * has no leading component that looks like a wrapping layout, and `partial` isn't set to true, we default to + * adding charset=utf-8 like markdown so that users don't have to worry about it for MDX pages without layouts. + */ +function shouldAddCharset(tree: Root, vfile: VFile) { + const srcDirUrl = vfile.data.applyFrontmatterExport?.srcDir; + if (!srcDirUrl) return false; + + const hasConstPartialTrue = tree.children.some( + (node) => node.type === 'mdxjsEsm' && exportConstPartialTrueRe.test(node.value), + ); + if (hasConstPartialTrue) return false; + + // NOTE: the pages directory is a non-configurable Astro behaviour + const pagesDir = path.join(fileURLToPath(srcDirUrl), 'pages').replace(/\\/g, '/'); + // `vfile.path` comes from Vite, which is a normalized path (no backslashes) + const filePath = vfile.path; + if (!filePath.startsWith(pagesDir)) return false; + + const hasLeadingUnderscoreInPath = filePath + .slice(pagesDir.length) + .replace(/\\/g, '/') + .split('/') + .some((part) => part.startsWith('_')); + if (hasLeadingUnderscoreInPath) return false; + + // Bail if the first content found is a wrapping layout component + for (const child of tree.children) { + if (child.type === 'element') break; + if (child.type === 'mdxJsxFlowElement') { + // If is fragment or lowercase tag name (html tags), skip and assume there's no layout + if (child.name == null) break; + if (child.name[0] === child.name[0].toLowerCase()) break; + return false; + } + } + + return true; +} diff --git a/packages/integrations/mdx/src/rehype-collect-headings.ts b/packages/integrations/mdx/src/rehype-collect-headings.ts index fafc59721..a51e8e9f0 100644 --- a/packages/integrations/mdx/src/rehype-collect-headings.ts +++ b/packages/integrations/mdx/src/rehype-collect-headings.ts @@ -1,9 +1,9 @@ -import type { MarkdownHeading, MarkdownVFile } from '@astrojs/markdown-remark'; +import type { VFile } from 'vfile'; import { jsToTreeNode } from './utils.js'; export function rehypeInjectHeadingsExport() { - return function (tree: any, file: MarkdownVFile) { - const headings: MarkdownHeading[] = file.data.__astroHeadings || []; + return function (tree: any, file: VFile) { + const headings = file.data.astro?.headings ?? []; tree.children.unshift( jsToTreeNode(`export function getHeadings() { return ${JSON.stringify(headings)} }`), ); diff --git a/packages/integrations/mdx/src/rehype-images-to-component.ts b/packages/integrations/mdx/src/rehype-images-to-component.ts index 95b500784..da2f25ee5 100644 --- a/packages/integrations/mdx/src/rehype-images-to-component.ts +++ b/packages/integrations/mdx/src/rehype-images-to-component.ts @@ -1,8 +1,8 @@ -import type { MarkdownVFile } from '@astrojs/markdown-remark'; import type { Properties, Root } from 'hast'; import type { MdxJsxAttribute, MdxjsEsm } from 'mdast-util-mdx'; import type { MdxJsxFlowElementHast } from 'mdast-util-mdx-jsx'; import { visit } from 'unist-util-visit'; +import type { VFile } from 'vfile'; import { jsToTreeNode } from './utils.js'; export const ASTRO_IMAGE_ELEMENT = 'astro-image'; @@ -72,18 +72,18 @@ function getImageComponentAttributes(props: Properties): MdxJsxAttribute[] { } export function rehypeImageToComponent() { - return function (tree: Root, file: MarkdownVFile) { - if (!file.data.imagePaths) return; + return function (tree: Root, file: VFile) { + if (!file.data.astro?.imagePaths) return; const importsStatements: MdxjsEsm[] = []; const importedImages = new Map<string, string>(); visit(tree, 'element', (node, index, parent) => { - if (!file.data.imagePaths || node.tagName !== 'img' || !node.properties.src) return; + if (!file.data.astro?.imagePaths || node.tagName !== 'img' || !node.properties.src) return; const src = decodeURI(String(node.properties.src)); - if (!file.data.imagePaths.has(src)) return; + if (!file.data.astro.imagePaths?.includes(src)) return; let importName = importedImages.get(src); diff --git a/packages/integrations/mdx/src/server.ts b/packages/integrations/mdx/src/server.ts new file mode 100644 index 000000000..79934eb32 --- /dev/null +++ b/packages/integrations/mdx/src/server.ts @@ -0,0 +1,73 @@ +import type { NamedSSRLoadedRendererValue } from 'astro'; +import { AstroError } from 'astro/errors'; +import { AstroJSX, jsx } from 'astro/jsx-runtime'; +import { renderJSX } from 'astro/runtime/server/index.js'; + +const slotName = (str: string) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase()); + +// NOTE: In practice, MDX components are always tagged with `__astro_tag_component__`, so the right renderer +// is used directly, and this check is not often used to return true. +export async function check( + Component: any, + props: any, + { default: children = null, ...slotted } = {}, +) { + if (typeof Component !== 'function') return false; + const slots: Record<string, any> = {}; + for (const [key, value] of Object.entries(slotted)) { + const name = slotName(key); + slots[name] = value; + } + try { + const result = await Component({ ...props, ...slots, children }); + return result[AstroJSX]; + } catch (e) { + throwEnhancedErrorIfMdxComponent(e as Error, Component); + } + return false; +} + +export async function renderToStaticMarkup( + this: any, + Component: any, + props = {}, + { default: children = null, ...slotted } = {}, +) { + const slots: Record<string, any> = {}; + for (const [key, value] of Object.entries(slotted)) { + const name = slotName(key); + slots[name] = value; + } + + const { result } = this; + try { + const html = await renderJSX(result, jsx(Component, { ...props, ...slots, children })); + return { html }; + } catch (e) { + throwEnhancedErrorIfMdxComponent(e as Error, Component); + throw e; + } +} + +function throwEnhancedErrorIfMdxComponent(error: Error, Component: any) { + // if the exception is from an mdx component + // throw an error + if (Component[Symbol.for('mdx-component')]) { + // if it's an existing AstroError, we don't need to re-throw, keep the original hint + if (AstroError.is(error)) return; + // Mimic the fields of the internal `AstroError` class (not from `astro/errors`) to + // provide better title and hint for the error overlay + (error as any).title = error.name; + (error as any).hint = + `This issue often occurs when your MDX component encounters runtime errors.`; + throw error; + } +} + +const renderer: NamedSSRLoadedRendererValue = { + name: 'astro:jsx', + check, + renderToStaticMarkup, +}; + +export default renderer; diff --git a/packages/integrations/mdx/src/utils.ts b/packages/integrations/mdx/src/utils.ts index ad98abb9e..7dcd4a14c 100644 --- a/packages/integrations/mdx/src/utils.ts +++ b/packages/integrations/mdx/src/utils.ts @@ -1,7 +1,7 @@ +import { parseFrontmatter } from '@astrojs/markdown-remark'; import type { Options as AcornOpts } from 'acorn'; import { parse } from 'acorn'; import type { AstroConfig, AstroIntegrationLogger, SSRError } from 'astro'; -import matter from 'gray-matter'; import { bold } from 'kleur/colors'; import type { MdxjsEsm } from 'mdast-util-mdx'; import type { PluggableList } from 'unified'; @@ -48,9 +48,9 @@ export function getFileInfo(id: string, config: AstroConfig): FileInfo { * Match YAML exception handling from Astro core errors * @see 'astro/src/core/errors.ts' */ -export function parseFrontmatter(code: string, id: string) { +export function safeParseFrontmatter(code: string, id: string) { try { - return matter(code); + return parseFrontmatter(code, { frontmatter: 'empty-with-spaces' }); } catch (e: any) { if (e.name === 'YAMLException') { const err: SSRError = e; diff --git a/packages/integrations/mdx/src/vite-plugin-mdx.ts b/packages/integrations/mdx/src/vite-plugin-mdx.ts index 5a409d40d..869c65d26 100644 --- a/packages/integrations/mdx/src/vite-plugin-mdx.ts +++ b/packages/integrations/mdx/src/vite-plugin-mdx.ts @@ -1,13 +1,18 @@ -import { setVfileFrontmatter } from '@astrojs/markdown-remark'; import type { SSRError } from 'astro'; import { getAstroMetadata } from 'astro/jsx/rehype.js'; import { VFile } from 'vfile'; import type { Plugin } from 'vite'; import type { MdxOptions } from './index.js'; import { createMdxProcessor } from './plugins.js'; -import { parseFrontmatter } from './utils.js'; +import { safeParseFrontmatter } from './utils.js'; -export function vitePluginMdx(mdxOptions: MdxOptions): Plugin { +export interface VitePluginMdxOptions { + mdxOptions: MdxOptions; + srcDir: URL; +} + +// NOTE: Do not destructure `opts` as we're assigning a reference that will be mutated later +export function vitePluginMdx(opts: VitePluginMdxOptions): Plugin { let processor: ReturnType<typeof createMdxProcessor> | undefined; let sourcemapEnabled: boolean; @@ -39,16 +44,24 @@ export function vitePluginMdx(mdxOptions: MdxOptions): Plugin { async transform(code, id) { if (!id.endsWith('.mdx')) return; - const { data: frontmatter, content: pageContent, matter } = parseFrontmatter(code, id); - const frontmatterLines = matter ? matter.match(/\n/g)?.join('') + '\n\n' : ''; + const { frontmatter, content } = safeParseFrontmatter(code, id); - const vfile = new VFile({ value: frontmatterLines + pageContent, path: id }); - // Ensure `data.astro` is available to all remark plugins - setVfileFrontmatter(vfile, frontmatter); + const vfile = new VFile({ + value: content, + path: id, + data: { + astro: { + frontmatter, + }, + applyFrontmatterExport: { + srcDir: opts.srcDir, + }, + }, + }); // Lazily initialize the MDX processor if (!processor) { - processor = createMdxProcessor(mdxOptions, { sourcemap: sourcemapEnabled }); + processor = createMdxProcessor(opts.mdxOptions, { sourcemap: sourcemapEnabled }); } try { diff --git a/packages/integrations/mdx/test/css-head-mdx.test.js b/packages/integrations/mdx/test/css-head-mdx.test.js index 4d4df5cec..96ee7c900 100644 --- a/packages/integrations/mdx/test/css-head-mdx.test.js +++ b/packages/integrations/mdx/test/css-head-mdx.test.js @@ -15,7 +15,6 @@ describe('Head injection w/ MDX', () => { integrations: [mdx()], // test suite was authored when inlineStylesheets defaulted to never build: { inlineStylesheets: 'never' }, - experimental: { contentLayer: true }, }); }); @@ -24,14 +23,14 @@ describe('Head injection w/ MDX', () => { await fixture.build(); }); - it('only injects contents into head', async () => { + it('injects content styles into head', async () => { const html = await fixture.readFile('/indexThree/index.html'); const { document } = parseHTML(html); const links = document.querySelectorAll('head link[rel=stylesheet]'); assert.equal(links.length, 1); - const scripts = document.querySelectorAll('head script[type=module]'); + const scripts = document.querySelectorAll('script[type=module]'); assert.equal(scripts.length, 1); }); @@ -50,7 +49,7 @@ describe('Head injection w/ MDX', () => { const links = document.querySelectorAll('head link[rel=stylesheet]'); assert.equal(links.length, 1); - const scripts = document.querySelectorAll('head script[type=module]'); + const scripts = document.querySelectorAll('script[type=module]'); assert.equal(scripts.length, 1); }); diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/content/config.ts b/packages/integrations/mdx/test/fixtures/mdx-images/src/content/config.ts new file mode 100644 index 000000000..14443e78d --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/content/config.ts @@ -0,0 +1,5 @@ +import { defineCollection, z } from 'astro:content'; + +const blog = defineCollection({}); + +export const collections = { blog }; diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/tsconfig.json b/packages/integrations/mdx/test/fixtures/mdx-images/tsconfig.json index b5bf6a715..c193287fc 100644 --- a/packages/integrations/mdx/test/fixtures/mdx-images/tsconfig.json +++ b/packages/integrations/mdx/test/fixtures/mdx-images/tsconfig.json @@ -5,5 +5,7 @@ "paths": { "~/assets/*": ["src/assets/*"] }, - } + }, + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] } diff --git a/packages/integrations/mdx/test/fixtures/mdx-page/src/layouts/EncodingLayout.astro b/packages/integrations/mdx/test/fixtures/mdx-page/src/layouts/EncodingLayout.astro new file mode 100644 index 000000000..13e0e91ed --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-page/src/layouts/EncodingLayout.astro @@ -0,0 +1 @@ +<slot></slot> diff --git a/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-frontmatter.mdx b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-frontmatter.mdx new file mode 100644 index 000000000..471827de0 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-frontmatter.mdx @@ -0,0 +1,7 @@ +--- +layout: ../layouts/EncodingLayout.astro +--- + +# 我的第一篇博客文章 + +发表于:2022-07-01 diff --git a/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-manual.mdx b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-manual.mdx new file mode 100644 index 000000000..1c8c78630 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-manual.mdx @@ -0,0 +1,12 @@ +import EncodingLayout from '../layouts/EncodingLayout.astro' + +{/* Ensure random stuff preceding the wrapper layout is ignored when detecting a wrapper layout */} +export const foo = {} + +<EncodingLayout> + +# 我的第一篇博客文章 + +发表于:2022-07-01 + +</EncodingLayout> diff --git a/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding.mdx b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding.mdx new file mode 100644 index 000000000..572b3c370 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding.mdx @@ -0,0 +1,3 @@ +# 我的第一篇博客文章 + +发表于:2022-07-01 diff --git a/packages/integrations/mdx/test/mdx-page.test.js b/packages/integrations/mdx/test/mdx-page.test.js index 7948de653..b58781efc 100644 --- a/packages/integrations/mdx/test/mdx-page.test.js +++ b/packages/integrations/mdx/test/mdx-page.test.js @@ -1,5 +1,6 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; +import * as cheerio from 'cheerio'; import { parseHTML } from 'linkedom'; import { loadFixture } from '../../../astro/test/test-utils.js'; @@ -36,6 +37,23 @@ describe('MDX Page', () => { assert.notEqual(stylesheet, null); }); + + it('Renders MDX in utf-8 by default', async () => { + const html = await fixture.readFile('/chinese-encoding/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), '我的第一篇博客文章'); + assert.match(html, /<meta charset="utf-8"/); + }); + + it('Renders MDX with layout frontmatter without utf-8 by default', async () => { + const html = await fixture.readFile('/chinese-encoding-layout-frontmatter/index.html'); + assert.doesNotMatch(html, /<meta charset="utf-8"/); + }); + + it('Renders MDX with layout manual import without utf-8 by default', async () => { + const html = await fixture.readFile('/chinese-encoding-layout-manual/index.html'); + assert.doesNotMatch(html, /<meta charset="utf-8"/); + }); }); describe('dev', () => { @@ -61,5 +79,31 @@ describe('MDX Page', () => { assert.equal(h1.textContent, 'Hello page!'); }); + + it('Renders MDX in utf-8 by default', async () => { + const res = await fixture.fetch('/chinese-encoding/'); + assert.equal(res.status, 200); + const html = await res.text(); + const $ = cheerio.load(html); + assert.equal($('h1').text(), '我的第一篇博客文章'); + assert.doesNotMatch(res.headers.get('content-type'), /charset=utf-8/); + assert.match(html, /<meta charset="utf-8"/); + }); + + it('Renders MDX with layout frontmatter without utf-8 by default', async () => { + const res = await fixture.fetch('/chinese-encoding-layout-frontmatter/'); + assert.equal(res.status, 200); + const html = await res.text(); + assert.doesNotMatch(res.headers.get('content-type'), /charset=utf-8/); + assert.doesNotMatch(html, /<meta charset="utf-8"/); + }); + + it('Renders MDX with layout manual import without utf-8 by default', async () => { + const res = await fixture.fetch('/chinese-encoding-layout-manual/'); + assert.equal(res.status, 200); + const html = await res.text(); + assert.doesNotMatch(res.headers.get('content-type'), /charset=utf-8/); + assert.doesNotMatch(html, /<meta charset="utf-8"/); + }); }); }); diff --git a/packages/integrations/mdx/test/mdx-vite-env-vars.test.js b/packages/integrations/mdx/test/mdx-vite-env-vars.test.js index 80a9b1cec..213386ceb 100644 --- a/packages/integrations/mdx/test/mdx-vite-env-vars.test.js +++ b/packages/integrations/mdx/test/mdx-vite-env-vars.test.js @@ -57,8 +57,8 @@ describe('MDX - Vite env vars', () => { const dataAttrDump = document.querySelector('[data-env-dump]'); assert.notEqual(dataAttrDump, null); - assert.notEqual(dataAttrDump.getAttribute('data-env-prod'), null); - assert.equal(dataAttrDump.getAttribute('data-env-dev'), null); + assert.equal(dataAttrDump.getAttribute('data-env-prod'), 'true'); + assert.equal(dataAttrDump.getAttribute('data-env-dev'), 'false'); assert.equal(dataAttrDump.getAttribute('data-env-base-url'), '/'); assert.equal(dataAttrDump.getAttribute('data-env-mode'), 'production'); }); |