astro 2.9.3 → 2.9.4

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.
@@ -13,6 +13,7 @@ const { fallback = 'animate' } = Astro.props as Props;
13
13
  <script>
14
14
  type Fallback = 'none' | 'animate' | 'swap';
15
15
  type Direction = 'forward' | 'back';
16
+ type Events = 'astro:load' | 'astro:beforeload';
16
17
 
17
18
  // The History API does not tell you if navigation is forward or back, so
18
19
  // you can figure it using an index. On pushState the index is incremented so you
@@ -25,7 +26,8 @@ const { fallback = 'animate' } = Astro.props as Props;
25
26
  const supportsViewTransitions = !!document.startViewTransition;
26
27
  const transitionEnabledOnThisPage = () =>
27
28
  !!document.querySelector('[name="astro-view-transitions-enabled"]');
28
- const onload = () => document.dispatchEvent(new Event('astro:load'));
29
+ const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));
30
+ const onload = () => triggerEvent('astro:load');
29
31
 
30
32
  async function getHTML(href: string) {
31
33
  const res = await fetch(href);
@@ -65,7 +67,10 @@ const { fallback = 'animate' } = Astro.props as Props;
65
67
  async function updateDOM(dir: Direction, html: string, fallback?: Fallback) {
66
68
  const doc = parser.parseFromString(html, 'text/html');
67
69
  doc.documentElement.dataset.astroTransition = dir;
68
- const swap = () => document.documentElement.replaceWith(doc.documentElement);
70
+ const swap = () => {
71
+ document.documentElement.replaceWith(doc.documentElement);
72
+ triggerEvent('astro:beforeload');
73
+ };
69
74
 
70
75
  // Wait on links to finish, to prevent FOUC
71
76
  const links = Array.from(doc.querySelectorAll('head link[rel=stylesheet]')).map(
@@ -148,6 +153,7 @@ const { fallback = 'animate' } = Astro.props as Props;
148
153
  link.href &&
149
154
  (!link.target || link.target === '_self') &&
150
155
  link.origin === location.origin &&
156
+ !link.hash &&
151
157
  ev.button === 0 && // left clicks only
152
158
  !ev.metaKey && // new tab (mac)
153
159
  !ev.ctrlKey && // new tab (windows)
@@ -162,13 +168,19 @@ const { fallback = 'animate' } = Astro.props as Props;
162
168
  history.pushState({ index: currentHistoryIndex }, '', link.href);
163
169
  }
164
170
  });
165
- window.addEventListener('popstate', () => {
171
+ window.addEventListener('popstate', (ev) => {
166
172
  if (!transitionEnabledOnThisPage()) {
167
173
  // The current page doesn't haven't View Transitions,
168
174
  // respect that with a full page reload
169
175
  location.reload();
170
176
  return;
171
177
  }
178
+ // hash change creates no state.
179
+ if (ev.state === null) {
180
+ history.replaceState({ index: currentHistoryIndex }, '');
181
+ ev.preventDefault();
182
+ return;
183
+ }
172
184
  const nextIndex = history.state?.index ?? currentHistoryIndex + 1;
173
185
  const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
174
186
  navigate(direction, location.href);
@@ -1826,6 +1826,7 @@ export interface SSRMetadata {
1826
1826
  headInTree: boolean;
1827
1827
  extraHead: string[];
1828
1828
  propagators: Map<AstroComponentFactory, AstroComponentInstance>;
1829
+ contentKeys: Set<string>;
1829
1830
  }
1830
1831
  export interface PreviewServer {
1831
1832
  host?: string;
@@ -1,5 +1,7 @@
1
+ import type { AstroSettings } from '../@types/astro.js';
1
2
  import { type ImageService } from './services/service.js';
2
3
  import type { GetImageResult, ImageMetadata, ImageTransform } from './types.js';
4
+ export declare function injectImageEndpoint(settings: AstroSettings): AstroSettings;
3
5
  export declare function isESMImportedImage(src: ImageMetadata | string): src is ImageMetadata;
4
6
  export declare function getConfiguredImageService(): Promise<ImageService>;
5
7
  export declare function getImage(options: ImageTransform, serviceConfig: Record<string, any>): Promise<GetImageResult>;
@@ -1,5 +1,13 @@
1
1
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
2
2
  import { isLocalService } from "./services/service.js";
3
+ function injectImageEndpoint(settings) {
4
+ settings.injectedRoutes.push({
5
+ pattern: "/_image",
6
+ entryPoint: "astro/assets/image-endpoint",
7
+ prerender: false
8
+ });
9
+ return settings;
10
+ }
3
11
  function isESMImportedImage(src) {
4
12
  return typeof src === "object";
5
13
  }
@@ -44,5 +52,6 @@ async function getImage(options, serviceConfig) {
44
52
  export {
45
53
  getConfiguredImageService,
46
54
  getImage,
55
+ injectImageEndpoint,
47
56
  isESMImportedImage
48
57
  };
@@ -18,10 +18,8 @@ const qualityTable = {
18
18
  webp: baseQuality
19
19
  // Squoosh's PNG encoder does not support a quality setting, so we can skip that here
20
20
  };
21
- async function getRotationForEXIF(transform, inputBuffer) {
22
- const filePath = transform.src.slice("/@fs".length);
23
- const filePathURL = new URL("." + filePath, "file:");
24
- const meta = await imageMetadata(filePathURL, inputBuffer);
21
+ async function getRotationForEXIF(inputBuffer) {
22
+ const meta = await imageMetadata(inputBuffer);
25
23
  if (!meta)
26
24
  return void 0;
27
25
  switch (meta.orientation) {
@@ -47,7 +45,7 @@ const service = {
47
45
  if (format === "svg")
48
46
  return { data: inputBuffer, format: "svg" };
49
47
  const operations = [];
50
- const rotation = await getRotationForEXIF(transform, inputBuffer);
48
+ const rotation = await getRotationForEXIF(inputBuffer);
51
49
  if (rotation) {
52
50
  operations.push(rotation);
53
51
  }
@@ -15,7 +15,7 @@ declare global {
15
15
  };
16
16
  }
17
17
  /**
18
- * Type returned by ESM imports of images and direct calls to imageMetadata
18
+ * Type returned by ESM imports of images
19
19
  */
20
20
  export interface ImageMetadata {
21
21
  src: string;
@@ -1,4 +1,4 @@
1
- import fs from "node:fs";
1
+ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { prependForwardSlash, slash } from "../../core/path.js";
@@ -8,26 +8,36 @@ async function emitESMImage(id, watchMode, fileEmitter) {
8
8
  return void 0;
9
9
  }
10
10
  const url = pathToFileURL(id);
11
- const meta = await imageMetadata(url);
12
- if (!meta) {
11
+ let fileData;
12
+ try {
13
+ fileData = await fs.readFile(url);
14
+ } catch (err) {
13
15
  return void 0;
14
16
  }
17
+ const fileMetadata = await imageMetadata(fileData);
18
+ if (!fileMetadata) {
19
+ return void 0;
20
+ }
21
+ const emittedImage = {
22
+ src: "",
23
+ ...fileMetadata
24
+ };
15
25
  if (!watchMode) {
16
26
  const pathname = decodeURI(url.pathname);
17
- const filename = path.basename(pathname, path.extname(pathname) + `.${meta.format}`);
27
+ const filename = path.basename(pathname, path.extname(pathname) + `.${fileMetadata.format}`);
18
28
  const handle = fileEmitter({
19
29
  name: filename,
20
- source: await fs.promises.readFile(url),
30
+ source: await fs.readFile(url),
21
31
  type: "asset"
22
32
  });
23
- meta.src = `__ASTRO_ASSET_IMAGE__${handle}__`;
33
+ emittedImage.src = `__ASTRO_ASSET_IMAGE__${handle}__`;
24
34
  } else {
25
- url.searchParams.append("origWidth", meta.width.toString());
26
- url.searchParams.append("origHeight", meta.height.toString());
27
- url.searchParams.append("origFormat", meta.format);
28
- meta.src = `/@fs` + prependForwardSlash(fileURLToNormalizedPath(url));
35
+ url.searchParams.append("origWidth", fileMetadata.width.toString());
36
+ url.searchParams.append("origHeight", fileMetadata.height.toString());
37
+ url.searchParams.append("origFormat", fileMetadata.format);
38
+ emittedImage.src = `/@fs` + prependForwardSlash(fileURLToNormalizedPath(url));
29
39
  }
30
- return meta;
40
+ return emittedImage;
31
41
  }
32
42
  function fileURLToNormalizedPath(filePath) {
33
43
  return slash(fileURLToPath(filePath) + filePath.search).replace(/\\/g, "/");
@@ -1,3 +1,3 @@
1
1
  /// <reference types="node" />
2
2
  import type { ImageMetadata } from '../types.js';
3
- export declare function imageMetadata(src: URL | string, data?: Buffer): Promise<ImageMetadata | undefined>;
3
+ export declare function imageMetadata(data: Buffer): Promise<Omit<ImageMetadata, 'src'> | undefined>;
@@ -1,22 +1,11 @@
1
- import fs from "node:fs/promises";
2
- import { fileURLToPath } from "node:url";
3
1
  import imageSize from "../vendor/image-size/index.js";
4
- async function imageMetadata(src, data) {
5
- let file = data;
6
- if (!file) {
7
- try {
8
- file = await fs.readFile(src);
9
- } catch (e) {
10
- return void 0;
11
- }
12
- }
13
- const { width, height, type, orientation } = imageSize(file);
2
+ async function imageMetadata(data) {
3
+ const { width, height, type, orientation } = imageSize(data);
14
4
  const isPortrait = (orientation || 0) >= 5;
15
5
  if (!width || !height || !type) {
16
6
  return void 0;
17
7
  }
18
8
  return {
19
- src: fileURLToPath(src),
20
9
  width: isPortrait ? height : width,
21
10
  height: isPortrait ? width : height,
22
11
  format: type,
@@ -86,7 +86,7 @@ async function add(names, { flags, logging }) {
86
86
  ["svelte", "astro add svelte"],
87
87
  ["solid-js", "astro add solid-js"],
88
88
  ["lit", "astro add lit"],
89
- ["alpine", "astro add alpine"]
89
+ ["alpinejs", "astro add alpinejs"]
90
90
  ],
91
91
  "SSR Adapters": [
92
92
  ["netlify", "astro add netlify"],
@@ -17,11 +17,10 @@ async function loadSettings({ cmd, flags, logging }) {
17
17
  await handleConfigError(e, { cmd, cwd: root, flags, logging });
18
18
  return {};
19
19
  });
20
- const mode = cmd === "build" ? "build" : "dev";
21
20
  if (!initialAstroConfig)
22
21
  return;
23
22
  telemetry.record(event.eventCliSession(cmd, initialUserConfig, flags));
24
- return createSettings(initialAstroConfig, mode, root);
23
+ return createSettings(initialAstroConfig, root);
25
24
  }
26
25
  async function handleConfigError(e, { cmd, cwd, flags, logging }) {
27
26
  const path = await resolveConfigPath({ cwd, flags, fs });
@@ -26,7 +26,7 @@ function getViteConfig(inlineConfig) {
26
26
  level: "info"
27
27
  };
28
28
  const { astroConfig: config } = await openConfig({ cmd });
29
- const settings = createSettings(config, cmd, inlineConfig.root);
29
+ const settings = createSettings(config, inlineConfig.root);
30
30
  await runHookConfigSetup({ settings, command: cmd, logging });
31
31
  const viteConfig = await createVite(
32
32
  {
@@ -5,8 +5,8 @@ import {
5
5
  createComponent,
6
6
  createHeadAndContent,
7
7
  renderComponent,
8
- renderScriptElement,
9
8
  renderTemplate,
9
+ renderUniqueScriptElement,
10
10
  renderUniqueStylesheet,
11
11
  unescapeHTML
12
12
  } from "../runtime/server/index.js";
@@ -222,7 +222,7 @@ async function render({
222
222
  }).join("");
223
223
  }
224
224
  if (Array.isArray(collectedScripts)) {
225
- scripts = collectedScripts.map((script) => renderScriptElement(script)).join("");
225
+ scripts = collectedScripts.map((script) => renderUniqueScriptElement(result, script)).join("");
226
226
  }
227
227
  let props = baseProps;
228
228
  if (id.endsWith("mdx")) {
@@ -84,11 +84,14 @@ function chunkIsPage(settings, output, internals) {
84
84
  return false;
85
85
  }
86
86
  async function generatePages(opts, internals) {
87
+ var _a, _b;
87
88
  const timer = performance.now();
88
89
  const ssr = isServerLikeOutput(opts.settings.config);
89
90
  const outFolder = ssr ? opts.settings.config.build.server : getOutDirWithinCwd(opts.settings.config.outDir);
90
- if (ssr && !hasPrerenderedPages(internals))
91
+ if (ssr && !hasPrerenderedPages(internals)) {
92
+ (_a = globalThis == null ? void 0 : globalThis.astroAsset) == null ? true : delete _a.addStaticImage;
91
93
  return;
94
+ }
92
95
  const verb = ssr ? "prerendering" : "generating";
93
96
  info(opts.logging, null, `
94
97
  ${bgGreen(black(` ${verb} static routes `))}`);
@@ -139,7 +142,7 @@ ${bgGreen(black(` generating optimized images `))}`);
139
142
  for (const imageData of getStaticImageList()) {
140
143
  await generateImage(opts, imageData[1].options, imageData[1].path);
141
144
  }
142
- delete globalThis.astroAsset.addStaticImage;
145
+ (_b = globalThis == null ? void 0 : globalThis.astroAsset) == null ? true : delete _b.addStaticImage;
143
146
  }
144
147
  await runHookBuildGenerated({
145
148
  config: opts.settings.config,
@@ -1,12 +1,14 @@
1
1
  import * as colors from "kleur/colors";
2
2
  import fs from "node:fs";
3
3
  import { performance } from "node:perf_hooks";
4
+ import { injectImageEndpoint } from "../../assets/internal.js";
4
5
  import {
5
6
  runHookBuildDone,
6
7
  runHookBuildStart,
7
8
  runHookConfigDone,
8
9
  runHookConfigSetup
9
10
  } from "../../integrations/index.js";
11
+ import { isServerLikeOutput } from "../../prerender/utils.js";
10
12
  import { createVite } from "../create-vite.js";
11
13
  import { debug, info, levels, timerMessage, warn } from "../logger/core.js";
12
14
  import { printHelp } from "../messages.js";
@@ -60,6 +62,9 @@ class AstroBuilder {
60
62
  command: "build",
61
63
  logging
62
64
  });
65
+ if (this.settings.config.experimental.assets && isServerLikeOutput(this.settings.config)) {
66
+ this.settings = injectImageEndpoint(this.settings);
67
+ }
63
68
  this.manifest = createRouteManifest({ settings: this.settings }, this.logging);
64
69
  const viteConfig = await createVite(
65
70
  {
@@ -1,4 +1,4 @@
1
1
  import type { AstroConfig, AstroSettings, AstroUserConfig } from '../../@types/astro';
2
- export declare function createBaseSettings(config: AstroConfig, mode: 'build' | 'dev'): AstroSettings;
3
- export declare function createSettings(config: AstroConfig, mode: 'build' | 'dev', cwd?: string): AstroSettings;
2
+ export declare function createBaseSettings(config: AstroConfig): AstroSettings;
3
+ export declare function createSettings(config: AstroConfig, cwd?: string): AstroSettings;
4
4
  export declare function createDefaultDevSettings(userConfig?: AstroUserConfig, root?: string | URL): Promise<AstroSettings>;
@@ -3,7 +3,6 @@ import path from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { getContentPaths } from "../../content/index.js";
5
5
  import jsxRenderer from "../../jsx/renderer.js";
6
- import { isServerLikeOutput } from "../../prerender/utils.js";
7
6
  import { markdownContentEntryType } from "../../vite-plugin-markdown/content-entry-type.js";
8
7
  import { getDefaultClientDirectives } from "../client-directive/index.js";
9
8
  import { AstroError, AstroErrorData } from "../errors/index.js";
@@ -12,14 +11,14 @@ import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from "./../constants.js";
12
11
  import { createDefaultDevConfig } from "./config.js";
13
12
  import { AstroTimer } from "./timer.js";
14
13
  import { loadTSConfig } from "./tsconfig.js";
15
- function createBaseSettings(config, mode) {
14
+ function createBaseSettings(config) {
16
15
  const { contentDir } = getContentPaths(config);
17
16
  return {
18
17
  config,
19
18
  tsConfig: void 0,
20
19
  tsConfigPath: void 0,
21
20
  adapter: void 0,
22
- injectedRoutes: config.experimental.assets && (isServerLikeOutput(config) || mode === "dev") ? [{ pattern: "/_image", entryPoint: "astro/assets/image-endpoint", prerender: false }] : [],
21
+ injectedRoutes: [],
23
22
  pageExtensions: [".astro", ".html", ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS],
24
23
  contentEntryTypes: [markdownContentEntryType],
25
24
  dataEntryTypes: [
@@ -92,9 +91,9 @@ function createBaseSettings(config, mode) {
92
91
  timer: new AstroTimer()
93
92
  };
94
93
  }
95
- function createSettings(config, mode, cwd) {
94
+ function createSettings(config, cwd) {
96
95
  const tsconfig = loadTSConfig(cwd);
97
- const settings = createBaseSettings(config, mode);
96
+ const settings = createBaseSettings(config);
98
97
  const watchFiles = (tsconfig == null ? void 0 : tsconfig.exists) ? [tsconfig.path, ...tsconfig.extendedPaths] : [];
99
98
  if (cwd) {
100
99
  watchFiles.push(fileURLToPath(new URL("./package.json", pathToFileURL(cwd))));
@@ -109,7 +108,7 @@ async function createDefaultDevSettings(userConfig = {}, root) {
109
108
  root = fileURLToPath(root);
110
109
  }
111
110
  const config = await createDefaultDevConfig(userConfig, root);
112
- return createBaseSettings(config, "dev");
111
+ return createBaseSettings(config);
113
112
  }
114
113
  export {
115
114
  createBaseSettings,
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "2.9.3";
1
+ const ASTRO_VERSION = "2.9.4";
2
2
  const SUPPORTED_MARKDOWN_FILE_EXTENSIONS = [
3
3
  ".markdown",
4
4
  ".mdown",
@@ -1,5 +1,6 @@
1
1
  import nodeFs from "node:fs";
2
2
  import * as vite from "vite";
3
+ import { injectImageEndpoint } from "../../assets/internal.js";
3
4
  import {
4
5
  runHookConfigDone,
5
6
  runHookConfigSetup,
@@ -29,6 +30,9 @@ async function createContainer(params = {}) {
29
30
  logging,
30
31
  isRestart
31
32
  });
33
+ if (settings.config.experimental.assets) {
34
+ settings = injectImageEndpoint(settings);
35
+ }
32
36
  const { host, headers, open } = settings.config.server;
33
37
  const rendererClientEntries = settings.renderers.map((r) => r.clientEntrypoint).filter(Boolean);
34
38
  const viteConfig = await createVite(
@@ -54,7 +54,7 @@ async function dev(settings, options) {
54
54
  isRestart: options.isRestart
55
55
  })
56
56
  );
57
- const currentVersion = "2.9.3";
57
+ const currentVersion = "2.9.4";
58
58
  if (currentVersion.includes("-")) {
59
59
  warn(options.logging, null, msg.prerelease({ currentVersion }));
60
60
  }
@@ -62,7 +62,7 @@ async function restartContainer({
62
62
  });
63
63
  info(logging, "astro", logMsg + "\n");
64
64
  let astroConfig = newConfig.astroConfig;
65
- const settings = createSettings(astroConfig, "dev", resolvedRoot);
65
+ const settings = createSettings(astroConfig, resolvedRoot);
66
66
  await close();
67
67
  return {
68
68
  container: await createRestartedContainer(container, settings, needsStart),
@@ -47,7 +47,7 @@ function serverStart({
47
47
  base,
48
48
  isRestart = false
49
49
  }) {
50
- const version = "2.9.3";
50
+ const version = "2.9.4";
51
51
  const localPrefix = `${dim("\u2503")} Local `;
52
52
  const networkPrefix = `${dim("\u2503")} Network `;
53
53
  const emptyPrefix = " ".repeat(11);
@@ -233,7 +233,7 @@ function printHelp({
233
233
  message.push(
234
234
  linebreak(),
235
235
  ` ${bgGreen(black(` ${commandName} `))} ${green(
236
- `v${"2.9.3"}`
236
+ `v${"2.9.4"}`
237
237
  )} ${headline}`
238
238
  );
239
239
  }
@@ -178,7 +178,8 @@ function createResult(args) {
178
178
  hasDirectives: /* @__PURE__ */ new Set(),
179
179
  headInTree: false,
180
180
  extraHead: [],
181
- propagators: /* @__PURE__ */ new Map()
181
+ propagators: /* @__PURE__ */ new Map(),
182
+ contentKeys: /* @__PURE__ */ new Set()
182
183
  }
183
184
  };
184
185
  return result;
@@ -3,7 +3,7 @@ export { createAstro } from './astro-global.js';
3
3
  export { renderEndpoint } from './endpoint.js';
4
4
  export { escapeHTML, HTMLBytes, HTMLString, isHTMLString, markHTMLString, unescapeHTML, } from './escape.js';
5
5
  export { renderJSX } from './jsx.js';
6
- export { addAttribute, createHeadAndContent, defineScriptVars, Fragment, maybeRenderHead, renderTemplate as render, renderComponent, Renderer as Renderer, renderHead, renderHTMLElement, renderPage, renderScriptElement, renderSlot, renderSlotToString, renderTemplate, renderToString, renderUniqueStylesheet, voidElementNames, } from './render/index.js';
6
+ export { addAttribute, createHeadAndContent, defineScriptVars, Fragment, maybeRenderHead, renderTemplate as render, renderComponent, Renderer as Renderer, renderHead, renderHTMLElement, renderPage, renderScriptElement, renderSlot, renderSlotToString, renderTemplate, renderToString, renderUniqueScriptElement, renderUniqueStylesheet, voidElementNames, } from './render/index.js';
7
7
  export type { AstroComponentFactory, AstroComponentInstance, ComponentSlots, RenderInstruction, } from './render/index.js';
8
8
  export { renderTransition } from './transition.js';
9
9
  export declare function mergeSlots(...slotted: unknown[]): Record<string, () => any>;
@@ -27,6 +27,7 @@ import {
27
27
  renderSlotToString,
28
28
  renderTemplate as renderTemplate2,
29
29
  renderToString,
30
+ renderUniqueScriptElement,
30
31
  renderUniqueStylesheet,
31
32
  voidElementNames
32
33
  } from "./render/index.js";
@@ -115,6 +116,7 @@ export {
115
116
  renderTemplate2 as renderTemplate,
116
117
  renderToString,
117
118
  renderTransition,
119
+ renderUniqueScriptElement,
118
120
  renderUniqueStylesheet,
119
121
  spreadAttributes,
120
122
  unescapeHTML,
@@ -41,6 +41,9 @@ async function renderToReadableStream(result, componentFactory, props, children,
41
41
  );
42
42
  if (templateResult instanceof Response)
43
43
  return templateResult;
44
+ if (isPage) {
45
+ await bufferHeadContent(result);
46
+ }
44
47
  let renderedFirstPageChunk = false;
45
48
  return new ReadableStream({
46
49
  start(controller) {
@@ -93,6 +96,19 @@ async function callComponentAsTemplateResultOrResponse(result, componentFactory,
93
96
  }
94
97
  return isHeadAndContent(factoryResult) ? factoryResult.content : factoryResult;
95
98
  }
99
+ async function bufferHeadContent(result) {
100
+ const iterator = result._metadata.propagators.values();
101
+ while (true) {
102
+ const { value, done } = iterator.next();
103
+ if (done) {
104
+ break;
105
+ }
106
+ const returnValue = await value.init(result);
107
+ if (isHeadAndContent(returnValue)) {
108
+ result._metadata.extraHead.push(returnValue.head);
109
+ }
110
+ }
111
+ }
96
112
  export {
97
113
  renderToReadableStream,
98
114
  renderToString
@@ -6,6 +6,6 @@ export { renderHTMLElement } from './dom.js';
6
6
  export { maybeRenderHead, renderHead } from './head.js';
7
7
  export { renderPage } from './page.js';
8
8
  export { renderSlot, renderSlotToString, type ComponentSlots } from './slot.js';
9
- export { renderScriptElement, renderUniqueStylesheet } from './tags.js';
9
+ export { renderScriptElement, renderUniqueScriptElement, renderUniqueStylesheet } from './tags.js';
10
10
  export type { RenderInstruction } from './types';
11
11
  export { addAttribute, defineScriptVars, voidElementNames } from './util.js';
@@ -5,7 +5,7 @@ import { renderHTMLElement } from "./dom.js";
5
5
  import { maybeRenderHead, renderHead } from "./head.js";
6
6
  import { renderPage } from "./page.js";
7
7
  import { renderSlot, renderSlotToString } from "./slot.js";
8
- import { renderScriptElement, renderUniqueStylesheet } from "./tags.js";
8
+ import { renderScriptElement, renderUniqueScriptElement, renderUniqueStylesheet } from "./tags.js";
9
9
  import { addAttribute, defineScriptVars, voidElementNames } from "./util.js";
10
10
  export {
11
11
  Fragment,
@@ -26,6 +26,7 @@ export {
26
26
  renderSlotToString,
27
27
  renderTemplate,
28
28
  renderToString,
29
+ renderUniqueScriptElement,
29
30
  renderUniqueStylesheet,
30
31
  voidElementNames
31
32
  };
@@ -1,4 +1,5 @@
1
1
  import type { SSRElement, SSRResult } from '../../../@types/astro';
2
2
  import type { StylesheetAsset } from '../../../core/app/types';
3
3
  export declare function renderScriptElement({ props, children }: SSRElement): string;
4
+ export declare function renderUniqueScriptElement(result: SSRResult, { props, children }: SSRElement): string;
4
5
  export declare function renderUniqueStylesheet(result: SSRResult, sheet: StylesheetAsset): string | undefined;
@@ -5,19 +5,52 @@ function renderScriptElement({ props, children }) {
5
5
  children
6
6
  });
7
7
  }
8
+ function renderUniqueScriptElement(result, { props, children }) {
9
+ if (Array.from(result.scripts).some((s) => {
10
+ if (s.props.type === props.type && s.props.src === props.src) {
11
+ return true;
12
+ }
13
+ if (!props.src && s.children === children)
14
+ return true;
15
+ }))
16
+ return "";
17
+ const key = `script-${props.type}-${props.src}-${children}`;
18
+ if (checkOrAddContentKey(result, key))
19
+ return "";
20
+ return renderScriptElement({ props, children });
21
+ }
8
22
  function renderUniqueStylesheet(result, sheet) {
9
23
  if (sheet.type === "external") {
10
24
  if (Array.from(result.styles).some((s) => s.props.href === sheet.src))
11
25
  return "";
12
- return renderElement("link", { props: { rel: "stylesheet", href: sheet.src }, children: "" });
26
+ const key = "link-external-" + sheet.src;
27
+ if (checkOrAddContentKey(result, key))
28
+ return "";
29
+ return renderElement("link", {
30
+ props: {
31
+ rel: "stylesheet",
32
+ href: sheet.src
33
+ },
34
+ children: ""
35
+ });
13
36
  }
14
37
  if (sheet.type === "inline") {
15
38
  if (Array.from(result.styles).some((s) => s.children.includes(sheet.content)))
16
39
  return "";
40
+ const key = `link-inline-` + sheet.content;
41
+ if (checkOrAddContentKey(result, key))
42
+ return "";
17
43
  return renderElement("style", { props: { type: "text/css" }, children: sheet.content });
18
44
  }
19
45
  }
46
+ function checkOrAddContentKey(result, key) {
47
+ if (result._metadata.contentKeys.has(key))
48
+ return true;
49
+ result._metadata.contentKeys.add(key);
50
+ return false;
51
+ }
20
52
  export {
21
53
  renderScriptElement,
54
+ renderUniqueScriptElement,
22
55
  renderUniqueStylesheet
23
56
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "2.9.3",
3
+ "version": "2.9.4",
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",
@@ -102,7 +102,7 @@
102
102
  "vendor"
103
103
  ],
104
104
  "dependencies": {
105
- "@astrojs/compiler": "^1.6.0",
105
+ "@astrojs/compiler": "^1.6.3",
106
106
  "@astrojs/internal-helpers": "^0.1.1",
107
107
  "@astrojs/language-server": "^1.0.0",
108
108
  "@astrojs/markdown-remark": "^2.2.1",