Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .agents/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ The choices that the code does not explain, with the options that were rejected.
- **All options go to `codeblocks()`** on Starlight sites, also when the preset is in `ec.config.mjs`; the preset reads them from the registry.
- **`codeblocks()` sets `tabWidth: 0`** unless the Starlight config or `ec.config.mjs` sets it, including when `ec.config.mjs` has plugins. Otherwise real tabs never reach visible whitespace or the copied text, with no warning.
- **Collapsible sections must come before `pluginCodeblocks()`.** The notation plugin throws otherwise, because our decorations shift the children that the other plugin picks by position. *Rejected:* moving every decoration to `postprocessRenderedBlockGroup` (touches most features for one optional plugin).
- **Astro sites without Starlight use `codeblocks()` from `starlight-codeblocks/astro`**, which returns `[setup, astroExpressiveCode()]`. Astro flattens the array, so Expressive Code stays where the author lists `codeblocks()`, before `mdx()`. It reuses the Starlight path: registry, ec-config override, Markdown plugin, runtimes; page CSS goes through `injectScript('page-ssr')` in place of `customCss`. It fails when the site also lists `expressiveCode()`. Code tabs and inline code highlighting are off by default there, because they change every Markdown page (directive syntax, a page stylesheet), and the API card page styles and script are never added; Starlight sites keep all three, since Starlight already turns on directives. `tabWidth: 0` stays, as on Starlight: it changes only code blocks. `astro-expressive-code` and `@astrojs/starlight` are optional peer dependencies, so each site installs only the one it uses. *Rejected:* adding Expressive Code through `updateConfig` (Astro appends it after `mdx()`, which fails EC's order check); asking the author to add the preset to their own `expressiveCode()` (no registry, so no code tabs, inline code or built-in runtimes).
- **Without Starlight, the integration turns on S盲tteri directives for code tabs and gives each unclaimed directive back as its source text**, as Starlight does, in `astro:config:done` so that other integrations claim theirs first. Without it, `16:9` renders as `16`. It slices the source by position. *Rejected:* `mdast-util-to-markdown` as Starlight uses (two dependencies, and the output can differ from the source).
- **The preset takes `{ base }`** for sites without Starlight, so site-relative `[!link]` URLs and adapter hrefs get the base.
- **The registry is a public contract** for other plugins (starlight-pydocs): it exists after `codeblocks()` runs `config:setup`, and `options.inlineHighlighting` and `options.apiLinks` are `false` or a truthy object. Nothing else is public; `test/starlight.test.ts` guards it.
- **Under `unified()`, one remark adapter runs the S盲tteri plugin definition**, so each Markdown feature has one implementation. *Rejected:* a remark-native copy of each feature; a warning that the features are off.
Expand All @@ -29,7 +31,7 @@ The choices that the code does not explain, with the options that were rejected.
## Client code

- **One loader in `jsModules`, feature modules emitted as hashed assets next to `ec.<hash>.js`.** `jsModules` load only on pages with code blocks. *Rejected:* `injectScript('page')` (loads on every page); all feature code inline (every page pays for every feature).
- **Sites without `codeblocks()` get a loader that carries every module's source** and imports from `blob:` URLs. It works with any EC integration, at the cost of a larger `ec.<hash>.js` there. *Rejected:* an extra Astro integration to install; `data:` URLs (a third larger).
- **Sites without `codeblocks()` get a loader that carries every module's source** and imports from `blob:` URLs. It works with any EC integration, at the cost of a larger `ec.<hash>.js` there. Sites without Starlight get the full set-up from `starlight-codeblocks/astro`. *Rejected:* making that integration a requirement; `data:` URLs (a third larger).
- **Each client module builds on its own, with no shared chunks**, so a file works as a `blob:` module and the 3 kB budget is what a page downloads. The code walkthrough and runtimes are exempt.
- **Runtime modules are emitted at a fixed path** (`scb-runtime-<language>.js`, no hash), because code blocks render before the client build, so the URL must be known in advance.
- **Anchor positioning with a small script fallback.** *Rejected:* Floating UI (a dependency, over budget).
Expand Down
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Agent instructions

`starlight-codeblocks` is a Starlight plugin that adds 26 code block features on top of Expressive Code (EC), which renders every code block in Starlight. `packages/starlight-codeblocks/` is the plugin, `docs/` its Starlight docs site (it uses the plugin through `workspace:*`, and defines each feature's syntax and behaviour), and `skills/starlight-codeblocks/` an agent skill for people who use the plugin.
`starlight-codeblocks` is a Starlight plugin that adds 26 code block features on top of Expressive Code (EC), which renders every code block in Starlight. `packages/starlight-codeblocks/` is the plugin, `docs/` its Starlight docs site (it uses the plugin through `workspace:*`, and defines each feature's syntax and behaviour), `examples/astro/` a minimal Astro site without Starlight, and `skills/starlight-codeblocks/` an agent skill for people who use the plugin.

- `.agents/DECISIONS.md`: design decisions and the rejected options. Search it before you change existing behaviour.
- `.agents/WRITING-STYLE.md`: how the docs read, the feature page template and the glossary.
Expand All @@ -16,10 +16,10 @@
```
pnpm install
pnpm build Build the package (tsdown) into packages/starlight-codeblocks/dist
pnpm test Build, then Vitest unit tests and the node:test checks in scripts/
pnpm test Build, then Vitest unit tests, a build of examples/astro and the node:test checks in scripts/
pnpm lint Build, then Biome and type checks (tsc, astro check)
pnpm lint:docs Writing-style lint for docs/, the READMEs and skills/
pnpm docs:build Build the package and the docs site into docs/dist, then render the share cards
pnpm docs:build Build the package, the docs site into docs/dist and examples/astro into docs/dist/examples/astro, then render the share cards
pnpm test:e2e docs:build, then Playwright against astro preview of docs/dist
pnpm readme:media [slug...] docs:build, then regenerate the README images in .github/assets/readme/
```
Expand All @@ -34,6 +34,7 @@ pnpm readme:media [slug...] docs:build, then regenerate the README images in .
Paths are under `packages/starlight-codeblocks/src/` unless they start with `docs/`.

- `index.ts`: `codeblocks()`, the Starlight plugin. It validates options (`options.ts`), fills the `globalThis` registry (`registry.ts`) and adds the integration (`integration.ts`).
- `astro.ts`: `codeblocks()` for Astro without Starlight (`starlight-codeblocks/astro`). It does the same and adds `astroExpressiveCode()` itself. `examples/astro/` uses it: `pnpm build && pnpm --filter example-astro build`.
- `expressive-code/index.ts`: `createPlugins()`, the preset in order. Core first, notation second; the comments there give the other order constraints. One file per feature, `expressive-code/<name>.ts`. Shared helpers are in `core.ts` and `styles.ts`.
- `satteri/`: one mdast plugin for code tabs directive, `{:lang}` inline code, mention link checks and duplicate `id` checks. `remark.ts` runs it on `unified()`; add any context it lacks there, with a case in `test/remark.test.ts`.
- `client/<name>.ts`: browser modules, built to `dist/client/scb-<name>.<hash>.js` and loaded by one loader in `jsModules`. `api-card-page.ts` is the exception: a page script and CSS for API cards on links outside code blocks.
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img src="https://raw.githubusercontent.com/ewels/starlight-codeblocks/main/.github/assets/logotype.svg" width="800" alt="starlight-codeblocks">
</h1>

A Starlight plugin that adds 26 features to code blocks, such as focus, line states, annotations, links to API docs and runnable examples. It builds on Expressive Code, so the code blocks you already have keep working.
A Starlight and Astro plugin that adds 26 features to code blocks, such as focus, line states, annotations, links to API docs and runnable examples. It builds on Expressive Code, so the code blocks you already have keep working.

Using an agent? [Point it at the bundled skill](https://ewels.github.io/starlight-codeblocks/agent-skill/).

Expand Down Expand Up @@ -31,6 +31,8 @@ export default defineConfig({
});
```

On an Astro site without Starlight, add `codeblocks()` from `starlight-codeblocks/astro` to `integrations` instead. The [Astro setup](https://ewels.github.io/starlight-codeblocks/install/astro/) page gives the details.

Most features start only when a code block uses their attribute, or a [comment notation](https://ewels.github.io/starlight-codeblocks/comment-notation/) directive such as `# [!code focus]`. A few, such as file icons and colour swatches, apply to every matching block.

The [documentation](https://ewels.github.io/starlight-codeblocks/) has a page for each feature, with live examples, and a reference for every option.
Expand Down
10 changes: 9 additions & 1 deletion docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import { sidebar } from './src/sidebar.mjs';
export default defineConfig({
site: 'https://ewels.github.io',
base: '/starlight-codeblocks',
redirects: {
'/getting-started': '/starlight-codeblocks/install/starlight/',
'/astro-without-starlight': '/starlight-codeblocks/install/astro/',
},
integrations: [
starlight({
title: 'starlight-codeblocks',
Expand Down Expand Up @@ -76,7 +80,11 @@ export default defineConfig({
runnable: { timeout: 5000 },
lineStates: { states: { todo: { label: 'To do', colour: { dark: '#c792ea', light: '#7c3aed' } } } },
}),
starlightLinksValidator({ exclude: linksValidatorExclude }),
starlightLinksValidator({
// The example site builds into docs/dist after the docs, so the validator cannot see it.
exclude: (context) =>
linksValidatorExclude(context) || context.link.startsWith('/starlight-codeblocks/examples/'),
}),
],
sidebar,
}),
Expand Down
2 changes: 1 addition & 1 deletion docs/e2e/carousel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ test('a button or a dot shows its example, announces it and marks itself, and th
});

test('the buttons are a flat list in sidebar order, in even columns, with one dot each', async ({ page }) => {
await page.goto('./getting-started/');
await page.goto('./install/starlight/');
const sidebarIds = await page
.locator('nav[aria-label="Main"] a[href*="/features/"]')
.evaluateAll((els) =>
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,6 @@ starlight({

## Options with an ec.config.mjs file

Some sites keep their Expressive Code options in an `ec.config.mjs` file, next to `astro.config.mjs`. If that file has a `plugins` list, add `pluginCodeblocks()` to it, as in [Sites with an ec.config.mjs file](/starlight-codeblocks/getting-started/#sites-with-an-ecconfigmjs-file).
Some sites keep their Expressive Code options in an `ec.config.mjs` file, next to `astro.config.mjs`. If that file has a `plugins` list, add `pluginCodeblocks()` to it, as in [Sites with an ec.config.mjs file](/starlight-codeblocks/install/starlight/#sites-with-an-ecconfigmjs-file).

Give `pluginCodeblocks()` no argument. It reads its options from `codeblocks()`, so all options stay in `astro.config.mjs`. `styleOverrides` can go in either file.
8 changes: 6 additions & 2 deletions docs/src/content/docs/features/code-tabs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ title: Code tabs
description: Show several code blocks as one, with editor tabs in the title bar, such as the files of a project or the commands for each package manager.
---

import { LinkCard, TabItem, Tabs } from '@astrojs/starlight/components';
import { Aside, LinkCard, TabItem, Tabs } from '@astrojs/starlight/components';
import Example from '../../../components/Example.astro';
import Options from '../../../components/Options.astro';

<Aside>
On an Astro site without Starlight, code tabs are off until you set `codeTabs: {}` in `codeblocks()`. See [Astro setup](/starlight-codeblocks/install/astro/#options).
</Aside>

Examples often come in several files, or in versions for each package manager or language. Stacked one after the other, they fill the page with code that most readers skip. Code tabs show them as one block, with a tab for each block in the title bar, as in a code editor.

export const files = `
Expand Down Expand Up @@ -82,7 +86,7 @@ Each code block is a variant. A tab shows the `title` of its variant. A variant

### Package managers

A tab with no `title` shows the `label`, with no file icon. Blocks with the same `sync` key switch together, on this page and on the other pages of the site. Select **pnpm**, and the install command on the [home page](/starlight-codeblocks/) changes too:
A tab with no `title` shows the `label`, with no file icon. Blocks with the same `sync` key switch together, on this page and on the other pages of the site. Select **pnpm**, and every other block with `sync="pm"` changes too:

export const packageManagers = `
:::code-tabs{sync="pm"}
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/features/hidden-lines.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ With `hiddenLines: false`, the plugin ignores `hidden={range}`, and `[!code hide

## Hidden lines or collapsible sections

Expressive Code has its own plugin for a similar job, [`@expressive-code/plugin-collapsible-sections`](https://expressive-code.com/plugins/collapsible-sections/). Its `collapse={range}` attribute folds lines into a section with a summary line, such as "3 collapsed lines". The plugin is not part of Starlight, so a site must install it and add it to the `plugins` list of its `ec.config.mjs` file. That list replaces the plugins of Starlight, so add `pluginCodeblocks()` to it too, as in [Sites with an ec.config.mjs file](/starlight-codeblocks/getting-started/#sites-with-an-ecconfigmjs-file):
Expressive Code has its own plugin for a similar job, [`@expressive-code/plugin-collapsible-sections`](https://expressive-code.com/plugins/collapsible-sections/). Its `collapse={range}` attribute folds lines into a section with a summary line, such as "3 collapsed lines". The plugin is not part of Starlight, so a site must install it and add it to the `plugins` list of its `ec.config.mjs` file. That list replaces the plugins of Starlight, so add `pluginCodeblocks()` to it too, as in [Sites with an ec.config.mjs file](/starlight-codeblocks/install/starlight/#sites-with-an-ecconfigmjs-file):

```js title="ec.config.mjs"
import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections';
Expand Down
10 changes: 5 additions & 5 deletions docs/src/content/docs/features/inline-code-highlighting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { Aside, LinkCard } from '@astrojs/starlight/components';
import Example from '../../../components/Example.astro';
import Options from '../../../components/Options.astro';

<Aside>
On an Astro site without Starlight, inline code highlighting is off until you set `inlineHighlighting: {}` in `codeblocks()`. See [Astro setup](/starlight-codeblocks/install/astro/#options).
</Aside>

Starlight shows inline code in the prose in one colour, with no syntax highlighting. A short expression such as a function call is then harder to read than the same code in a block. A `{:lang}` suffix at the end of the inline code gives it the syntax colours of that language. A site can also set a [default language](#a-default-language) for all inline code.

<Aside type="tip">
Expand Down Expand Up @@ -39,11 +43,7 @@ Each piece of inline code with a suffix gets the colours and the background of t
| `` `<code>{:<lang>}` `` | Inside the backticks, at the end of the code |
| `` `<code>`{:<lang>} `` | Directly after the closing backtick |

`<lang>` is a language name or alias that Expressive Code knows, as in a code block: `js`, `py`, `sh`, `css` and others. Languages that the site adds in the `shiki.langs` option of Expressive Code work too. The suffix must follow the code or the backtick with no space between them.

<Aside type="tip">
If possible, use the first syntax, with the suffix inside the backticks. It is the same syntax as `rehype-pretty-code`, and it is valid in `.md` and `.mdx` files with no change.
</Aside>
`<lang>` is a language name or alias that Expressive Code knows, as in a code block: `js`, `py`, `sh`, `css` and others. Languages that the site adds in the `shiki.langs` option of Expressive Code work too. The suffix must follow the code or the backtick with no space between them. If possible, use the first syntax, with the suffix inside the backticks. It is the same syntax as `rehype-pretty-code`, and it is valid in `.md` and `.mdx` files with no change.

<Aside type="caution">
MDX reads a `{:js}` after the closing backtick as a JavaScript expression, and the build fails.
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/features/run-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ The language of the block chooses the runtime. Three languages work with no set-

- For other languages, a site adds a runtime in the [`runtimes` option](#options). The guide to [adding a runtime](/starlight-codeblocks/extend/add-a-runtime/) shows how.
- A site runtime for Python, JavaScript or TypeScript replaces the built-in one.
- Sites without Starlight add each runtime themselves, as in [Without Starlight](/starlight-codeblocks/reference/expressive-code-plugins/#without-starlight).
- Sites without `codeblocks()` add each runtime themselves, as in [Without codeblocks()](/starlight-codeblocks/reference/expressive-code-plugins/#without-codeblocks).

In JavaScript and TypeScript, `console.log()` and `console.info()` go to standard output. `console.error()` and `console.warn()` go to standard error. The code runs as the body of an `async` function, so `await` works at the top level. The code cannot use `import` statements.

Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/features/visible-whitespace.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ The style setting `codeblocksWhitespace.foreground` changes the colour of the gl

- The glyphs add visual noise on a block with a lot of whitespace. Use `whitespace="all"` only where the extra detail matters, and leave the default, leading-only view for everything else.
- Expressive Code removes the whitespace at the end of each line before any plugin sees the code. The plugin puts it back for fenced code blocks in Markdown and MDX. A block that the `<Code>` component renders does not show trailing whitespace.
- Expressive Code turns every tab into spaces before this feature can see it. `codeblocks()` sets `tabWidth: 0` for you unless you already chose your own value. A site without Starlight sets `tabWidth: 0` in `ec.config.mjs` itself. A tab then reaches the plugin unchanged, and a Makefile block copies its real tab rather than spaces. Set `expressiveCode.tabWidth` in the Starlight config, or `tabWidth` in `ec.config.mjs`, for Expressive Code's normal tab-to-space expansion instead.
- Expressive Code turns every tab into spaces before this feature can see it. `codeblocks()` sets `tabWidth: 0` for you unless you already chose your own value. A site without `codeblocks()` sets `tabWidth: 0` in `ec.config.mjs` itself. A tab then reaches the plugin unchanged, and a Makefile block copies its real tab rather than spaces. Set `expressiveCode.tabWidth` in the Starlight config, or `tabWidth` in `ec.config.mjs`, for Expressive Code's normal tab-to-space expansion instead.

## Related

Expand Down
Loading
Loading