Skip to content

Commit

Permalink
add behind an experiment flag
Browse files Browse the repository at this point in the history
  • Loading branch information
stramel committed Oct 5, 2024
1 parent f129438 commit ad55d4e
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 20 deletions.
6 changes: 4 additions & 2 deletions packages/astro/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
/// <reference path="./types/content.d.ts" />
/// <reference path="./types/actions.d.ts" />

import type { SvgRenderMode } from './dist/assets/utils/svg';

// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace App {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
Expand Down Expand Up @@ -123,9 +125,9 @@ declare module '*.svg' {
*/
size?: number | string;
/**
* Bypasses automatic sprite optimization by directly inlinining the SVG
* Override the default rendering mode for SVGs
*/
inline?: boolean
mode?: SvgRenderMode
} & astroHTML.JSX.SVGAttributes

const Component: ((_props: Props) => any) & ImageMetadata;
Expand Down
17 changes: 17 additions & 0 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import type {
REDIRECT_STATUS_CODES,
SUPPORTED_MARKDOWN_FILE_EXTENSIONS,
} from './../core/constants.js';
import type { SvgRenderMode } from '../assets/utils/svg.js';

export type { AstroIntegrationLogger, ToolbarServerHelpers };

Expand Down Expand Up @@ -2348,6 +2349,22 @@ export interface AstroUserConfig {
* For a complete overview and the full API reference, see [the Content Layer API RFC](https://github.com/withastro/roadmap/blob/content-layer/proposals/0050-content-layer.md) and [share your feedback](https://github.com/withastro/roadmap/pull/982).
*/
contentLayer?: boolean;

/**
* @docs
* @name experimental.svg
* @type {object}
* @default `undefined`
*/
svg?: {
/**
* @docs
* @name experimental.svg.mode
* @type {string}
* @default 'inline'
*/
mode?: SvgRenderMode;
};
};
}

Expand Down
25 changes: 12 additions & 13 deletions packages/astro/src/assets/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,23 @@ export function createSvgComponent({ meta, attributes, children }: SvgComponentP
const id = `a:${ids++}`;
const rendered = new WeakSet<Response>();
const Component = createComponent((result, props) => {
const { title: titleProp, viewBox, ...normalizedProps } = normalizeProps(attributes, props);
const { title: titleProp, viewBox, mode, ...normalizedProps } = normalizeProps(attributes, props);
const title = titleProp ? unescapeHTML(`<title>${titleProp}</title>`) : '';

// Bypasses automatic sprite optimization and directly inline the SVG
if (normalizedProps['inline']) {
delete normalizedProps.inline;
return render`<svg${spreadAttributes({viewBox, ...normalizedProps})}>${title}${unescapeHTML(children)}</svg>`
}
if (mode === 'sprite') {
// On the first render, include the symbol definition
let symbol: any = '';
if (!rendered.has(result.response)) {
// We only need the viewBox on the symbol definition, we can drop it everywhere else
symbol = unescapeHTML(`<symbol${spreadAttributes({ viewBox, id })}>${children}</symbol>`);
rendered.add(result.response);
}

// On the first render, include the symbol definition
let symbol: any = '';
if (!rendered.has(result.response)) {
// We only need the viewBox on the symbol definition, we can drop it everywhere else
symbol = unescapeHTML(`<symbol${spreadAttributes({ viewBox, id })}>${children}</symbol>`);
rendered.add(result.response);
return render`<svg${spreadAttributes(normalizedProps)}>${title}${symbol}<use xlink:href="#${id}" /></svg>`;
}

return render`<svg${spreadAttributes(normalizedProps)}>${title}${symbol}<use xlink:href="#${id}" /></svg>`;
// Default to inline mode
return render`<svg${spreadAttributes({viewBox, ...normalizedProps})}>${title}${unescapeHTML(children)}</svg>`
});

makeNonEnumerable(Component);
Expand Down
8 changes: 5 additions & 3 deletions packages/astro/src/assets/utils/svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { SvgComponentProps } from '../runtime.js';
type SvgAttributes = Record<string, any>;

/**
* Some attributes required for `image/svg+xml` are irrelevant when inlined ina `text/html` document. We can save a few bytes by dropping them.
* Some attributes required for `image/svg+xml` are irrelevant when inlined in a `text/html` document. We can save a few bytes by dropping them.
*/
const ATTRS_TO_DROP = ['xmlns', 'xmlns:xlink', 'version'];
const DEFAULT_ATTRS: SvgAttributes = { role: 'img' };
Expand Down Expand Up @@ -40,12 +40,14 @@ function parseSvg(contents: string) {
return { attributes, body };
}

export function makeSvgComponent(meta: ImageMetadata, contents: Buffer | string) {
export type SvgRenderMode = 'inline' | 'sprite';

export function makeSvgComponent(meta: ImageMetadata, contents: Buffer | string, options?: { mode?: SvgRenderMode }) {
const file = typeof contents === 'string' ? contents : contents.toString('utf-8');
const { attributes, body: children } = parseSvg(file);
const props: SvgComponentProps = {
meta,
attributes: dropAttributes(attributes),
attributes: dropAttributes({ mode: options?.mode, ...attributes }),
children,
};

Expand Down
5 changes: 3 additions & 2 deletions packages/astro/src/assets/vite-plugin-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,10 @@ export default function assets({
});
}

if (/\.svg$/.test(id)) {
if (settings.config.experimental.svg && /\.svg$/.test(id)) {
const { contents, ...metadata } = imageMetadata;
return makeSvgComponent(metadata, contents!);
// We know that the contents are present, as we only emit this property for SVG files
return makeSvgComponent(metadata, contents!, { mode: settings.config.experimental.svg.mode });
}

// We can only reliably determine if an image is used on the server, as we need to track its usage throughout the entire build.
Expand Down
11 changes: 11 additions & 0 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ export const ASTRO_CONFIG_DEFAULTS = {
validateSecrets: false,
},
contentLayer: false,
svg: {
mode: 'inline',
},
},
} satisfies AstroUserConfig & { server: { open: boolean } };

Expand Down Expand Up @@ -544,6 +547,14 @@ export const AstroConfigSchema = z.object({
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.contentIntellisense),
contentLayer: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.contentLayer),
svg: z
.object({
mode: z
.union([z.literal('inline'), z.literal('sprite')])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.svg.mode),
})
.optional(),
})
.strict(
`Invalid or outdated experimental feature.\nCheck for incorrect spelling or outdated Astro version.\nSee https://docs.astro.build/en/reference/configuration-reference/#experimental-flags for a list of all current experiments.`,
Expand Down

0 comments on commit ad55d4e

Please sign in to comment.