astro 7.0.4 → 7.0.6

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 (59) hide show
  1. package/components/Picture.astro +19 -2
  2. package/dist/actions/handler.js +7 -0
  3. package/dist/cli/dev/background.js +4 -2
  4. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  5. package/dist/cli/install-package.js +5 -3
  6. package/dist/container/index.js +4 -2
  7. package/dist/content/content-layer.js +3 -3
  8. package/dist/core/app/origin-check.d.ts +32 -0
  9. package/dist/core/app/origin-check.js +52 -0
  10. package/dist/core/base-pipeline.js +1 -1
  11. package/dist/core/build/plugins/index.js +2 -0
  12. package/dist/core/build/plugins/plugin-css-target-lowering.d.ts +14 -0
  13. package/dist/core/build/plugins/plugin-css-target-lowering.js +82 -0
  14. package/dist/core/constants.js +1 -1
  15. package/dist/core/create-vite.js +2 -0
  16. package/dist/core/dev/dev.js +1 -1
  17. package/dist/core/fetch/fetch-state.js +2 -0
  18. package/dist/core/head-propagation/buffer.d.ts +14 -3
  19. package/dist/core/head-propagation/buffer.js +17 -7
  20. package/dist/core/messages/runtime.js +1 -1
  21. package/dist/core/pages/handler.js +9 -1
  22. package/dist/core/routing/generator.js +2 -2
  23. package/dist/core/util.d.ts +8 -0
  24. package/dist/core/util.js +23 -0
  25. package/dist/core/viteUtils.d.ts +3 -0
  26. package/dist/core/viteUtils.js +8 -2
  27. package/dist/prefetch/index.js +10 -0
  28. package/dist/runtime/server/render/astro/instance.js +3 -0
  29. package/dist/runtime/server/render/common.js +7 -1
  30. package/dist/runtime/server/render/dom.js +4 -1
  31. package/dist/runtime/server/render/instruction.d.ts +1 -0
  32. package/dist/runtime/server/render/instruction.js +5 -1
  33. package/dist/runtime/server/render/page.js +7 -2
  34. package/dist/runtime/server/render/server-islands-shared.d.ts +1 -0
  35. package/dist/runtime/server/render/server-islands-shared.js +4 -0
  36. package/dist/runtime/server/render/server-islands.js +9 -8
  37. package/dist/runtime/server/render/slot.d.ts +15 -2
  38. package/dist/runtime/server/render/slot.js +37 -6
  39. package/dist/runtime/server/render/util.d.ts +1 -0
  40. package/dist/runtime/server/render/util.js +1 -0
  41. package/dist/template/4xx.js +1 -1
  42. package/dist/transitions/swap-functions.js +9 -2
  43. package/dist/types/public/internal.d.ts +14 -0
  44. package/dist/vite-plugin-astro/index.js +3 -1
  45. package/dist/vite-plugin-astro-server/plugin.js +3 -0
  46. package/dist/vite-plugin-config-alias/index.d.ts +19 -1
  47. package/dist/vite-plugin-config-alias/index.js +6 -5
  48. package/dist/vite-plugin-css/index.js +1 -1
  49. package/dist/vite-plugin-html/transform/index.js +16 -7
  50. package/dist/vite-plugin-html/transform/slots.d.ts +10 -7
  51. package/dist/vite-plugin-html/transform/slots.js +19 -26
  52. package/dist/vite-plugin-html/transform/utils.d.ts +0 -3
  53. package/dist/vite-plugin-html/transform/utils.js +1 -20
  54. package/package.json +5 -10
  55. package/templates/content/types.d.ts +4 -0
  56. package/dist/core/app/middlewares.d.ts +0 -8
  57. package/dist/core/app/middlewares.js +0 -49
  58. package/dist/vite-plugin-html/transform/escape.d.ts +0 -7
  59. package/dist/vite-plugin-html/transform/escape.js +0 -30
@@ -1,6 +1,6 @@
1
1
  import { markHTMLString } from "../escape.js";
2
2
  import { renderSlotToString } from "./slot.js";
3
- import { toAttributeString } from "./util.js";
3
+ import { INVALID_ATTR_NAME_CHAR, toAttributeString } from "./util.js";
4
4
  function componentIsHTMLElement(Component) {
5
5
  return typeof HTMLElement !== "undefined" && HTMLElement.isPrototypeOf(Component);
6
6
  }
@@ -8,6 +8,9 @@ async function renderHTMLElement(result, constructor, props, slots) {
8
8
  const name = getHTMLElementName(constructor);
9
9
  let attrHTML = "";
10
10
  for (const attr in props) {
11
+ if (INVALID_ATTR_NAME_CHAR.test(attr)) {
12
+ continue;
13
+ }
11
14
  attrHTML += ` ${attr}="${toAttributeString(await props[attr])}"`;
12
15
  }
13
16
  return markHTMLString(
@@ -35,3 +35,4 @@ export type TemplateExitInstruction = {
35
35
  export type RenderInstruction = RenderDirectiveInstruction | RenderHeadInstruction | MaybeRenderHeadInstruction | RendererHydrationScriptInstruction | ServerIslandRuntimeInstruction | RenderScriptInstruction | TemplateEnterInstruction | TemplateExitInstruction;
36
36
  export declare function createRenderInstruction<T extends RenderInstruction>(instruction: T): T;
37
37
  export declare function isRenderInstruction(chunk: any): chunk is RenderInstruction;
38
+ export declare function isScriptInstruction(chunk: any): chunk is RenderScriptInstruction;
@@ -7,7 +7,11 @@ function createRenderInstruction(instruction) {
7
7
  function isRenderInstruction(chunk) {
8
8
  return chunk && typeof chunk === "object" && chunk[RenderInstructionSymbol];
9
9
  }
10
+ function isScriptInstruction(chunk) {
11
+ return chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "script";
12
+ }
10
13
  export {
11
14
  createRenderInstruction,
12
- isRenderInstruction
15
+ isRenderInstruction,
16
+ isScriptInstruction
13
17
  };
@@ -1,4 +1,5 @@
1
1
  import { isRoute404, isRoute500 } from "../../../core/routing/internal/route-errors.js";
2
+ import { isPropagatingHint } from "../../../core/head-propagation/resolver.js";
2
3
  import { renderToAsyncIterable, renderToReadableStream, renderToString } from "./astro/render.js";
3
4
  import { encoder } from "./common.js";
4
5
  import { renderComponentToString } from "./component.js";
@@ -7,7 +8,9 @@ import { isDeno, isNode } from "./util.js";
7
8
  import { isAstroComponentFactory } from "./astro/factory.js";
8
9
  async function renderPage(result, componentFactory, props, children, streaming, route) {
9
10
  if (!isAstroComponentFactory(componentFactory)) {
10
- result._metadata.headInTree = result.componentMetadata.get(componentFactory.moduleId)?.containsHead ?? false;
11
+ const nonAstroMeta = result.componentMetadata.get(componentFactory.moduleId);
12
+ result._metadata.headInTree = nonAstroMeta?.containsHead ?? false;
13
+ result._metadata.routeHasPropagation = isPropagatingHint(nonAstroMeta?.propagation ?? "none");
11
14
  const pageProps = { ...props ?? {}, "server:root": true };
12
15
  const str = await renderComponentToString(
13
16
  result,
@@ -31,7 +34,9 @@ async function renderPage(result, componentFactory, props, children, streaming,
31
34
  status: result.response.status
32
35
  });
33
36
  }
34
- result._metadata.headInTree = result.componentMetadata.get(componentFactory.moduleId)?.containsHead ?? false;
37
+ const pageMeta = result.componentMetadata.get(componentFactory.moduleId);
38
+ result._metadata.headInTree = pageMeta?.containsHead ?? false;
39
+ result._metadata.routeHasPropagation = isPropagatingHint(pageMeta?.propagation ?? "none");
35
40
  let body;
36
41
  if (streaming) {
37
42
  if (isNode && !isDeno) {
@@ -0,0 +1 @@
1
+ export declare const SERVER_ISLAND_START = "[if astro]>server-island-start<![endif]";
@@ -0,0 +1,4 @@
1
+ const SERVER_ISLAND_START = "[if astro]>server-island-start<![endif]";
2
+ export {
3
+ SERVER_ISLAND_START
4
+ };
@@ -3,6 +3,7 @@ import { markHTMLString, stringifyForScript } from "../escape.js";
3
3
  import { renderChild } from "./any.js";
4
4
  import { createThinHead } from "./astro/head-and-content.js";
5
5
  import { createRenderInstruction } from "./instruction.js";
6
+ import { SERVER_ISLAND_START } from "./server-islands-shared.js";
6
7
  import { renderSlotToString } from "./slot.js";
7
8
  const internalProps = /* @__PURE__ */ new Set([
8
9
  "server:component-path",
@@ -56,7 +57,7 @@ class ServerIslandComponent {
56
57
  const hostId = await this.getHostId();
57
58
  const islandContent = await this.getIslandContent();
58
59
  destination.write(createRenderInstruction({ type: "server-island-runtime" }));
59
- destination.write("<!--[if astro]>server-island-start<![endif]-->");
60
+ destination.write(`<!--${SERVER_ISLAND_START}-->`);
60
61
  for (const name in this.slots) {
61
62
  if (name === "fallback") {
62
63
  await renderChild(destination, this.slots.fallback(this.result));
@@ -114,14 +115,14 @@ class ServerIslandComponent {
114
115
  for (const name in this.slots) {
115
116
  if (name !== "fallback") {
116
117
  const content = await renderSlotToString(this.result, this.slots[name]);
117
- let slotHtml = content.toString();
118
118
  const slotContent = content;
119
- if (Array.isArray(slotContent.instructions)) {
120
- for (const instruction of slotContent.instructions) {
121
- if (instruction.type === "script") {
122
- slotHtml += instruction.content;
123
- }
119
+ let slotHtml = "";
120
+ if (slotContent.chunks?.length) {
121
+ for (const part of slotContent.chunks) {
122
+ slotHtml += typeof part === "string" ? part : part.content;
124
123
  }
124
+ } else {
125
+ slotHtml = content.toString();
125
126
  }
126
127
  renderedSlots[name] = slotHtml;
127
128
  }
@@ -186,7 +187,7 @@ const SERVER_ISLAND_REPLACER = markHTMLString(
186
187
  // Load the HTML before modifying the DOM in case of errors
187
188
  let html = await r.text();
188
189
  // Remove any placeholder content before the island script
189
- while (s.previousSibling && s.previousSibling.nodeType !== 8 && s.previousSibling.data !== '[if astro]>server-island-start<![endif]')
190
+ while (s.previousSibling && s.previousSibling.nodeType !== 8 && s.previousSibling.data !== '${SERVER_ISLAND_START}')
190
191
  s.previousSibling.remove();
191
192
  s.previousSibling?.remove();
192
193
  // Insert the new HTML
@@ -2,15 +2,28 @@ import type { SSRResult } from '../../../types/public/internal.js';
2
2
  import { HTMLString } from '../escape.js';
3
3
  import { renderTemplate } from './astro/render-template.js';
4
4
  import { type RenderInstance } from './common.js';
5
- import type { RenderInstruction } from './instruction.js';
5
+ import { type RenderInstruction, type RenderScriptInstruction } from './instruction.js';
6
6
  type RenderTemplateResult = ReturnType<typeof renderTemplate>;
7
7
  export type ComponentSlots = Record<string, ComponentSlotValue>;
8
8
  export type ComponentSlotValue = (result: SSRResult) => RenderTemplateResult | Promise<RenderTemplateResult>;
9
9
  declare const slotString: unique symbol;
10
+ /**
11
+ * A part of a slot's content stream: either already-stringified HTML or a
12
+ * position-sensitive script instruction that is resolved (and deduplicated)
13
+ * lazily when the slot is finally stringified.
14
+ */
15
+ export type SlotStringChunk = string | RenderScriptInstruction;
10
16
  export declare class SlotString extends HTMLString {
11
17
  instructions: null | RenderInstruction[];
18
+ /**
19
+ * The slot's content as an ordered stream. Unlike `instructions` (which holds
20
+ * position-independent instructions like head/hydration content), scripts are
21
+ * kept inline here so they render at their original position instead of being
22
+ * hoisted to the start of the slot output.
23
+ */
24
+ chunks: SlotStringChunk[];
12
25
  [slotString]: boolean;
13
- constructor(content: string, instructions: null | RenderInstruction[]);
26
+ constructor(content: string, instructions: null | RenderInstruction[], chunks?: SlotStringChunk[]);
14
27
  }
15
28
  export declare function isSlotString(str: string): str is any;
16
29
  /**
@@ -2,13 +2,24 @@ import { HTMLString, markHTMLString, unescapeHTML } from "../escape.js";
2
2
  import { renderChild } from "./any.js";
3
3
  import { renderTemplate } from "./astro/render-template.js";
4
4
  import { chunkToString } from "./common.js";
5
+ import {
6
+ isScriptInstruction
7
+ } from "./instruction.js";
5
8
  const slotString = /* @__PURE__ */ Symbol.for("astro:slot-string");
6
9
  class SlotString extends HTMLString {
7
10
  instructions;
11
+ /**
12
+ * The slot's content as an ordered stream. Unlike `instructions` (which holds
13
+ * position-independent instructions like head/hydration content), scripts are
14
+ * kept inline here so they render at their original position instead of being
15
+ * hoisted to the start of the slot output.
16
+ */
17
+ chunks;
8
18
  [slotString];
9
- constructor(content, instructions) {
19
+ constructor(content, instructions, chunks = []) {
10
20
  super(content);
11
21
  this.instructions = instructions;
22
+ this.chunks = chunks;
12
23
  this[slotString] = true;
13
24
  }
14
25
  }
@@ -35,25 +46,35 @@ function renderSlot(result, slotted, fallback) {
35
46
  async function renderSlotToString(result, slotted, fallback) {
36
47
  let content = "";
37
48
  let instructions = null;
49
+ const chunks = [];
38
50
  const temporaryDestination = {
39
51
  write(chunk) {
40
52
  if (chunk instanceof SlotString) {
41
53
  content += chunk;
54
+ if (chunk.chunks.length) {
55
+ chunks.push(...chunk.chunks);
56
+ }
42
57
  instructions = mergeSlotInstructions(instructions, chunk);
43
58
  } else if (chunk instanceof Response) return;
44
59
  else if (typeof chunk === "object" && "type" in chunk && typeof chunk.type === "string") {
45
- if (instructions === null) {
46
- instructions = [];
60
+ if (isScriptInstruction(chunk)) {
61
+ chunks.push(chunk);
62
+ } else {
63
+ if (instructions === null) {
64
+ instructions = [];
65
+ }
66
+ instructions.push(chunk);
47
67
  }
48
- instructions.push(chunk);
49
68
  } else {
50
- content += chunkToString(result, chunk);
69
+ const str = chunkToString(result, chunk);
70
+ content += str;
71
+ chunks.push(str);
51
72
  }
52
73
  }
53
74
  };
54
75
  const renderInstance = renderSlot(result, slotted, fallback);
55
76
  await renderInstance.render(temporaryDestination);
56
- return markHTMLString(new SlotString(content, instructions));
77
+ return markHTMLString(new SlotString(content, instructions, chunks));
57
78
  }
58
79
  async function renderSlots(result, slots = {}) {
59
80
  let slotInstructions = null;
@@ -68,6 +89,16 @@ async function renderSlots(result, slots = {}) {
68
89
  }
69
90
  slotInstructions.push(...output.instructions);
70
91
  }
92
+ if (output.chunks) {
93
+ for (const part of output.chunks) {
94
+ if (typeof part !== "string") {
95
+ if (slotInstructions === null) {
96
+ slotInstructions = [];
97
+ }
98
+ slotInstructions.push(part);
99
+ }
100
+ }
101
+ }
71
102
  children[key] = output;
72
103
  })
73
104
  )
@@ -1,6 +1,7 @@
1
1
  import type { SSRElement } from '../../../types/public/internal.js';
2
2
  import type { RenderDestination, RenderFunction } from './common.js';
3
3
  export declare const voidElementNames: RegExp;
4
+ export declare const INVALID_ATTR_NAME_CHAR: RegExp;
4
5
  export declare const toAttributeString: (value: any, shouldEscape?: boolean) => any;
5
6
  export declare const toStyleString: (obj: Record<string, any>) => string;
6
7
  export declare function defineScriptVars(vars: Record<any, any>): any;
@@ -168,6 +168,7 @@ function promiseWithResolvers() {
168
168
  };
169
169
  }
170
170
  export {
171
+ INVALID_ATTR_NAME_CHAR,
171
172
  addAttribute,
172
173
  createBufferedRenderer,
173
174
  defineScriptVars,
@@ -10,7 +10,7 @@ function template({
10
10
  return `<!doctype html>
11
11
  <html lang="en">
12
12
  <head>
13
- <meta charset="UTF-8">
13
+ <meta charset="utf-8">
14
14
  <title>${tabTitle}</title>
15
15
  <style>
16
16
  :root {
@@ -1,3 +1,4 @@
1
+ import { SERVER_ISLAND_START } from "../runtime/server/render/server-islands-shared.js";
1
2
  const PERSIST_ATTR = "data-astro-transition-persist";
2
3
  const NON_OVERRIDABLE_ASTRO_ATTRS = ["data-astro-transition", "data-astro-transition-fallback"];
3
4
  const knownVueScopedStyles = /* @__PURE__ */ new Map();
@@ -29,6 +30,11 @@ function swapRootAttributes(newDoc) {
29
30
  );
30
31
  }
31
32
  function swapHeadElements(doc) {
33
+ const relevantNodes = (parent, what = "both") => [
34
+ ...[...parent.childNodes].filter(
35
+ (child) => child.nodeType === 8 && child.textContent === SERVER_ISLAND_START || what === "both" && child.nodeType === 1
36
+ )
37
+ ];
32
38
  for (const el of Array.from(document.head.children)) {
33
39
  const newEl = persistedHeadElement(el, doc);
34
40
  if (newEl) {
@@ -41,12 +47,13 @@ function swapHeadElements(doc) {
41
47
  el.remove();
42
48
  }
43
49
  }
50
+ relevantNodes(document.head, "commentsOnly").forEach((node) => node.remove());
44
51
  if (import.meta.env.DEV) {
45
- [...doc.head.children].forEach((child) => {
52
+ relevantNodes(doc.head).forEach((child) => {
46
53
  document.head.append(knownVueScopedStyles.get(child.dataset?.viteDevId) || child);
47
54
  });
48
55
  } else {
49
- document.head.append(...doc.head.children);
56
+ document.head.append(...relevantNodes(doc.head));
50
57
  }
51
58
  }
52
59
  function swapBodyElement(newElement, oldElement) {
@@ -280,6 +280,20 @@ export interface SSRMetadata {
280
280
  extraStyleHashes: string[];
281
281
  extraScriptHashes: string[];
282
282
  propagators: Set<AstroComponentInstance | ServerIslandComponent>;
283
+ /**
284
+ * `true` when the page being rendered is on a head-propagation path (its
285
+ * component metadata hint is `in-tree`/`self`). Only then do we await async
286
+ * slot pre-renders before collecting head content, so that propagating
287
+ * components hidden behind an `await` in slot markup are discovered in time.
288
+ * Pages with no propagation keep streaming without paying that cost.
289
+ */
290
+ routeHasPropagation: boolean;
291
+ /**
292
+ * Promises from async slot pre-renders that may still need to register
293
+ * propagating components. Drained by `collectPropagatedHeadParts` before
294
+ * head content is flushed. Only populated when `routeHasPropagation` is true.
295
+ */
296
+ pendingSlotEvaluations: Promise<unknown>[];
283
297
  /**
284
298
  * Tracks nesting depth of HTML `<template>` elements during rendering.
285
299
  * Scripts rendered inside `<template>` tags should not be deduplicated,
@@ -55,7 +55,9 @@ function astro({ settings, logger }) {
55
55
  viteConfig.resolve.conditions = [...defaultServerConditions];
56
56
  }
57
57
  }
58
- viteConfig.resolve.conditions.push("astro");
58
+ if (!viteConfig.resolve.conditions.includes("astro")) {
59
+ viteConfig.resolve.conditions.push("astro");
60
+ }
59
61
  },
60
62
  async configResolved(viteConfig) {
61
63
  const toolbarEnabled = await settings.preferences.get("devToolbar.enabled");
@@ -25,6 +25,9 @@ function createVitePluginAstroServer({
25
25
  return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr;
26
26
  },
27
27
  async configureServer(viteServer) {
28
+ if (process.env.VITEST) {
29
+ return;
30
+ }
28
31
  const ssrEnvironment = viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
29
32
  const prerenderEnvironment = viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.prerender];
30
33
  const runnableSsrEnvironment = isRunnableDevEnvironment(ssrEnvironment) ? ssrEnvironment : void 0;
@@ -1,6 +1,24 @@
1
1
  import { type Plugin as VitePlugin } from 'vite';
2
2
  import type { AstroSettings } from '../types/astro.js';
3
- /** Returns Vite plugins used to alias paths from tsconfig.json and jsconfig.json. */
3
+ /**
4
+ * Fallback for tsconfig path aliases that Vite's `resolve.tsconfigPaths` does
5
+ * not currently handle in Astro's pipeline.
6
+ *
7
+ * This plugin is intentionally limited to the syntax Astro already supported
8
+ * before enabling `resolve.tsconfigPaths`:
9
+ * - CSS: `@import "..."`
10
+ * - CSS: `@import url("...")`
11
+ * - CSS: quoted `url("...")` references
12
+ * - Modules: JS, TS, and Astro import specifiers handled by `resolveId`
13
+ *
14
+ * Do not expand this fallback to new CSS at-rules or preprocessor syntax. It
15
+ * does not support `@use`, `@forward`, `@reference`, `@config`, unquoted
16
+ * `url(...)`, or every place a CSS tool might accept a file path. Those should
17
+ * be handled by Vite's native resolver instead.
18
+ *
19
+ * @deprecated This fallback will be removed in a future Astro version once Vite
20
+ * handles these remaining alias paths.
21
+ */
4
22
  export default function configAliasVitePlugin({ settings, }: {
5
23
  settings: AstroSettings;
6
24
  }): VitePlugin[] | null;
@@ -51,10 +51,10 @@ function configAliasVitePlugin({
51
51
  const configAlias = getConfigAlias(settings);
52
52
  if (!configAlias) return null;
53
53
  return [
54
- // Pre-plugin: rewrite CSS @import aliases to absolute paths before Vite's CSS plugin.
55
- // Vite's internal CSS @import resolver (postcss-import) uses a mini plugin container
56
- // that doesn't include user resolveId hooks, so we must rewrite aliases in a transform
57
- // hook that runs before Vite's CSS processing.
54
+ // Deprecated CSS fallback for Vite's transform pipeline. Only supports
55
+ // `@import "..."`, `@import url("...")`, and quoted `url("...")`.
56
+ // Do not add support here for `@use`, `@forward`, `@reference`, `@config`,
57
+ // or other CSS/preprocessor file-reference syntax.
58
58
  {
59
59
  name: "astro:tsconfig-alias-css",
60
60
  enforce: "pre",
@@ -88,7 +88,8 @@ function configAliasVitePlugin({
88
88
  }
89
89
  }
90
90
  },
91
- // Post-plugin: resolve JS/TS imports using tsconfig path aliases via resolveId.
91
+ // Deprecated module fallback for JS, TS, and Astro import specifiers that
92
+ // Vite's native tsconfig path resolution does not currently resolve.
92
93
  {
93
94
  name: "astro:tsconfig-alias",
94
95
  // use post to only resolve ids that all other plugins before it can't
@@ -122,7 +122,7 @@ function astroDevCssPlugin({ routesList, command }) {
122
122
  }
123
123
  for (const collected of collectCSSWithOrder(componentPageId, mod)) {
124
124
  if (!cssWithOrder.has(collected.idKey)) {
125
- const content = cssContentCache.get(collected.id) || collected.content;
125
+ const content = cssContentCache.get(collected.idKey) || collected.content;
126
126
  cssWithOrder.set(collected.idKey, { ...collected, content });
127
127
  }
128
128
  }
@@ -1,13 +1,15 @@
1
1
  import MagicString from "magic-string";
2
- import { rehype } from "rehype";
3
- import { VFile } from "vfile";
4
- import escape from "./escape.js";
5
- import slots, { SLOT_PREFIX } from "./slots.js";
2
+ import { collectSlots, SLOT_PREFIX } from "./slots.js";
3
+ import { escapeTemplateLiteralCharacters, needsEscape } from "./utils.js";
6
4
  async function transform(code, id) {
7
5
  const s = new MagicString(code, { filename: id });
8
- const parser = rehype().data("settings", { fragment: true }).use(escape, { s }).use(slots, { s });
9
- const vfile = new VFile({ value: code, path: id });
10
- await parser.process(vfile);
6
+ let cursor = 0;
7
+ for (const slot of collectSlots(code)) {
8
+ escapeRange(s, code, cursor, slot.start);
9
+ s.overwrite(slot.start, slot.end, slot.value);
10
+ cursor = slot.end;
11
+ }
12
+ escapeRange(s, code, cursor, code.length);
11
13
  s.prepend(`function render({ slots: ${SLOT_PREFIX} }) {
12
14
  return \``);
13
15
  s.append('`\n }\nrender["astro:html"] = true;\nexport default render;');
@@ -16,6 +18,13 @@ async function transform(code, id) {
16
18
  map: s.generateMap({ hires: "boundary" })
17
19
  };
18
20
  }
21
+ function escapeRange(s, code, start, end) {
22
+ if (start >= end) return;
23
+ const segment = code.slice(start, end);
24
+ if (needsEscape(segment)) {
25
+ s.overwrite(start, end, escapeTemplateLiteralCharacters(segment));
26
+ }
27
+ }
19
28
  export {
20
29
  transform
21
30
  };
@@ -1,8 +1,11 @@
1
- import type { Root } from 'hast';
2
- import type MagicString from 'magic-string';
3
- import type { Plugin } from 'unified';
4
- declare const rehypeSlots: Plugin<[{
5
- s: MagicString;
6
- }], Root>;
7
- export default rehypeSlots;
8
1
  export declare const SLOT_PREFIX = "___SLOTS___";
2
+ export interface SlotReplacement {
3
+ start: number;
4
+ end: number;
5
+ value: string;
6
+ }
7
+ /**
8
+ * Find every `<slot>` (excluding `is:inline` ones) and describe how to replace it
9
+ * with a `${___SLOTS___[name] ?? `fallback`}` template literal expression.
10
+ */
11
+ export declare function collectSlots(code: string): SlotReplacement[];
@@ -1,31 +1,24 @@
1
- import { visit } from "unist-util-visit";
1
+ import { ELEMENT_NODE, parse, walkSync } from "ultrahtml";
2
2
  import { escapeTemplateLiteralCharacters } from "./utils.js";
3
- const rehypeSlots = ({ s }) => {
4
- return (tree, file) => {
5
- visit(tree, (node) => {
6
- if (node.type === "element" && node.tagName === "slot") {
7
- if (typeof node.properties?.["is:inline"] !== "undefined") return;
8
- const name = node.properties?.["name"] ?? "default";
9
- const start = node.position?.start.offset ?? 0;
10
- const end = node.position?.end.offset ?? 0;
11
- const first = node.children.at(0) ?? node;
12
- const last = node.children.at(-1) ?? node;
13
- const text = file.value.slice(
14
- first.position?.start.offset ?? 0,
15
- last.position?.end.offset ?? 0
16
- ).toString();
17
- s.overwrite(
18
- start,
19
- end,
20
- `\${${SLOT_PREFIX}["${name}"] ?? \`${escapeTemplateLiteralCharacters(text).trim()}\`}`
21
- );
22
- }
23
- });
24
- };
25
- };
26
- var slots_default = rehypeSlots;
27
3
  const SLOT_PREFIX = `___SLOTS___`;
4
+ function collectSlots(code) {
5
+ const slots = [];
6
+ walkSync(parse(code), (node) => {
7
+ if (node.type !== ELEMENT_NODE || node.name !== "slot") return;
8
+ if ("is:inline" in node.attributes) return;
9
+ const [open, close] = node.loc;
10
+ if (!close) return;
11
+ const name = node.attributes.name ?? "default";
12
+ const fallback = open.end < close.start ? code.slice(open.end, close.start) : code.slice(open.start, close.end);
13
+ slots.push({
14
+ start: open.start,
15
+ end: close.end,
16
+ value: `\${${SLOT_PREFIX}["${name}"] ?? \`${escapeTemplateLiteralCharacters(fallback).trim()}\`}`
17
+ });
18
+ });
19
+ return slots;
20
+ }
28
21
  export {
29
22
  SLOT_PREFIX,
30
- slots_default as default
23
+ collectSlots
31
24
  };
@@ -1,5 +1,2 @@
1
- import type { Element } from 'hast';
2
- import type MagicString from 'magic-string';
3
- export declare function replaceAttribute(s: MagicString, node: Element, key: string, newValue: string): MagicString | undefined;
4
1
  export declare function needsEscape(value: any): value is string;
5
2
  export declare function escapeTemplateLiteralCharacters(value: string): string;
@@ -1,21 +1,3 @@
1
- const splitAttrsTokenizer = /([${}@\w:\-]*)\s*=\s*?(['"]?)(.*?)\2\s+/g;
2
- function replaceAttribute(s, node, key, newValue) {
3
- splitAttrsTokenizer.lastIndex = 0;
4
- const text = s.original.slice(node.position?.start.offset ?? 0, node.position?.end.offset ?? 0).toString();
5
- const offset = text.indexOf(key);
6
- if (offset === -1) return;
7
- const start = node.position.start.offset + offset;
8
- const tokens = text.slice(offset).split(splitAttrsTokenizer);
9
- const token = tokens[0].replace(/([^>])>[\s\S]*$/gm, "$1");
10
- if (token.trim() === key) {
11
- const end = start + key.length;
12
- return s.overwrite(start, end, newValue, { contentOnly: true });
13
- } else {
14
- const length = token.length;
15
- const end = start + length;
16
- return s.overwrite(start, end, newValue, { contentOnly: true });
17
- }
18
- }
19
1
  const NEEDS_ESCAPE_RE = /[`\\]|\$\{/g;
20
2
  function needsEscape(value) {
21
3
  NEEDS_ESCAPE_RE.lastIndex = 0;
@@ -42,6 +24,5 @@ function escapeTemplateLiteralCharacters(value) {
42
24
  }
43
25
  export {
44
26
  escapeTemplateLiteralCharacters,
45
- needsEscape,
46
- replaceAttribute
27
+ needsEscape
47
28
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "7.0.4",
3
+ "version": "7.0.6",
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",
@@ -131,7 +131,6 @@
131
131
  "package-manager-detector": "^1.6.0",
132
132
  "piccolore": "^0.1.3",
133
133
  "picomatch": "^4.0.4",
134
- "rehype": "^13.0.2",
135
134
  "semver": "^7.7.4",
136
135
  "shiki": "^4.0.2",
137
136
  "smol-toml": "^1.6.0",
@@ -141,23 +140,21 @@
141
140
  "tinyglobby": "^0.2.15",
142
141
  "ultrahtml": "^1.6.0",
143
142
  "unifont": "~0.7.4",
144
- "unist-util-visit": "^5.1.0",
145
143
  "unstorage": "^1.17.5",
146
- "vfile": "^6.0.3",
147
144
  "vite": "^8.0.13",
148
145
  "vitefu": "^1.1.2",
149
146
  "xxhash-wasm": "^1.1.0",
150
147
  "yargs-parser": "^22.0.0",
151
148
  "zod": "^4.3.6",
152
- "@astrojs/internal-helpers": "0.10.0",
153
- "@astrojs/markdown-satteri": "0.3.2",
149
+ "@astrojs/internal-helpers": "0.10.1",
150
+ "@astrojs/markdown-satteri": "0.3.3",
154
151
  "@astrojs/telemetry": "3.3.2"
155
152
  },
156
153
  "optionalDependencies": {
157
154
  "sharp": "^0.34.0 || ^0.35.0"
158
155
  },
159
156
  "peerDependencies": {
160
- "@astrojs/markdown-remark": "7.2.0"
157
+ "@astrojs/markdown-remark": "7.2.1"
161
158
  },
162
159
  "peerDependenciesMeta": {
163
160
  "@astrojs/markdown-remark": {
@@ -167,7 +164,6 @@
167
164
  "devDependencies": {
168
165
  "@playwright/test": "1.58.2",
169
166
  "@types/aria-query": "^5.0.4",
170
- "@types/hast": "^3.0.4",
171
167
  "@types/html-escaper": "3.0.4",
172
168
  "@types/http-cache-semantics": "^4.2.0",
173
169
  "@types/js-yaml": "^4.0.9",
@@ -189,10 +185,9 @@
189
185
  "sass": "^1.98.0",
190
186
  "typescript": "^6.0.3",
191
187
  "undici": "^7.22.0",
192
- "unified": "^11.0.5",
193
188
  "vitest": "^4.1.0",
194
189
  "@astrojs/check": "0.9.9",
195
- "@astrojs/markdown-remark": "7.2.0",
190
+ "@astrojs/markdown-remark": "7.2.1",
196
191
  "astro-scripts": "0.0.14"
197
192
  },
198
193
  "engines": {
@@ -85,6 +85,10 @@ declare module 'astro:content' {
85
85
  entry: DataEntryMap[C][string],
86
86
  ): Promise<RenderResult>;
87
87
 
88
+ export function render<C extends keyof LiveContentConfig['collections']>(
89
+ entry: import('astro').LiveDataEntry<LiveLoaderDataType<C>>,
90
+ ): Promise<RenderResult>;
91
+
88
92
  export function reference<
89
93
  C extends
90
94
  | keyof DataEntryMap
@@ -1,8 +0,0 @@
1
- import type { MiddlewareHandler } from '../../types/public/common.js';
2
- /**
3
- * Returns a middleware function in charge to check the `origin` header.
4
- *
5
- * @private
6
- */
7
- export declare function createOriginCheckMiddleware(): MiddlewareHandler;
8
- export declare function hasFormLikeHeader(contentType: string | null): boolean;