@astrojs/starlight 0.39.3 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/components/AnchorHeading.astro +2 -1
- package/index.ts +28 -21
- package/integrations/anchor-icon.ts +5 -0
- package/integrations/aside-icons.ts +39 -0
- package/integrations/markdown-plugins.ts +71 -0
- package/integrations/markdown-processor.ts +64 -0
- package/integrations/{code-rtl-support.ts → rehype-code-rtl-support.ts} +2 -2
- package/integrations/{heading-links.ts → rehype-heading-links.ts} +4 -6
- package/integrations/remark-asides.ts +229 -0
- package/integrations/remark-rehype.ts +25 -55
- package/integrations/satteri.ts +288 -0
- package/integrations/vite-lazy-barrel-optimization.ts +40 -0
- package/integrations/{virtual-user-config.ts → vite-virtual-modules.ts} +1 -1
- package/package.json +14 -6
- package/user-components/Badge.astro +1 -6
- package/user-components/Icon.astro +9 -16
- package/integrations/asides.ts +0 -283
|
@@ -1,25 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import type { AstroConfig } from 'astro';
|
|
4
|
-
import { rehypeHeadingIds } from '@astrojs/markdown-remark';
|
|
1
|
+
import { isUnifiedProcessor, rehypeHeadingIds } from '@astrojs/markdown-remark';
|
|
5
2
|
import type { Root as RehypeRoot } from 'hast';
|
|
6
3
|
import type { Root as RemarkRoot } from 'mdast';
|
|
7
4
|
import remarkDirective from 'remark-directive';
|
|
8
5
|
import type { Plugin } from 'unified';
|
|
9
6
|
import type { VFile } from 'vfile';
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
|
|
7
|
+
import { remarkAsides, remarkDirectivesRestoration } from './remark-asides';
|
|
8
|
+
import { rehypeRtlCodeSupport } from './rehype-code-rtl-support';
|
|
9
|
+
import rehypeAutolinkHeadings from './rehype-heading-links';
|
|
10
|
+
import {
|
|
11
|
+
getMarkdownProcessorPaths,
|
|
12
|
+
shouldTransformPath,
|
|
13
|
+
type MarkdownProcessorPluginOptions,
|
|
14
|
+
} from './markdown-processor';
|
|
15
|
+
|
|
16
|
+
// Re-exported so callers can narrow `markdown.processor` to the Unified processor and use the
|
|
17
|
+
// remark directive-restoration plugin through the same lazy import that loads the optional
|
|
18
|
+
// `@astrojs/markdown-remark` peer dependency.
|
|
19
|
+
export { isUnifiedProcessor, remarkDirectivesRestoration };
|
|
15
20
|
|
|
16
21
|
/** List of remark plugins to apply. */
|
|
17
|
-
export function starlightRemarkPlugins(options:
|
|
22
|
+
export function starlightRemarkPlugins(options: MarkdownProcessorPluginOptions): RemarkPlugin[] {
|
|
18
23
|
return [remarkDirective, remarkPlugins(options)];
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
/** List of rehype plugins to apply. */
|
|
22
|
-
export function starlightRehypePlugins(options:
|
|
27
|
+
export function starlightRehypePlugins(options: MarkdownProcessorPluginOptions): RehypePlugin[] {
|
|
23
28
|
return [
|
|
24
29
|
...(options.starlightConfig.markdown.headingLinks ? [[rehypeHeadingIds]] : []),
|
|
25
30
|
rehypePlugins(options),
|
|
@@ -27,8 +32,8 @@ export function starlightRehypePlugins(options: RemarkRehypePluginOptions): Rehy
|
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
/** Remark plugin applying other Starlight remark plugins if necessary. */
|
|
30
|
-
function remarkPlugins(options:
|
|
31
|
-
const
|
|
35
|
+
function remarkPlugins(options: MarkdownProcessorPluginOptions): RemarkPlugin {
|
|
36
|
+
const allowedPaths = getMarkdownProcessorPaths(options);
|
|
32
37
|
|
|
33
38
|
return function attacher(this) {
|
|
34
39
|
const remarkAsidesTransformer = remarkAsides(options).call(this)!;
|
|
@@ -36,7 +41,7 @@ function remarkPlugins(options: RemarkRehypePluginOptions): RemarkPlugin {
|
|
|
36
41
|
return async function transformer(...args) {
|
|
37
42
|
const [, file] = args;
|
|
38
43
|
|
|
39
|
-
if (!shouldTransformFile(file,
|
|
44
|
+
if (!shouldTransformFile(file, allowedPaths)) return;
|
|
40
45
|
|
|
41
46
|
await remarkAsidesTransformer(...args);
|
|
42
47
|
};
|
|
@@ -44,8 +49,8 @@ function remarkPlugins(options: RemarkRehypePluginOptions): RemarkPlugin {
|
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
/** Rehype plugin applying other Starlight rehype plugins if necessary. */
|
|
47
|
-
function rehypePlugins(options:
|
|
48
|
-
const
|
|
52
|
+
function rehypePlugins(options: MarkdownProcessorPluginOptions): RehypePlugin {
|
|
53
|
+
const allowedPaths = getMarkdownProcessorPaths(options);
|
|
49
54
|
|
|
50
55
|
return function attacher(this) {
|
|
51
56
|
const rehypeRtlCodeSupportTransformer = rehypeRtlCodeSupport(options).call(this);
|
|
@@ -54,7 +59,7 @@ function rehypePlugins(options: RemarkRehypePluginOptions): RehypePlugin {
|
|
|
54
59
|
return async function transformer(...args) {
|
|
55
60
|
const [, file] = args;
|
|
56
61
|
|
|
57
|
-
if (!shouldTransformFile(file,
|
|
62
|
+
if (!shouldTransformFile(file, allowedPaths)) return;
|
|
58
63
|
|
|
59
64
|
await rehypeRtlCodeSupportTransformer(...args);
|
|
60
65
|
|
|
@@ -65,52 +70,17 @@ function rehypePlugins(options: RemarkRehypePluginOptions): RehypePlugin {
|
|
|
65
70
|
};
|
|
66
71
|
}
|
|
67
72
|
|
|
68
|
-
/**
|
|
69
|
-
* Returns the paths to the Starlight docs collection and any additional paths defined in the
|
|
70
|
-
* `starlightConfig.markdown.processedDirs` option that can be used with the
|
|
71
|
-
* `shouldTransformFile()` utility to determine if a file should be transformed by a plugin or not.
|
|
72
|
-
*/
|
|
73
|
-
function getRemarkRehypePaths(options: RemarkRehypePluginOptions): string[] {
|
|
74
|
-
const paths = [normalizePath(resolveCollectionPath('docs', options.astroConfig.srcDir))];
|
|
75
|
-
|
|
76
|
-
for (const processedDir of options.starlightConfig.markdown.processedDirs) {
|
|
77
|
-
paths.push(normalizePath(resolve(fileURLToPath(options.astroConfig.root), processedDir)));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return paths;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
73
|
/**
|
|
84
74
|
* Determines if a file should be transformed by a remark/rehype plugin, e.g. files without a known
|
|
85
|
-
* path or files that are not part of the allowed
|
|
75
|
+
* path or files that are not part of the allowed paths are skipped.
|
|
86
76
|
*/
|
|
87
|
-
function shouldTransformFile(file: VFile,
|
|
77
|
+
function shouldTransformFile(file: VFile, allowedPaths: string[]) {
|
|
88
78
|
// If the content is rendered using the content loader `renderMarkdown()` API, a file path
|
|
89
79
|
// is not provided.
|
|
90
80
|
// In that case, we skip the file.
|
|
91
81
|
if (!file?.path) return false;
|
|
92
82
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// If the document is not part of the allowed remark/rehype paths, skip it.
|
|
96
|
-
return remarkRehypePaths.some((path) => normalizedPath.startsWith(path));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* File path separators seems to be inconsistent on Windows between remark/rehype plugins used on
|
|
101
|
-
* Markdown vs MDX files.
|
|
102
|
-
* For the time being, we normalize all paths to unix style paths.
|
|
103
|
-
*/
|
|
104
|
-
const backSlashRegex = /\\/g;
|
|
105
|
-
function normalizePath(path: string) {
|
|
106
|
-
return path.replace(backSlashRegex, '/');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export interface RemarkRehypePluginOptions {
|
|
110
|
-
starlightConfig: Pick<StarlightConfig, 'defaultLocale' | 'locales' | 'markdown'>;
|
|
111
|
-
astroConfig: Pick<AstroConfig, 'root' | 'srcDir'>;
|
|
112
|
-
useTranslations: HookParameters<'config:setup'>['useTranslations'];
|
|
113
|
-
absolutePathToLang: HookParameters<'config:setup'>['absolutePathToLang'];
|
|
83
|
+
return shouldTransformPath(file.path, allowedPaths);
|
|
114
84
|
}
|
|
115
85
|
|
|
116
86
|
type RemarkPlugin = Plugin<[], RemarkRoot>;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { satteriHeadingIdsPlugin } from '@astrojs/markdown-satteri';
|
|
3
|
+
import type { Element, Properties } from 'hast';
|
|
4
|
+
import type { Paragraph } from 'mdast';
|
|
5
|
+
import { directiveToMarkdown } from 'mdast-util-directive';
|
|
6
|
+
import { toMarkdown } from 'mdast-util-to-markdown';
|
|
7
|
+
import type {
|
|
8
|
+
HastPluginDefinition,
|
|
9
|
+
HastPluginInput,
|
|
10
|
+
MdastPluginInput,
|
|
11
|
+
MdastPluginDefinition,
|
|
12
|
+
} from 'satteri';
|
|
13
|
+
import { anchorLinkIconPath } from './anchor-icon';
|
|
14
|
+
import { asideIconPathAttrs, isAsideVariant } from './aside-icons';
|
|
15
|
+
import {
|
|
16
|
+
getMarkdownProcessorPaths,
|
|
17
|
+
shouldTransformPath,
|
|
18
|
+
type MarkdownProcessorPluginOptions,
|
|
19
|
+
} from './markdown-processor';
|
|
20
|
+
import { Icons } from '../components-internals/Icons';
|
|
21
|
+
import { throwInvalidAsideIconError } from './asides-error';
|
|
22
|
+
import type { StarlightIcon } from '../types';
|
|
23
|
+
|
|
24
|
+
export function starlightSatteriPlugins(options: MarkdownProcessorPluginOptions): {
|
|
25
|
+
mdastPlugins: MdastPluginInput[];
|
|
26
|
+
hastPlugins: HastPluginInput[];
|
|
27
|
+
} {
|
|
28
|
+
const allowedPaths = getMarkdownProcessorPaths(options);
|
|
29
|
+
return {
|
|
30
|
+
mdastPlugins: [satteriAsidesPlugin(options, allowedPaths)],
|
|
31
|
+
hastPlugins: [
|
|
32
|
+
satteriRtlCodeSupportPlugin(allowedPaths),
|
|
33
|
+
...(options.starlightConfig.markdown.headingLinks
|
|
34
|
+
? [() => satteriHeadingIdsPlugin(), satteriAutolinkHeadingsPlugin(options, allowedPaths)]
|
|
35
|
+
: []),
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Recover directives Starlight didn't claim so user content isn't dropped
|
|
42
|
+
*/
|
|
43
|
+
export function satteriDirectivesRestoration(): MdastPluginDefinition {
|
|
44
|
+
return {
|
|
45
|
+
name: 'starlight-directives-restoration',
|
|
46
|
+
textDirective(node) {
|
|
47
|
+
// Leave directives another plugin already handled (i.e. set `data` on) untouched.
|
|
48
|
+
if (node.data !== undefined) return;
|
|
49
|
+
return { type: 'text', value: serializeDirective(node) };
|
|
50
|
+
},
|
|
51
|
+
leafDirective(node) {
|
|
52
|
+
if (node.data !== undefined) return;
|
|
53
|
+
return {
|
|
54
|
+
type: 'paragraph',
|
|
55
|
+
children: [{ type: 'text', value: serializeDirective(node) }],
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
containerDirective(node) {
|
|
59
|
+
if (node.data !== undefined) return;
|
|
60
|
+
return paragraphElement('div', {}, [...node.children]);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function paragraphElement(
|
|
66
|
+
tagName: string,
|
|
67
|
+
properties: Properties,
|
|
68
|
+
children: unknown[] = []
|
|
69
|
+
): Paragraph {
|
|
70
|
+
return {
|
|
71
|
+
type: 'paragraph',
|
|
72
|
+
data: { hName: tagName, hProperties: properties },
|
|
73
|
+
children: children as Paragraph['children'],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Convert `:::variant` directive blocks into styled asides. */
|
|
78
|
+
function satteriAsidesPlugin(
|
|
79
|
+
options: MarkdownProcessorPluginOptions,
|
|
80
|
+
allowedPaths: string[]
|
|
81
|
+
): MdastPluginDefinition {
|
|
82
|
+
return {
|
|
83
|
+
name: 'starlight-asides',
|
|
84
|
+
containerDirective(node, ctx) {
|
|
85
|
+
if (!shouldTransformPath(ctx.fileURL, allowedPaths)) return;
|
|
86
|
+
if (!isAsideVariant(node.name)) return;
|
|
87
|
+
|
|
88
|
+
const variant = node.name;
|
|
89
|
+
// `shouldTransformPath` above already returned for a missing `fileURL`.
|
|
90
|
+
const filename = fileURLToPath(ctx.fileURL!);
|
|
91
|
+
const t = options.useTranslations(options.absolutePathToLang(filename));
|
|
92
|
+
|
|
93
|
+
let title = t(`aside.${variant}`);
|
|
94
|
+
let titleNode: unknown[] = [{ type: 'text', value: title }];
|
|
95
|
+
const children = [...node.children];
|
|
96
|
+
const firstChild = children[0];
|
|
97
|
+
if (
|
|
98
|
+
firstChild?.type === 'paragraph' &&
|
|
99
|
+
firstChild.data?.directiveLabel &&
|
|
100
|
+
firstChild.children.length > 0
|
|
101
|
+
) {
|
|
102
|
+
titleNode = firstChild.children;
|
|
103
|
+
title = ctx.textContent(firstChild);
|
|
104
|
+
children.shift();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const customIconName = node.attributes?.['icon'];
|
|
108
|
+
let innerSvgHtml: string;
|
|
109
|
+
if (customIconName) {
|
|
110
|
+
const icon = Icons[customIconName as StarlightIcon];
|
|
111
|
+
if (!icon) throwInvalidAsideIconError(customIconName);
|
|
112
|
+
innerSvgHtml = icon;
|
|
113
|
+
} else {
|
|
114
|
+
innerSvgHtml = asideIconPathAttrs[variant]
|
|
115
|
+
.map((attrs) => `<path${attrsToHtml(attrs)}/>`)
|
|
116
|
+
.join('');
|
|
117
|
+
}
|
|
118
|
+
const iconSvg = `<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" class="starlight-aside__icon">${innerSvgHtml}</svg>`;
|
|
119
|
+
|
|
120
|
+
return paragraphElement(
|
|
121
|
+
'aside',
|
|
122
|
+
{
|
|
123
|
+
'aria-label': title,
|
|
124
|
+
class: `starlight-aside starlight-aside--${variant}`,
|
|
125
|
+
},
|
|
126
|
+
[
|
|
127
|
+
paragraphElement('p', { class: 'starlight-aside__title', 'aria-hidden': 'true' }, [
|
|
128
|
+
{ type: 'html', value: iconSvg },
|
|
129
|
+
...titleNode,
|
|
130
|
+
]),
|
|
131
|
+
paragraphElement('div', { class: 'starlight-aside__content' }, children),
|
|
132
|
+
]
|
|
133
|
+
);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function attrsToHtml(attrs: Record<string, string>): string {
|
|
139
|
+
let out = '';
|
|
140
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
141
|
+
out += ` ${key}="${value.replace(/&/g, '&').replace(/"/g, '"')}"`;
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function serializeDirective(node: Parameters<typeof toMarkdown>[0]): string {
|
|
147
|
+
const md = toMarkdown(node, { extensions: [directiveToMarkdown()] });
|
|
148
|
+
return md.at(-1) === '\n' ? md.slice(0, -1) : md;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function satteriRtlCodeSupportPlugin(allowedPaths: string[]): () => HastPluginDefinition {
|
|
152
|
+
return () => {
|
|
153
|
+
// HACK: Sätteri currently does not expose a way to either know the parent of a node, or
|
|
154
|
+
// skipping a subtree visit. To work around this, we manually track the source spans of `<pre>`
|
|
155
|
+
// elements and skip applying `dir="auto"` to `<code>` elements inside those spans. This is:
|
|
156
|
+
// bad, because it means that it won't work for nodes without positions (e.g. generated nodes),
|
|
157
|
+
// but it's as good as it gets right now.
|
|
158
|
+
const preSpans: Array<[number, number]> = [];
|
|
159
|
+
return {
|
|
160
|
+
name: 'starlight-rtl-code-support',
|
|
161
|
+
element: [
|
|
162
|
+
{
|
|
163
|
+
filter: ['pre'],
|
|
164
|
+
visit(node, ctx) {
|
|
165
|
+
if (!shouldTransformPath(ctx.fileURL, allowedPaths)) return;
|
|
166
|
+
const span = nodeSpan(node);
|
|
167
|
+
if (span) preSpans.push(span);
|
|
168
|
+
if (node.properties && 'dir' in node.properties) return;
|
|
169
|
+
ctx.setProperty(node, 'dir', 'ltr');
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
filter: ['code'],
|
|
174
|
+
visit(node, ctx) {
|
|
175
|
+
if (!shouldTransformPath(ctx.fileURL, allowedPaths)) return;
|
|
176
|
+
if (isInsideSpan(nodeSpan(node), preSpans)) return;
|
|
177
|
+
if (node.properties && 'dir' in node.properties) return;
|
|
178
|
+
ctx.setProperty(node, 'dir', 'auto');
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
// Shiki runs ahead of us and replaces the highlighted `<pre>` element with a raw HTML
|
|
183
|
+
// node, so the `pre` element visitor above never sees it. Patch the raw markup instead.
|
|
184
|
+
raw(node, ctx) {
|
|
185
|
+
if (!shouldTransformPath(ctx.fileURL, allowedPaths)) return undefined;
|
|
186
|
+
const value = ltrRawPre(node.value);
|
|
187
|
+
if (value === null) return undefined;
|
|
188
|
+
return { type: 'raw', value };
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** The source byte span of a node, or `null` when it carries no position (e.g. a generated node). */
|
|
195
|
+
function nodeSpan(node: { position?: Element['position'] }): [number, number] | null {
|
|
196
|
+
const start = node.position?.start.offset;
|
|
197
|
+
const end = node.position?.end.offset;
|
|
198
|
+
return typeof start === 'number' && typeof end === 'number' ? [start, end] : null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function isInsideSpan(span: [number, number] | null, spans: Array<[number, number]>): boolean {
|
|
202
|
+
if (!span) return false;
|
|
203
|
+
return spans.some(([start, end]) => span[0] >= start && span[1] <= end);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const rawPreOpenTag = /<pre(?=[\s>])[^>]*>/;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Add `dir="ltr"` to the opening tag of a raw `<pre>` HTML string, unless it already declares a
|
|
210
|
+
* `dir`. Returns `null` when the value isn’t a `<pre>`, leaving unrelated raw HTML untouched.
|
|
211
|
+
*/
|
|
212
|
+
function ltrRawPre(value: string): string | null {
|
|
213
|
+
const openTag = value.match(rawPreOpenTag)?.[0];
|
|
214
|
+
if (!openTag || /\sdir\s*=/.test(openTag)) return null;
|
|
215
|
+
return value.replace(openTag, () => `<pre dir="ltr"${openTag.slice(4)}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function satteriAutolinkHeadingsPlugin(
|
|
219
|
+
options: MarkdownProcessorPluginOptions,
|
|
220
|
+
allowedPaths: string[]
|
|
221
|
+
): HastPluginDefinition {
|
|
222
|
+
return {
|
|
223
|
+
name: 'starlight-autolink-headings',
|
|
224
|
+
element: {
|
|
225
|
+
filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
|
|
226
|
+
visit(node, ctx) {
|
|
227
|
+
if (!shouldTransformPath(ctx.fileURL, allowedPaths)) return;
|
|
228
|
+
|
|
229
|
+
const id = node.properties?.['id'];
|
|
230
|
+
if (typeof id !== 'string' || !id) return;
|
|
231
|
+
|
|
232
|
+
const title = ctx.textContent(node);
|
|
233
|
+
// `shouldTransformPath` above already returned for a missing `fileURL`.
|
|
234
|
+
const filename = fileURLToPath(ctx.fileURL!);
|
|
235
|
+
const t = options.useTranslations(options.absolutePathToLang(filename));
|
|
236
|
+
const accessibleLabel = t('heading.anchorLabel', {
|
|
237
|
+
title,
|
|
238
|
+
interpolation: { escapeValue: false },
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
type: 'element',
|
|
243
|
+
tagName: 'div',
|
|
244
|
+
properties: { class: `sl-heading-wrapper level-${node.tagName}` },
|
|
245
|
+
children: [
|
|
246
|
+
node,
|
|
247
|
+
{
|
|
248
|
+
type: 'element',
|
|
249
|
+
tagName: 'a',
|
|
250
|
+
properties: { class: 'sl-anchor-link', href: '#' + id },
|
|
251
|
+
children: [
|
|
252
|
+
{
|
|
253
|
+
type: 'element',
|
|
254
|
+
tagName: 'span',
|
|
255
|
+
properties: { 'aria-hidden': 'true', class: 'sl-anchor-icon' },
|
|
256
|
+
children: [
|
|
257
|
+
{
|
|
258
|
+
type: 'element',
|
|
259
|
+
tagName: 'svg',
|
|
260
|
+
properties: { width: '16', height: '16', viewBox: '0 0 24 24' },
|
|
261
|
+
children: [
|
|
262
|
+
{
|
|
263
|
+
type: 'element',
|
|
264
|
+
tagName: 'path',
|
|
265
|
+
properties: {
|
|
266
|
+
fill: 'currentcolor',
|
|
267
|
+
d: anchorLinkIconPath,
|
|
268
|
+
},
|
|
269
|
+
children: [],
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
type: 'element',
|
|
277
|
+
tagName: 'span',
|
|
278
|
+
properties: { class: 'sr-only', 'data-pagefind-ignore': true },
|
|
279
|
+
children: [{ type: 'text', value: accessibleLabel }],
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
};
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import type { ViteUserConfig } from 'astro';
|
|
3
|
+
|
|
4
|
+
// https://vite.dev/guide/api-plugin#hook-filters
|
|
5
|
+
const componentsBarrelIdFilter = /[\\/]components\.ts(?:\?.*)?$/;
|
|
6
|
+
|
|
7
|
+
const backSlashRegex = /\\/g;
|
|
8
|
+
const queryStringRegex = /\?.*$/;
|
|
9
|
+
|
|
10
|
+
const starlightComponentsBarrelId = normalizeId(
|
|
11
|
+
fileURLToPath(new URL('../components.ts', import.meta.url))
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Vite plugin that marks the Starlight components barrel file as having no side effects so that
|
|
16
|
+
* lazy barrel optimization can be applied to it.
|
|
17
|
+
*
|
|
18
|
+
* @see https://rolldown.rs/in-depth/lazy-barrel-optimization
|
|
19
|
+
*/
|
|
20
|
+
export function vitePluginStarlightLazyBarrelOptimization(): VitePlugin {
|
|
21
|
+
return {
|
|
22
|
+
name: 'vite-plugin-starlight-lazy-barrel-optimization',
|
|
23
|
+
enforce: 'pre',
|
|
24
|
+
transform: {
|
|
25
|
+
filter: {
|
|
26
|
+
id: componentsBarrelIdFilter,
|
|
27
|
+
},
|
|
28
|
+
handler(code, id) {
|
|
29
|
+
if (normalizeId(id) !== starlightComponentsBarrelId) return;
|
|
30
|
+
return { code, moduleSideEffects: false };
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeId(id: string) {
|
|
37
|
+
return id.replace(backSlashRegex, '/').replace(queryStringRegex, '');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type VitePlugin = NonNullable<ViteUserConfig['plugins']>[number];
|
|
@@ -17,7 +17,7 @@ function resolveVirtualModuleId<T extends string>(id: T): `\0${T}` {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/** Vite plugin that exposes Starlight user config and project context via virtual modules. */
|
|
20
|
-
export function
|
|
20
|
+
export function vitePluginStarlightVirtualModules(
|
|
21
21
|
{
|
|
22
22
|
command,
|
|
23
23
|
isNodeCompatibleEnv,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrojs/starlight",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"description": "Build beautiful, high-performance documentation websites with Astro",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -42,25 +42,32 @@
|
|
|
42
42
|
"./style/markdown.css": "./style/markdown.css"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"
|
|
45
|
+
"@astrojs/markdown-remark": "^7.2.0",
|
|
46
|
+
"astro": "^7.0.2"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"@astrojs/markdown-remark": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
46
52
|
},
|
|
47
53
|
"devDependencies": {
|
|
54
|
+
"@astrojs/markdown-remark": "^7.2.0",
|
|
48
55
|
"@playwright/test": "^1.59.1",
|
|
49
56
|
"@types/node": "^22.19.17",
|
|
50
57
|
"@vitest/coverage-v8": "^4.1.5",
|
|
51
|
-
"astro": "^
|
|
58
|
+
"astro": "^7.0.2",
|
|
52
59
|
"linkedom": "^0.18.12",
|
|
53
60
|
"vitest": "^4.1.5"
|
|
54
61
|
},
|
|
55
62
|
"dependencies": {
|
|
56
|
-
"@astrojs/markdown-
|
|
57
|
-
"@astrojs/mdx": "^
|
|
63
|
+
"@astrojs/markdown-satteri": "^0.3.2",
|
|
64
|
+
"@astrojs/mdx": "^7.0.0",
|
|
58
65
|
"@astrojs/sitemap": "^3.7.2",
|
|
59
66
|
"@pagefind/default-ui": "^1.3.0",
|
|
60
67
|
"@types/hast": "^3.0.4",
|
|
61
68
|
"@types/js-yaml": "^4.0.9",
|
|
62
69
|
"@types/mdast": "^4.0.4",
|
|
63
|
-
"astro-expressive-code": "^0.
|
|
70
|
+
"astro-expressive-code": "^0.43.1",
|
|
64
71
|
"bcp-47": "^2.1.0",
|
|
65
72
|
"hast-util-from-html": "^2.0.3",
|
|
66
73
|
"hast-util-select": "^6.0.4",
|
|
@@ -77,6 +84,7 @@
|
|
|
77
84
|
"rehype": "^13.0.2",
|
|
78
85
|
"rehype-format": "^5.0.1",
|
|
79
86
|
"remark-directive": "^4.0.0",
|
|
87
|
+
"satteri": "^0.9.1",
|
|
80
88
|
"ultrahtml": "^1.6.0",
|
|
81
89
|
"unified": "^11.0.5",
|
|
82
90
|
"unist-util-visit": "^5.1.0",
|
|
@@ -16,14 +16,9 @@ const {
|
|
|
16
16
|
Astro.props,
|
|
17
17
|
'Invalid prop passed to the `<Badge/>` component.'
|
|
18
18
|
);
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* The fragment around the element is used as a workaround to avoid a trailing whitespace in the output.
|
|
22
|
-
* @see https://github.com/withastro/compiler/issues/1003
|
|
23
|
-
*/
|
|
24
19
|
---
|
|
25
20
|
|
|
26
|
-
|
|
21
|
+
<span class:list={['sl-badge', variant, size, customClass]} {...attrs}>{text}</span>
|
|
27
22
|
|
|
28
23
|
<style>
|
|
29
24
|
@layer starlight.components {
|
|
@@ -11,24 +11,17 @@ interface Props {
|
|
|
11
11
|
|
|
12
12
|
const { name, label, size = '1em', color } = Astro.props;
|
|
13
13
|
const a11yAttrs = label ? ({ 'aria-label': label } as const) : ({ 'aria-hidden': 'true' } as const);
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* The fragment around the element is used as a workaround to avoid a trailing whitespace in the output.
|
|
17
|
-
* @see https://github.com/withastro/compiler/issues/1003
|
|
18
|
-
*/
|
|
19
14
|
---
|
|
20
15
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/>
|
|
31
|
-
</>
|
|
16
|
+
<svg
|
|
17
|
+
{...a11yAttrs}
|
|
18
|
+
class={Astro.props.class}
|
|
19
|
+
width="16"
|
|
20
|
+
height="16"
|
|
21
|
+
viewBox="0 0 24 24"
|
|
22
|
+
fill="currentColor"
|
|
23
|
+
set:html={Icons[name]}
|
|
24
|
+
/>
|
|
32
25
|
|
|
33
26
|
<style define:vars={{ 'sl-icon-color': color, 'sl-icon-size': size }}>
|
|
34
27
|
@layer starlight.components {
|