astro 6.3.8 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/components/Code.astro +1 -1
  2. package/dist/assets/fonts/config.d.ts +4 -4
  3. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  4. package/dist/content/content-layer.js +16 -10
  5. package/dist/content/data-store.d.ts +1 -1
  6. package/dist/content/runtime-assets.d.ts +2 -2
  7. package/dist/content/runtime.d.ts +1 -1
  8. package/dist/content/runtime.js +3 -1
  9. package/dist/content/utils.d.ts +1 -1
  10. package/dist/content/utils.js +1 -1
  11. package/dist/core/app/entrypoints/node.d.ts +1 -1
  12. package/dist/core/app/entrypoints/node.js +2 -0
  13. package/dist/core/app/node.d.ts +16 -0
  14. package/dist/core/app/node.js +56 -13
  15. package/dist/core/build/static-build.js +13 -8
  16. package/dist/core/config/merge.js +4 -0
  17. package/dist/core/config/schemas/base.d.ts +16 -10
  18. package/dist/core/config/schemas/base.js +23 -3
  19. package/dist/core/config/schemas/relative.d.ts +60 -42
  20. package/dist/core/config/validate.js +52 -0
  21. package/dist/core/constants.js +1 -1
  22. package/dist/core/dev/dev.js +1 -1
  23. package/dist/core/fetch/fetch-state.js +49 -0
  24. package/dist/core/messages/runtime.js +1 -1
  25. package/dist/core/preview/index.js +6 -5
  26. package/dist/core/preview/static-preview-server.js +2 -1
  27. package/dist/core/render/params-and-props.js +1 -1
  28. package/dist/core/render/route-cache.js +1 -1
  29. package/dist/core/routing/validation.js +1 -1
  30. package/dist/core/server-islands/vite-plugin-server-islands.d.ts +6 -1
  31. package/dist/core/server-islands/vite-plugin-server-islands.js +13 -3
  32. package/dist/core/session/config.d.ts +1 -1
  33. package/dist/markdown/index.d.ts +4 -0
  34. package/dist/markdown/index.js +14 -0
  35. package/dist/prerender/utils.js +5 -1
  36. package/dist/types/public/config.d.ts +32 -1
  37. package/dist/types/public/content.d.ts +1 -1
  38. package/dist/types/public/index.d.ts +1 -1
  39. package/dist/types/public/integrations.d.ts +8 -0
  40. package/dist/vite-plugin-app/app.js +2 -9
  41. package/dist/vite-plugin-integrations-container/index.js +15 -6
  42. package/dist/vite-plugin-markdown/content-entry-type.js +7 -4
  43. package/dist/vite-plugin-markdown/images.js +9 -11
  44. package/dist/vite-plugin-markdown/index.js +12 -11
  45. package/package.json +4 -6
  46. package/dist/jsx/rehype.d.ts +0 -5
  47. package/dist/jsx/rehype.js +0 -241
@@ -0,0 +1,4 @@
1
+ export { extractFrontmatter, isFrontmatterValid, parseFrontmatter, type ParseFrontmatterOptions, type ParseFrontmatterResult, } from '@astrojs/internal-helpers/frontmatter';
2
+ export { resolvePath } from '../core/viteUtils.js';
3
+ export { createDefaultAstroMetadata } from '../vite-plugin-astro/metadata.js';
4
+ export type { AstroMarkdownOptions, AstroMetadata, MarkdownProcessor, MarkdownRenderer, MarkdownRenderOptions, MarkdownRenderResult, MdxRenderer, MdxRendererOptions, MdxRenderResult, } from '@astrojs/internal-helpers/markdown';
@@ -0,0 +1,14 @@
1
+ import {
2
+ extractFrontmatter,
3
+ isFrontmatterValid,
4
+ parseFrontmatter
5
+ } from "@astrojs/internal-helpers/frontmatter";
6
+ import { resolvePath } from "../core/viteUtils.js";
7
+ import { createDefaultAstroMetadata } from "../vite-plugin-astro/metadata.js";
8
+ export {
9
+ createDefaultAstroMetadata,
10
+ extractFrontmatter,
11
+ isFrontmatterValid,
12
+ parseFrontmatter,
13
+ resolvePath
14
+ };
@@ -3,7 +3,11 @@ function getPrerenderDefault(config) {
3
3
  return config.output !== "server";
4
4
  }
5
5
  function getServerOutputDirectory(settings) {
6
- return settings.buildOutput === "server" ? settings.config.build.server : getOutDirWithinCwd(settings.config.outDir);
6
+ const preserveStructure = settings.adapter?.adapterFeatures?.preserveBuildServerDir;
7
+ if (settings.buildOutput === "server" || preserveStructure) {
8
+ return settings.config.build.server;
9
+ }
10
+ return getOutDirWithinCwd(settings.config.outDir);
7
11
  }
8
12
  function getPrerenderOutputDirectory(settings) {
9
13
  return new URL("./.prerender/", getServerOutputDirectory(settings));
@@ -1,6 +1,7 @@
1
1
  import type { OutgoingHttpHeaders } from 'node:http';
2
2
  import type { RemotePattern } from '@astrojs/internal-helpers/remote';
3
- import type { RehypePlugins, RemarkPlugins, RemarkRehype, ShikiConfig, Smartypants, SyntaxHighlightConfigType } from '@astrojs/markdown-remark';
3
+ import type { RehypePlugins, RemarkPlugins, RemarkRehype, ShikiConfig, Smartypants, SyntaxHighlightConfigType } from '@astrojs/internal-helpers/markdown';
4
+ import type { MarkdownProcessor } from '../../markdown/index.js';
4
5
  import type { UserConfig as OriginalViteUserConfig, SSROptions as ViteSSROptions } from 'vite';
5
6
  import type { FontFamily, FontProvider } from '../../assets/fonts/types.js';
6
7
  import type { ImageFit, ImageLayout } from '../../assets/types.js';
@@ -1980,6 +1981,7 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
1980
1981
  * @docs
1981
1982
  * @name markdown.remarkPlugins
1982
1983
  * @type {RemarkPlugins}
1984
+ * @deprecated Pass `remarkPlugins` to `unified({ remarkPlugins })` from `@astrojs/markdown-remark` and set it as `markdown.processor` instead. Will be removed in a future major.
1983
1985
  * @description
1984
1986
  * Pass [remark plugins](https://github.com/remarkjs/remark) to customize how your Markdown is built. You can import and apply the plugin function (recommended), or pass the plugin name as a string.
1985
1987
  *
@@ -1997,6 +1999,7 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
1997
1999
  * @docs
1998
2000
  * @name markdown.rehypePlugins
1999
2001
  * @type {RehypePlugins}
2002
+ * @deprecated Pass `rehypePlugins` to `unified({ rehypePlugins })` from `@astrojs/markdown-remark` and set it as `markdown.processor` instead. Will be removed in a future major.
2000
2003
  * @description
2001
2004
  * Pass [rehype plugins](https://github.com/remarkjs/remark-rehype) to customize how your Markdown's output HTML is processed. You can import and apply the plugin function (recommended), or pass the plugin name as a string.
2002
2005
  *
@@ -2016,6 +2019,7 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
2016
2019
  * @type {boolean}
2017
2020
  * @default `true`
2018
2021
  * @version 2.0.0
2022
+ * @deprecated Pass `gfm` to your processor instead (e.g. `unified({ gfm: false })`). Will be removed in a future major.
2019
2023
  * @description
2020
2024
  * Astro uses [GitHub-flavored Markdown](https://github.com/remarkjs/remark-gfm) by default. To disable this, set the `gfm` flag to `false`:
2021
2025
  *
@@ -2034,6 +2038,7 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
2034
2038
  * @type {boolean | Smartypants}
2035
2039
  * @default `true`
2036
2040
  * @version 2.0.0
2041
+ * @deprecated Pass `smartypants` to your processor instead (e.g. `unified({ smartypants: false })`). Will be removed in a future major.
2037
2042
  * @description
2038
2043
  * Whether to use the [SmartyPants formatter](https://daringfireball.net/projects/smartypants/) to transform straight quotes into smart quotes, dashes into en/em dashes, and triple dots into ellipses.
2039
2044
  *
@@ -2046,6 +2051,7 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
2046
2051
  * @docs
2047
2052
  * @name markdown.remarkRehype
2048
2053
  * @type {RemarkRehype}
2054
+ * @deprecated Pass `remarkRehype` to `unified({ remarkRehype })` from `@astrojs/markdown-remark` and set it as `markdown.processor` instead. Will be removed in a future major.
2049
2055
  * @description
2050
2056
  * Pass options to [remark-rehype](https://github.com/remarkjs/remark-rehype#api).
2051
2057
  *
@@ -2059,6 +2065,31 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
2059
2065
  * ```
2060
2066
  */
2061
2067
  remarkRehype?: RemarkRehype;
2068
+ /**
2069
+ * @docs
2070
+ * @name markdown.processor
2071
+ * @type {MarkdownProcessor}
2072
+ * @version 6.4.0
2073
+ * @description
2074
+ * Configures the Markdown processor used to render `.md` files. Defaults to `unified()` from
2075
+ * `@astrojs/markdown-remark` (the remark/rehype pipeline).
2076
+ *
2077
+ * ```js
2078
+ * // astro.config.mjs
2079
+ * import { defineConfig } from 'astro/config';
2080
+ * import { unified } from '@astrojs/markdown-remark';
2081
+ * import remarkToc from 'remark-toc';
2082
+ *
2083
+ * export default defineConfig({
2084
+ * markdown: {
2085
+ * processor: unified({
2086
+ * remarkPlugins: [remarkToc],
2087
+ * }),
2088
+ * },
2089
+ * });
2090
+ * ```
2091
+ */
2092
+ processor?: MarkdownProcessor;
2062
2093
  };
2063
2094
  /**
2064
2095
  * @docs
@@ -1,4 +1,4 @@
1
- import type { MarkdownHeading } from '@astrojs/markdown-remark';
1
+ import type { MarkdownHeading } from '@astrojs/internal-helpers/markdown';
2
2
  import type * as rollup from 'rollup';
3
3
  import type { DataEntry, RenderedContent } from '../../content/data-store.js';
4
4
  import type { LiveCollectionError } from '../../content/loaders/errors.js';
@@ -1,5 +1,5 @@
1
1
  export type { RemotePattern } from '@astrojs/internal-helpers/remote';
2
- export type { MarkdownHeading, RehypePlugins, RemarkPlugins, ShikiConfig, } from '@astrojs/markdown-remark';
2
+ export type { MarkdownHeading, RehypePlugins, RemarkPlugins, ShikiConfig, } from '@astrojs/internal-helpers/markdown';
3
3
  export type { ExternalImageService, ImageService, LocalImageService, } from '../../assets/services/service.js';
4
4
  export type { AssetsGlobalStaticImagesList, GetImageResult, ImageInputFormat, ImageMetadata, ImageOutputFormat, ImageQuality, ImageQualityPreset, ImageTransform, UnresolvedImageTransform, } from '../../assets/types.js';
5
5
  export type { ContainerRenderer } from '../../container/index.js';
@@ -102,6 +102,14 @@ export interface AstroAdapterFeatures {
102
102
  * of the build output type.
103
103
  */
104
104
  preserveBuildClientDir?: boolean;
105
+ /**
106
+ * When true, static builds will preserve the server directory structure
107
+ * instead of outputting to outDir. This ensures static builds use
108
+ * build.server for server output, maintaining consistency with server builds.
109
+ * Useful for adapters that require a specific directory structure regardless
110
+ * of the build output type.
111
+ */
112
+ preserveBuildServerDir?: boolean;
105
113
  }
106
114
  interface AdapterExplicitProperties {
107
115
  /**
@@ -1,6 +1,5 @@
1
1
  import { removeTrailingForwardSlash } from "@astrojs/internal-helpers/path";
2
2
  import { BaseApp } from "../core/app/entrypoints/index.js";
3
- import { getFirstForwardedValue, validateForwardedHeaders } from "../core/app/validate-headers.js";
4
3
  import { shouldAppendForwardSlash } from "../core/build/util.js";
5
4
  import { clientLocalsSymbol } from "../core/constants.js";
6
5
  import { createSafeError } from "../core/errors/index.js";
@@ -105,14 +104,8 @@ class AstroServerApp extends BaseApp {
105
104
  isHttps,
106
105
  prerenderOnly
107
106
  }) {
108
- const validated = validateForwardedHeaders(
109
- getFirstForwardedValue(incomingRequest.headers["x-forwarded-proto"]),
110
- getFirstForwardedValue(incomingRequest.headers["x-forwarded-host"]),
111
- getFirstForwardedValue(incomingRequest.headers["x-forwarded-port"]),
112
- this.manifest.allowedDomains
113
- );
114
- const protocol = validated.protocol ?? (isHttps ? "https" : "http");
115
- const host = validated.host ?? incomingRequest.headers[":authority"] ?? incomingRequest.headers.host;
107
+ const protocol = isHttps ? "https" : "http";
108
+ const host = incomingRequest.headers[":authority"] ?? incomingRequest.headers.host;
116
109
  const origin = `${protocol}://${host}`;
117
110
  const url = new URL(origin + incomingRequest.url);
118
111
  let pathname;
@@ -4,22 +4,31 @@ function astroIntegrationsContainerPlugin({
4
4
  settings,
5
5
  logger
6
6
  }) {
7
+ let server;
7
8
  return {
8
9
  name: "astro:integration-container",
9
- async configureServer(server) {
10
- if (server.config.isProduction) return;
11
- await runHookServerSetup({ config: settings.config, server, logger });
10
+ async configureServer(_server) {
11
+ server = _server;
12
+ if (_server.config.isProduction) return;
13
+ await runHookServerSetup({ config: settings.config, server: _server, logger });
12
14
  },
13
15
  async buildStart() {
14
16
  if (settings.injectedRoutes.length === settings.resolvedInjectedRoutes.length) return;
15
17
  settings.resolvedInjectedRoutes = await Promise.all(
16
- settings.injectedRoutes.map((route) => resolveEntryPoint.call(this, route))
18
+ settings.injectedRoutes.map((route) => resolveEntryPoint(route, server, this))
17
19
  );
18
20
  }
19
21
  };
20
22
  }
21
- async function resolveEntryPoint(route) {
22
- const resolvedId = await this.resolve(route.entrypoint.toString()).then((res) => res?.id).catch(() => void 0);
23
+ async function resolveEntryPoint(route, server, pluginContext) {
24
+ const entrypoint = route.entrypoint.toString();
25
+ let resolvedId;
26
+ if (server) {
27
+ const resolved = await server.environments.ssr.pluginContainer.resolveId(entrypoint);
28
+ resolvedId = resolved?.id;
29
+ } else {
30
+ resolvedId = await pluginContext.resolve(entrypoint).then((res) => res?.id).catch(() => void 0);
31
+ }
23
32
  if (!resolvedId) return route;
24
33
  const resolvedEntryPoint = new URL(`file://${normalizePath(resolvedId)}`);
25
34
  return { ...route, resolvedEntryPoint };
@@ -1,5 +1,4 @@
1
1
  import { fileURLToPath, pathToFileURL } from "node:url";
2
- import { createMarkdownProcessor } from "@astrojs/markdown-remark";
3
2
  import { safeParseFrontmatter } from "../content/utils.js";
4
3
  const markdownContentEntryType = {
5
4
  extensions: [".md"],
@@ -15,9 +14,13 @@ const markdownContentEntryType = {
15
14
  // We need to handle propagation for Markdown because they support layouts which will bring in styles.
16
15
  handlePropagation: true,
17
16
  async getRenderFunction(config) {
18
- const processor = await createMarkdownProcessor({
19
- image: config.image,
20
- ...config.markdown
17
+ const { markdown, image } = config;
18
+ const processor = await markdown.processor.createRenderer({
19
+ image,
20
+ syntaxHighlight: markdown.syntaxHighlight,
21
+ shikiConfig: markdown.shikiConfig,
22
+ gfm: markdown.gfm,
23
+ smartypants: markdown.smartypants
21
24
  });
22
25
  return async function renderToString(entry) {
23
26
  const result = await processor.render(entry.body ?? "", {
@@ -13,13 +13,12 @@ function getMarkdownCodeForImages(localImagePaths, remoteImagePaths, html) {
13
13
  "\\\\$&"
14
14
  )} + '[^"]*)"', 'g');
15
15
  let match;
16
- let occurrenceCounter = 0;
17
16
  while ((match = regex.exec(html)) !== null) {
18
- const matchKey = ${rawUrl} + '_' + occurrenceCounter;
19
- const imageProps = JSON.parse(match[1].replace(/&#x22;/g, '"').replace(/&#x27;/g, "'"));
17
+ const imageProps = JSON.parse(match[1].replace(/&(?:#x22|quot);/g, '"').replace(/&(?:#x27|apos);/g, "'"));
20
18
  const { src, ...props } = imageProps;
21
- imageSources[matchKey] = await getImage({src: Astro__${entry.safeName}, ...props});
22
- occurrenceCounter++;
19
+ // Key on the decoded src so it lines up with the lookup in updateImageReferences,
20
+ // which JSON-parses the attribute too (so its key uses the decoded path).
21
+ imageSources[src + '_' + imageProps.index] = await getImage({src: Astro__${entry.safeName}, ...props});
23
22
  }
24
23
  }`;
25
24
  }).join("\n")}
@@ -31,12 +30,9 @@ function getMarkdownCodeForImages(localImagePaths, remoteImagePaths, html) {
31
30
  "\\\\$&"
32
31
  )} + '[^"]*)"', 'g');
33
32
  let match;
34
- let occurrenceCounter = 0;
35
33
  while ((match = regex.exec(html)) !== null) {
36
- const matchKey = ${rawUrl} + '_' + occurrenceCounter;
37
- const props = JSON.parse(match[1].replace(/&#x22;/g, '"').replace(/&#x27;/g, "'"));
38
- imageSources[matchKey] = await getImage(props);
39
- occurrenceCounter++;
34
+ const props = JSON.parse(match[1].replace(/&(?:#x22|quot);/g, '"').replace(/&(?:#x27|apos);/g, "'"));
35
+ imageSources[props.src + '_' + props.index] = await getImage(props);
40
36
  }
41
37
  }`;
42
38
  }).join("\n")}
@@ -47,7 +43,9 @@ function getMarkdownCodeForImages(localImagePaths, remoteImagePaths, html) {
47
43
  const imageSources = await images(html);
48
44
 
49
45
  return html.replaceAll(/__ASTRO_IMAGE_="([^"]+)"/gm, (full, imagePath) => {
50
- const decodedImagePath = JSON.parse(imagePath.replace(/&#x22;/g, '"'));
46
+ // Markdown processors disagree on character-reference style \u2014 remark emits
47
+ // \`&#x22;\`/\`&#x27;\`, satteri emits \`&quot;\`/\`&apos;\`. Decode both before JSON.parse.
48
+ const decodedImagePath = JSON.parse(imagePath.replace(/&(?:#x22|quot);/g, '"').replace(/&(?:#x27|apos);/g, "'"));
51
49
 
52
50
  // Use the 'index' property for each image occurrence
53
51
  const srcKey = decodedImagePath.src + '_' + decodedImagePath.index;
@@ -1,9 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import { fileURLToPath, pathToFileURL } from "node:url";
3
- import {
4
- createMarkdownProcessor,
5
- isFrontmatterValid
6
- } from "@astrojs/markdown-remark";
3
+ import { isFrontmatterValid } from "@astrojs/internal-helpers/frontmatter";
7
4
  import { safeParseFrontmatter } from "../content/utils.js";
8
5
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
9
6
  import { isMarkdownFile, isPage } from "../core/util.js";
@@ -20,12 +17,12 @@ const astroErrorModulePath = normalizePath(
20
17
  fileURLToPath(new URL("../core/errors/index.js", import.meta.url))
21
18
  );
22
19
  function markdown({ settings, logger }) {
23
- let processor;
20
+ let renderer;
24
21
  return {
25
22
  enforce: "pre",
26
23
  name: "astro:markdown",
27
24
  buildEnd() {
28
- processor = void 0;
25
+ renderer = void 0;
29
26
  },
30
27
  resolveId: {
31
28
  filter: {
@@ -62,13 +59,17 @@ function markdown({ settings, logger }) {
62
59
  const rawFile = await fs.promises.readFile(fileId, "utf-8");
63
60
  const raw = safeParseFrontmatter(rawFile, id);
64
61
  const fileURL = pathToFileURL(fileId);
65
- if (!processor) {
66
- processor = createMarkdownProcessor({
67
- image: settings.config.image,
68
- ...settings.config.markdown
62
+ if (!renderer) {
63
+ const { markdown: md, image } = settings.config;
64
+ renderer = md.processor.createRenderer({
65
+ image,
66
+ syntaxHighlight: md.syntaxHighlight,
67
+ shikiConfig: md.shikiConfig,
68
+ gfm: md.gfm,
69
+ smartypants: md.smartypants
69
70
  });
70
71
  }
71
- const renderResult = await (await processor).render(raw.content, {
72
+ const renderResult = await (await renderer).render(raw.content, {
72
73
  fileURL,
73
74
  frontmatter: raw.frontmatter
74
75
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "6.3.8",
3
+ "version": "6.4.0",
4
4
  "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
5
5
  "type": "module",
6
6
  "author": "withastro",
@@ -39,7 +39,6 @@
39
39
  "./astro-jsx": "./astro-jsx.d.ts",
40
40
  "./tsconfigs/*.json": "./tsconfigs/*",
41
41
  "./tsconfigs/*": "./tsconfigs/*.json",
42
- "./jsx/rehype.js": "./dist/jsx/rehype.js",
43
42
  "./jsx-runtime": {
44
43
  "types": "./jsx-runtime.d.ts",
45
44
  "default": "./dist/jsx-runtime/index.js"
@@ -49,6 +48,7 @@
49
48
  "default": "./dist/jsx-runtime/index.js"
50
49
  },
51
50
  "./compiler-runtime": "./dist/runtime/compiler/index.js",
51
+ "./markdown": "./dist/markdown/index.js",
52
52
  "./runtime/*": "./dist/runtime/*",
53
53
  "./config": "./dist/config/entrypoint.js",
54
54
  "./container": "./dist/container/index.js",
@@ -164,8 +164,8 @@
164
164
  "xxhash-wasm": "^1.1.0",
165
165
  "yargs-parser": "^22.0.0",
166
166
  "zod": "^4.3.6",
167
- "@astrojs/internal-helpers": "0.9.1",
168
- "@astrojs/markdown-remark": "7.1.2",
167
+ "@astrojs/internal-helpers": "0.10.0",
168
+ "@astrojs/markdown-remark": "7.2.0",
169
169
  "@astrojs/telemetry": "3.3.2"
170
170
  },
171
171
  "optionalDependencies": {
@@ -188,8 +188,6 @@
188
188
  "expect-type": "^1.3.0",
189
189
  "fs-fixture": "^2.13.0",
190
190
  "hono": "^4.12.14",
191
- "mdast-util-mdx": "^3.0.0",
192
- "mdast-util-mdx-jsx": "^3.2.0",
193
191
  "node-mocks-http": "^1.17.2",
194
192
  "parse-srcset": "^1.0.2",
195
193
  "rehype-autolink-headings": "^7.1.0",
@@ -1,5 +0,0 @@
1
- import type { RehypePlugin } from '@astrojs/markdown-remark';
2
- import type { VFile } from 'vfile';
3
- import type { PluginMetadata } from '../vite-plugin-astro/types.js';
4
- export declare const rehypeAnalyzeAstroMetadata: RehypePlugin;
5
- export declare function getAstroMetadata(file: VFile): PluginMetadata["astro"] | undefined;
@@ -1,241 +0,0 @@
1
- import { visit } from "unist-util-visit";
2
- import { AstroError } from "../core/errors/errors.js";
3
- import { AstroErrorData } from "../core/errors/index.js";
4
- import { resolvePath } from "../core/viteUtils.js";
5
- import { createDefaultAstroMetadata } from "../vite-plugin-astro/metadata.js";
6
- const ClientOnlyPlaceholder = "astro-client-only";
7
- const rehypeAnalyzeAstroMetadata = () => {
8
- return (tree, file) => {
9
- const metadata = createDefaultAstroMetadata();
10
- const imports = parseImports(tree.children);
11
- visit(tree, (node) => {
12
- if (node.type !== "mdxJsxFlowElement" && node.type !== "mdxJsxTextElement") return;
13
- const tagName = node.name;
14
- if (!tagName || !isComponent(tagName) || !(hasClientDirective(node) || hasServerDeferDirective(node)))
15
- return;
16
- const matchedImport = findMatchingImport(tagName, imports);
17
- if (!matchedImport) {
18
- throw new AstroError({
19
- ...AstroErrorData.NoMatchingImport,
20
- message: AstroErrorData.NoMatchingImport.message(node.name)
21
- });
22
- }
23
- if (matchedImport.path.endsWith(".astro")) {
24
- const clientAttribute = node.attributes.find(
25
- (attr) => attr.type === "mdxJsxAttribute" && attr.name.startsWith("client:")
26
- );
27
- if (clientAttribute) {
28
- console.warn(
29
- `You are attempting to render <${node.name} ${clientAttribute.name} />, but ${node.name} is an Astro component. Astro components do not render in the client and should not have a hydration directive. Please use a framework component for client rendering.`
30
- );
31
- }
32
- }
33
- const resolvedPath = resolvePath(matchedImport.path, file.path);
34
- if (hasClientOnlyDirective(node)) {
35
- metadata.clientOnlyComponents.push({
36
- exportName: matchedImport.name,
37
- localName: "",
38
- specifier: tagName,
39
- resolvedPath
40
- });
41
- addClientOnlyMetadata(node, matchedImport, resolvedPath);
42
- } else if (hasClientDirective(node)) {
43
- metadata.hydratedComponents.push({
44
- exportName: "*",
45
- localName: "",
46
- specifier: tagName,
47
- resolvedPath
48
- });
49
- addClientMetadata(node, matchedImport, resolvedPath);
50
- } else if (hasServerDeferDirective(node)) {
51
- metadata.serverComponents.push({
52
- exportName: matchedImport.name,
53
- localName: tagName,
54
- specifier: matchedImport.path,
55
- resolvedPath
56
- });
57
- addServerDeferMetadata(node, matchedImport, resolvedPath);
58
- }
59
- });
60
- file.data.__astroMetadata = metadata;
61
- };
62
- };
63
- function getAstroMetadata(file) {
64
- return file.data.__astroMetadata;
65
- }
66
- function parseImports(children) {
67
- const imports = /* @__PURE__ */ new Map();
68
- for (const child of children) {
69
- if (child.type !== "mdxjsEsm") continue;
70
- const body = child.data?.estree?.body;
71
- if (!body) continue;
72
- for (const ast of body) {
73
- if (ast.type !== "ImportDeclaration") continue;
74
- const source = ast.source.value;
75
- const specs = ast.specifiers.map((spec) => {
76
- switch (spec.type) {
77
- case "ImportDefaultSpecifier":
78
- return { local: spec.local.name, imported: "default" };
79
- case "ImportNamespaceSpecifier":
80
- return { local: spec.local.name, imported: "*" };
81
- case "ImportSpecifier": {
82
- return {
83
- local: spec.local.name,
84
- imported: spec.imported.type === "Identifier" ? spec.imported.name : String(spec.imported.value)
85
- };
86
- }
87
- default:
88
- throw new Error("Unknown import declaration specifier: " + spec);
89
- }
90
- });
91
- let specSet = imports.get(source);
92
- if (!specSet) {
93
- specSet = /* @__PURE__ */ new Set();
94
- imports.set(source, specSet);
95
- }
96
- for (const spec of specs) {
97
- specSet.add(spec);
98
- }
99
- }
100
- }
101
- return imports;
102
- }
103
- function isComponent(tagName) {
104
- return tagName[0] && tagName[0].toLowerCase() !== tagName[0] || tagName.includes(".") || /[^a-zA-Z]/.test(tagName[0]);
105
- }
106
- function hasClientDirective(node) {
107
- return node.attributes.some(
108
- (attr) => attr.type === "mdxJsxAttribute" && attr.name.startsWith("client:")
109
- );
110
- }
111
- function hasServerDeferDirective(node) {
112
- return node.attributes.some(
113
- (attr) => attr.type === "mdxJsxAttribute" && attr.name === "server:defer"
114
- );
115
- }
116
- function hasClientOnlyDirective(node) {
117
- return node.attributes.some(
118
- (attr) => attr.type === "mdxJsxAttribute" && attr.name === "client:only"
119
- );
120
- }
121
- function findMatchingImport(tagName, imports) {
122
- const tagSpecifier = tagName.split(".")[0];
123
- for (const [source, specs] of imports) {
124
- for (const { imported, local } of specs) {
125
- if (local === tagSpecifier) {
126
- if (tagSpecifier !== tagName) {
127
- switch (imported) {
128
- // Namespace import: "<buttons.Foo.Bar />" => name: "Foo.Bar"
129
- case "*": {
130
- const accessPath = tagName.slice(tagSpecifier.length + 1);
131
- return { name: accessPath, path: source };
132
- }
133
- // Default import: "<buttons.Foo.Bar />" => name: "default.Foo.Bar"
134
- case "default": {
135
- const accessPath = tagName.slice(tagSpecifier.length + 1);
136
- return { name: `default.${accessPath}`, path: source };
137
- }
138
- // Named import: "<buttons.Foo.Bar />" => name: "buttons.Foo.Bar"
139
- default: {
140
- return { name: tagName, path: source };
141
- }
142
- }
143
- }
144
- return { name: imported, path: source };
145
- }
146
- }
147
- }
148
- }
149
- function addClientMetadata(node, meta, resolvedPath) {
150
- const attributeNames = node.attributes.map((attr) => attr.type === "mdxJsxAttribute" ? attr.name : null).filter(Boolean);
151
- if (!attributeNames.includes("client:component-path")) {
152
- node.attributes.push({
153
- type: "mdxJsxAttribute",
154
- name: "client:component-path",
155
- value: resolvedPath
156
- });
157
- }
158
- if (!attributeNames.includes("client:component-export")) {
159
- if (meta.name === "*") {
160
- meta.name = node.name.split(".").slice(1).join(".");
161
- }
162
- node.attributes.push({
163
- type: "mdxJsxAttribute",
164
- name: "client:component-export",
165
- value: meta.name
166
- });
167
- }
168
- if (!attributeNames.includes("client:component-hydration")) {
169
- node.attributes.push({
170
- type: "mdxJsxAttribute",
171
- name: "client:component-hydration",
172
- value: null
173
- });
174
- }
175
- }
176
- function addClientOnlyMetadata(node, meta, resolvedPath) {
177
- const attributeNames = node.attributes.map((attr) => attr.type === "mdxJsxAttribute" ? attr.name : null).filter(Boolean);
178
- if (!attributeNames.includes("client:display-name")) {
179
- node.attributes.push({
180
- type: "mdxJsxAttribute",
181
- name: "client:display-name",
182
- value: node.name
183
- });
184
- }
185
- if (!attributeNames.includes("client:component-path")) {
186
- node.attributes.push({
187
- type: "mdxJsxAttribute",
188
- name: "client:component-path",
189
- value: resolvedPath
190
- });
191
- }
192
- if (!attributeNames.includes("client:component-export")) {
193
- if (meta.name === "*") {
194
- meta.name = node.name.split(".").slice(1).join(".");
195
- }
196
- node.attributes.push({
197
- type: "mdxJsxAttribute",
198
- name: "client:component-export",
199
- value: meta.name
200
- });
201
- }
202
- if (!attributeNames.includes("client:component-hydration")) {
203
- node.attributes.push({
204
- type: "mdxJsxAttribute",
205
- name: "client:component-hydration",
206
- value: null
207
- });
208
- }
209
- node.name = ClientOnlyPlaceholder;
210
- }
211
- function addServerDeferMetadata(node, meta, resolvedPath) {
212
- const attributeNames = node.attributes.map((attr) => attr.type === "mdxJsxAttribute" ? attr.name : null).filter(Boolean);
213
- if (!attributeNames.includes("server:component-directive")) {
214
- node.attributes.push({
215
- type: "mdxJsxAttribute",
216
- name: "server:component-directive",
217
- value: "server:defer"
218
- });
219
- }
220
- if (!attributeNames.includes("server:component-path")) {
221
- node.attributes.push({
222
- type: "mdxJsxAttribute",
223
- name: "server:component-path",
224
- value: resolvedPath
225
- });
226
- }
227
- if (!attributeNames.includes("server:component-export")) {
228
- if (meta.name === "*") {
229
- meta.name = node.name.split(".").slice(1).join(".");
230
- }
231
- node.attributes.push({
232
- type: "mdxJsxAttribute",
233
- name: "server:component-export",
234
- value: meta.name
235
- });
236
- }
237
- }
238
- export {
239
- getAstroMetadata,
240
- rehypeAnalyzeAstroMetadata
241
- };