astro 7.0.4 → 7.0.5

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 (33) hide show
  1. package/dist/cli/dev/background.js +4 -2
  2. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  3. package/dist/content/content-layer.js +3 -3
  4. package/dist/core/build/plugins/index.js +2 -0
  5. package/dist/core/build/plugins/plugin-css-target-lowering.d.ts +14 -0
  6. package/dist/core/build/plugins/plugin-css-target-lowering.js +82 -0
  7. package/dist/core/constants.js +1 -1
  8. package/dist/core/create-vite.js +2 -0
  9. package/dist/core/dev/dev.js +1 -1
  10. package/dist/core/fetch/fetch-state.js +2 -0
  11. package/dist/core/head-propagation/buffer.d.ts +14 -3
  12. package/dist/core/head-propagation/buffer.js +17 -7
  13. package/dist/core/messages/runtime.js +1 -1
  14. package/dist/prefetch/index.js +10 -0
  15. package/dist/runtime/server/render/astro/instance.js +3 -0
  16. package/dist/runtime/server/render/page.js +7 -2
  17. package/dist/runtime/server/render/server-islands-shared.d.ts +1 -0
  18. package/dist/runtime/server/render/server-islands-shared.js +4 -0
  19. package/dist/runtime/server/render/server-islands.js +3 -2
  20. package/dist/template/4xx.js +1 -1
  21. package/dist/transitions/swap-functions.js +9 -2
  22. package/dist/types/public/internal.d.ts +14 -0
  23. package/dist/vite-plugin-astro/index.js +3 -1
  24. package/dist/vite-plugin-config-alias/index.d.ts +19 -1
  25. package/dist/vite-plugin-config-alias/index.js +6 -5
  26. package/dist/vite-plugin-html/transform/index.js +16 -7
  27. package/dist/vite-plugin-html/transform/slots.d.ts +10 -7
  28. package/dist/vite-plugin-html/transform/slots.js +19 -26
  29. package/dist/vite-plugin-html/transform/utils.d.ts +0 -3
  30. package/dist/vite-plugin-html/transform/utils.js +1 -20
  31. package/package.json +1 -6
  32. package/dist/vite-plugin-html/transform/escape.d.ts +0 -7
  33. package/dist/vite-plugin-html/transform/escape.js +0 -30
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync, mkdirSync, openSync } from "node:fs";
3
- import { resolve } from "node:path";
3
+ import { createRequire } from "node:module";
4
+ import { dirname, resolve } from "node:path";
4
5
  import { fileURLToPath, pathToFileURL } from "node:url";
5
6
  import {
6
7
  checkExistingServer,
@@ -11,6 +12,7 @@ import {
11
12
  GRACEFUL_SHUTDOWN_TIMEOUT
12
13
  } from "../../core/dev/lockfile.js";
13
14
  import { resolveRoot } from "../../core/config/config.js";
15
+ const require2 = createRequire(import.meta.url);
14
16
  function formatBackgroundOutput(result) {
15
17
  return JSON.stringify(result);
16
18
  }
@@ -76,7 +78,7 @@ async function background({
76
78
  }
77
79
  const logFd = openSync(logFilePath, "w");
78
80
  const rootPath = fileURLToPath(root);
79
- const astroBin = resolve(rootPath, "node_modules", "astro", "bin", "astro.mjs");
81
+ const astroBin = resolve(dirname(require2.resolve("astro/package.json")), "bin", "astro.mjs");
80
82
  const child = spawn(process.execPath, [astroBin, ...args], {
81
83
  detached: true,
82
84
  stdio: ["ignore", logFd, logFd],
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "7.0.4";
3
+ version = "7.0.5";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -197,7 +197,7 @@ ${contentConfig.error.message}`
197
197
  logger.info("Content config changed");
198
198
  shouldClear = true;
199
199
  }
200
- if (previousAstroVersion && previousAstroVersion !== "7.0.4") {
200
+ if (previousAstroVersion && previousAstroVersion !== "7.0.5") {
201
201
  logger.info("Astro version changed");
202
202
  shouldClear = true;
203
203
  }
@@ -205,8 +205,8 @@ ${contentConfig.error.message}`
205
205
  logger.info("Clearing content store");
206
206
  this.#store.clearAll();
207
207
  }
208
- if ("7.0.4") {
209
- this.#store.metaStore().set("astro-version", "7.0.4");
208
+ if ("7.0.5") {
209
+ this.#store.metaStore().set("astro-version", "7.0.5");
210
210
  }
211
211
  if (currentConfigDigest) {
212
212
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -3,6 +3,7 @@ import { astroHeadBuildPlugin } from "../../../vite-plugin-head/index.js";
3
3
  import { pluginAnalyzer } from "./plugin-analyzer.js";
4
4
  import { pluginComponentEntry } from "./plugin-component-entry.js";
5
5
  import { pluginCSS } from "./plugin-css.js";
6
+ import { pluginCssTargetLowering } from "./plugin-css-target-lowering.js";
6
7
  import { pluginInternals } from "./plugin-internals.js";
7
8
  import { pluginMiddleware } from "./plugin-middleware.js";
8
9
  import { pluginPrerender } from "./plugin-prerender.js";
@@ -18,6 +19,7 @@ function getAllBuildPlugins(internals, options) {
18
19
  pluginInternals(options, internals),
19
20
  pluginMiddleware(options, internals),
20
21
  vitePluginActionsBuild(options, internals),
22
+ pluginCssTargetLowering(),
21
23
  ...pluginCSS(options, internals),
22
24
  astroHeadBuildPlugin(internals),
23
25
  pluginPrerender(options, internals),
@@ -0,0 +1,14 @@
1
+ import type { Plugin as VitePlugin } from 'vite';
2
+ /**
3
+ * Vite plugin that applies CSS target lowering without minification.
4
+ *
5
+ * Vite's `finalizeCss` only applies CSS target lowering (via lightningcss) when
6
+ * `config.build.cssMinify` is truthy, because target lowering and minification
7
+ * are coupled in `minifyCSS`. When users set `minify: false`, Astro forces
8
+ * `cssMinify: false`, which disables target lowering for all CSS.
9
+ *
10
+ * This plugin fills that gap: when `cssMinify` is falsy and a CSS target is
11
+ * configured, it runs lightningcss with `minify: false` on all CSS assets in
12
+ * `generateBundle`, applying target lowering without minification.
13
+ */
14
+ export declare function pluginCssTargetLowering(): VitePlugin;
@@ -0,0 +1,82 @@
1
+ import { createRequire } from "node:module";
2
+ const BROWSER_MAP = {
3
+ chrome: "chrome",
4
+ edge: "edge",
5
+ firefox: "firefox",
6
+ hermes: false,
7
+ ie: "ie",
8
+ ios: "ios_saf",
9
+ node: false,
10
+ opera: "opera",
11
+ rhino: false,
12
+ safari: "safari"
13
+ };
14
+ const VERSION_RE = /\d/;
15
+ function convertTargets(esbuildTarget) {
16
+ if (!esbuildTarget) return {};
17
+ const targets = {};
18
+ const entries = Array.isArray(esbuildTarget) ? esbuildTarget : [esbuildTarget];
19
+ for (const entry of entries) {
20
+ if (entry === "esnext") continue;
21
+ const index = entry.search(VERSION_RE);
22
+ if (index >= 0) {
23
+ const browserName = entry.slice(0, index);
24
+ const browser = BROWSER_MAP[browserName];
25
+ if (browser === false) continue;
26
+ if (browser) {
27
+ const [major, minor = 0] = entry.slice(index).split(".").map((v) => Number.parseInt(v, 10));
28
+ if (!isNaN(major) && !isNaN(minor)) {
29
+ const version = major << 16 | minor << 8;
30
+ if (!targets[browser] || version < targets[browser]) {
31
+ targets[browser] = version;
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+ return targets;
38
+ }
39
+ function pluginCssTargetLowering() {
40
+ let resolvedConfig;
41
+ return {
42
+ name: "astro:css-target-lowering",
43
+ enforce: "post",
44
+ configResolved(config) {
45
+ resolvedConfig = config;
46
+ },
47
+ async generateBundle(_outputOptions, bundle) {
48
+ if (resolvedConfig.build.cssMinify) return;
49
+ const cssTarget = resolvedConfig.build.cssTarget;
50
+ if (!cssTarget) return;
51
+ const targets = convertTargets(cssTarget);
52
+ if (Object.keys(targets).length === 0) return;
53
+ let lcssTransform;
54
+ try {
55
+ const requireFromVite = createRequire(import.meta.resolve("vite"));
56
+ lcssTransform = requireFromVite("lightningcss").transform;
57
+ } catch {
58
+ return;
59
+ }
60
+ for (const [, asset] of Object.entries(bundle)) {
61
+ if (asset.type !== "asset") continue;
62
+ if (!asset.fileName.endsWith(".css")) continue;
63
+ if (typeof asset.source !== "string") continue;
64
+ try {
65
+ const result = lcssTransform({
66
+ ...resolvedConfig.css?.lightningcss,
67
+ targets,
68
+ cssModules: void 0,
69
+ filename: asset.fileName,
70
+ code: Buffer.from(asset.source),
71
+ minify: false
72
+ });
73
+ asset.source = new TextDecoder().decode(result.code);
74
+ } catch {
75
+ }
76
+ }
77
+ }
78
+ };
79
+ }
80
+ export {
81
+ pluginCssTargetLowering
82
+ };
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "7.0.4";
1
+ const ASTRO_VERSION = "7.0.5";
2
2
  const ASTRO_GENERATOR = `Astro v${ASTRO_VERSION}`;
3
3
  const ASTRO_ERROR_HEADER = "X-Astro-Error";
4
4
  const DEFAULT_404_COMPONENT = "astro-default-404.astro";
@@ -201,6 +201,8 @@ async function createVite(commandConfig, { settings, logger, mode, command, fs =
201
201
  }
202
202
  },
203
203
  resolve: {
204
+ // Vite's native tsconfig path resolution; see configAliasVitePlugin for the deprecated fallback.
205
+ tsconfigPaths: true,
204
206
  alias: [
205
207
  {
206
208
  // This is needed for Deno compatibility, as the non-browser version
@@ -26,7 +26,7 @@ async function dev(inlineConfig) {
26
26
  await telemetry.record([]);
27
27
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
28
28
  const logger = restart.container.logger;
29
- const currentVersion = "7.0.4";
29
+ const currentVersion = "7.0.5";
30
30
  const isPrerelease = currentVersion.includes("-");
31
31
  if (!isPrerelease) {
32
32
  try {
@@ -297,6 +297,8 @@ class FetchState {
297
297
  extraStyleHashes,
298
298
  extraScriptHashes,
299
299
  propagators: /* @__PURE__ */ new Set(),
300
+ routeHasPropagation: false,
301
+ pendingSlotEvaluations: [],
300
302
  templateDepth: 0
301
303
  },
302
304
  cspDestination: manifest.csp?.cspDestination ?? (routeData.prerender ? "meta" : "header"),
@@ -3,10 +3,21 @@ export interface HeadPropagator {
3
3
  init(result: SSRResult): unknown | Promise<unknown>;
4
4
  }
5
5
  /**
6
- * Runs all registered propagators and collects emitted head HTML strings.
6
+ * Runs all registered propagators and collects the head HTML they emit.
7
7
  *
8
- * This iterates the live `Set`, so propagators discovered during iteration
9
- * are also processed in the same pass.
8
+ * Components with head content are discovered as we go. Initializing one
9
+ * propagator can register more of them: a component marked `in-tree` renders
10
+ * its children, and one of those children may be a `self` component that emits
11
+ * styles. Slots add a second way to find them — a slot whose markup contains an
12
+ * `await` only reaches the components after that `await` once it resolves, so
13
+ * we also wait for those pending slot pre-renders. We keep initializing
14
+ * propagators and waiting on slots until no new ones appear.
15
+ *
16
+ * Propagators are tracked in a `seen` set rather than read through a single
17
+ * live `Set` iterator. A propagator can be registered after we have already
18
+ * iterated to the end of the set (e.g. once a slot's `await` resolves), and a
19
+ * `Set` iterator that has reported `done` would never report those late
20
+ * additions.
10
21
  *
11
22
  * @example
12
23
  * If a layout initializes and discovers a nested component that also emits
@@ -1,15 +1,25 @@
1
1
  async function collectPropagatedHeadParts(input) {
2
2
  const collectedHeadParts = [];
3
- const iterator = input.propagators.values();
3
+ const seen = /* @__PURE__ */ new Set();
4
+ const pendingSlotEvaluations = input.result._metadata?.pendingSlotEvaluations ?? [];
4
5
  while (true) {
5
- const { value, done } = iterator.next();
6
- if (done) {
7
- break;
6
+ if (pendingSlotEvaluations.length > 0) {
7
+ const batch = pendingSlotEvaluations.splice(0, pendingSlotEvaluations.length);
8
+ await Promise.all(batch);
9
+ continue;
8
10
  }
9
- const returnValue = await value.init(input.result);
10
- if (input.isHeadAndContent(returnValue) && returnValue.head) {
11
- collectedHeadParts.push(returnValue.head);
11
+ let progressed = false;
12
+ for (const propagator of input.propagators) {
13
+ if (seen.has(propagator)) continue;
14
+ seen.add(propagator);
15
+ progressed = true;
16
+ const returnValue = await propagator.init(input.result);
17
+ if (input.isHeadAndContent(returnValue) && returnValue.head) {
18
+ collectedHeadParts.push(returnValue.head);
19
+ }
20
+ break;
12
21
  }
22
+ if (!progressed) break;
13
23
  }
14
24
  return collectedHeadParts;
15
25
  }
@@ -269,7 +269,7 @@ function printHelp({
269
269
  message.push(
270
270
  linebreak(),
271
271
  ` ${bgGreen(black(` ${commandName} `))} ${green(
272
- `v${"7.0.4"}`
272
+ `v${"7.0.5"}`
273
273
  )} ${headline}`
274
274
  );
275
275
  }
@@ -191,6 +191,16 @@ function onPageLoad(cb) {
191
191
  }
192
192
  cb();
193
193
  });
194
+ new MutationObserver((mutations) => {
195
+ for (const mutation of mutations) {
196
+ for (const node of mutation.addedNodes) {
197
+ if (node instanceof Element && (node.tagName === "A" || node.querySelector?.("a"))) {
198
+ cb();
199
+ return;
200
+ }
201
+ }
202
+ }
203
+ }).observe(document.body, { childList: true, subtree: true });
194
204
  }
195
205
  function appendSpeculationRules(url, eagerness) {
196
206
  const script = document.createElement("script");
@@ -18,6 +18,9 @@ class AstroComponentInstance {
18
18
  for (const name in slots) {
19
19
  let didRender = false;
20
20
  let value = slots[name](result);
21
+ if (result._metadata.routeHasPropagation && isPromise(value)) {
22
+ result._metadata.pendingSlotEvaluations.push(value);
23
+ }
21
24
  this.slotValues[name] = () => {
22
25
  if (!didRender) {
23
26
  didRender = true;
@@ -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));
@@ -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
@@ -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");
@@ -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
@@ -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.5",
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,9 +140,7 @@
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",
@@ -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,7 +185,6 @@
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
190
  "@astrojs/markdown-remark": "7.2.0",
@@ -1,7 +0,0 @@
1
- import type { Root } from 'hast';
2
- import type MagicString from 'magic-string';
3
- import type { Plugin } from 'unified';
4
- declare const rehypeEscape: Plugin<[{
5
- s: MagicString;
6
- }], Root>;
7
- export default rehypeEscape;
@@ -1,30 +0,0 @@
1
- import { visit } from "unist-util-visit";
2
- import { escapeTemplateLiteralCharacters, needsEscape, replaceAttribute } from "./utils.js";
3
- const rehypeEscape = ({ s }) => {
4
- return (tree) => {
5
- visit(tree, (node) => {
6
- if (node.type === "text" || node.type === "comment") {
7
- if (needsEscape(node.value)) {
8
- s.overwrite(
9
- node.position.start.offset,
10
- node.position.end.offset,
11
- escapeTemplateLiteralCharacters(node.value)
12
- );
13
- }
14
- } else if (node.type === "element") {
15
- if (!node.properties) return;
16
- for (let [key, value] of Object.entries(node.properties)) {
17
- key = key.replace(/([A-Z])/g, "-$1").toLowerCase();
18
- const newKey = needsEscape(key) ? escapeTemplateLiteralCharacters(key) : key;
19
- const newValue = needsEscape(value) ? escapeTemplateLiteralCharacters(value) : value;
20
- if (newKey === key && newValue === value) continue;
21
- replaceAttribute(s, node, key, value === "" ? newKey : `${newKey}="${newValue}"`);
22
- }
23
- }
24
- });
25
- };
26
- };
27
- var escape_default = rehypeEscape;
28
- export {
29
- escape_default as default
30
- };