diff --git a/.changeset/nasty-ladybugs-whisper.md b/.changeset/nasty-ladybugs-whisper.md
new file mode 100644
index 00000000..6c58facb
--- /dev/null
+++ b/.changeset/nasty-ladybugs-whisper.md
@@ -0,0 +1,5 @@
+---
+"vocs": major
+---
+
+**Breaking:** Removed the `::snip` directive. Use the `// [!include]` marker instead. [See more.](https://vocs.dev/docs/guides/code-snippets)
\ No newline at end of file
diff --git a/.changeset/strange-lions-kick.md b/.changeset/strange-lions-kick.md
new file mode 100644
index 00000000..0ff356ad
--- /dev/null
+++ b/.changeset/strange-lions-kick.md
@@ -0,0 +1,5 @@
+---
+"vocs": minor
+---
+
+Added support for [regions in code snippets](https://vocs.dev/docs/guides/code-snippets#regions).
\ No newline at end of file
diff --git a/package.json b/package.json
index 6a89ef83..b5bdd073 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
"tsx": "^4.6.1",
"typescript": "^5.2.2",
"vercel": "^32.5.6",
+ "viem": "^2.1.1",
"vocs": "workspace:*"
},
"simple-git-hooks": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e94a0c9a..92308b20 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -71,6 +71,9 @@ importers:
vercel:
specifier: ^32.5.6
version: 32.5.6
+ viem:
+ specifier: ^2.1.1
+ version: 2.1.1(typescript@5.2.2)
vocs:
specifier: workspace:*
version: link:src
@@ -444,6 +447,10 @@ importers:
packages:
+ /@adraffy/ens-normalize@1.10.0:
+ resolution: {integrity: sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==}
+ dev: true
+
/@alloc/quick-lru@5.2.0:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
@@ -1808,10 +1815,15 @@ packages:
- supports-color
dev: false
+ /@noble/curves@1.2.0:
+ resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==}
+ dependencies:
+ '@noble/hashes': 1.3.2
+ dev: true
+
/@noble/hashes@1.3.2:
resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==}
engines: {node: '>= 16'}
- dev: false
/@nodelib/fs.scandir@2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
@@ -2682,6 +2694,25 @@ packages:
dev: false
optional: true
+ /@scure/base@1.1.5:
+ resolution: {integrity: sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==}
+ dev: true
+
+ /@scure/bip32@1.3.2:
+ resolution: {integrity: sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==}
+ dependencies:
+ '@noble/curves': 1.2.0
+ '@noble/hashes': 1.3.2
+ '@scure/base': 1.1.5
+ dev: true
+
+ /@scure/bip39@1.2.1:
+ resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==}
+ dependencies:
+ '@noble/hashes': 1.3.2
+ '@scure/base': 1.1.5
+ dev: true
+
/@shuding/opentype.js@1.4.0-beta.0:
resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==}
engines: {node: '>= 8.0.0'}
@@ -3301,6 +3332,20 @@ packages:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
dev: true
+ /abitype@0.10.0(typescript@5.2.2):
+ resolution: {integrity: sha512-QvMHEUzgI9nPj9TWtUGnS2scas80/qaL5PBxGdwWhhvzqXfOph+IEiiiWrzuisu3U3JgDQVruW9oLbJoQ3oZ3A==}
+ peerDependencies:
+ typescript: '>=5.0.4'
+ zod: ^3 >=3.22.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ zod:
+ optional: true
+ dependencies:
+ typescript: 5.2.2
+ dev: true
+
/accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
@@ -5594,6 +5639,14 @@ packages:
/isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ /isows@1.0.3(ws@8.13.0):
+ resolution: {integrity: sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg==}
+ peerDependencies:
+ ws: '*'
+ dependencies:
+ ws: 8.13.0
+ dev: true
+
/jackspeak@2.3.6:
resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
engines: {node: '>=14'}
@@ -8618,6 +8671,29 @@ packages:
vfile-message: 4.0.2
dev: false
+ /viem@2.1.1(typescript@5.2.2):
+ resolution: {integrity: sha512-gJiwYceD7Dsjioglr+85GQS3u5Gp9XGG8oJqGsauBaEPFlkmbRx7cxD2Q5RZXFToVvEbarOWtITZtGHBsGv4MQ==}
+ peerDependencies:
+ typescript: '>=5.0.4'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@adraffy/ens-normalize': 1.10.0
+ '@noble/curves': 1.2.0
+ '@noble/hashes': 1.3.2
+ '@scure/bip32': 1.3.2
+ '@scure/bip39': 1.2.1
+ abitype: 0.10.0(typescript@5.2.2)
+ isows: 1.0.3(ws@8.13.0)
+ typescript: 5.2.2
+ ws: 8.13.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+ - zod
+ dev: true
+
/vite-node@0.28.5(@types/node@20.8.9):
resolution: {integrity: sha512-LmXb9saMGlrMZbXTvOveJKwMTBTNUH66c8rJnQ0ZPNX+myPEol64+szRzXtV5ORb0Hb/91yq+/D3oERoyAt6LA==}
engines: {node: '>=v14.16.0'}
@@ -8822,6 +8898,19 @@ packages:
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+ /ws@8.13.0:
+ resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+ dev: true
+
/xdg-app-paths@5.1.0:
resolution: {integrity: sha512-RAQ3WkPf4KTU1A8RtFx3gWywzVKe00tfOPFfl2NDGqbIFENQO4kqAJp7mhQjNj/33W5x5hiWWUdyfPq/5SU3QA==}
engines: {node: '>=6'}
diff --git a/site/pages/docs/guides/code-snippets.mdx b/site/pages/docs/guides/code-snippets.mdx
index 967078fb..72120368 100644
--- a/site/pages/docs/guides/code-snippets.mdx
+++ b/site/pages/docs/guides/code-snippets.mdx
@@ -1,105 +1,213 @@
# Code Snippets [Including code snippets in Markdown]
-You can include code snippets in a Markdown file by making use of the `::snip` directive.
+You can include code snippets in a Markdown file by making use of the `// [!include ...]` marker.
-:::tip[Tip]
-The `"::"` syntax refers to the [Directives Syntax Proposal](https://talk.commonmark.org/t/generic-directives-plugins-syntax/444).
-:::
+## Quick Start
::::steps
-## Create the code snippet
+### Create the code snippet
+
+First, we will create a snippet called `example.ts`. Let's put this under a `snippets/` directory.
-First, we will create a snippet called `snippet.ts` that we will import into a Markdown file.
+```ts [docs/snippets/example.ts]
+// [!include ~/snippets/example.ts:import]
-```ts [docs/snippets/snippet.ts]
-import { writeFileSync } from "fs"
+// [!include ~/snippets/example.ts:setup]
-writeFileSync("myfile.txt", "Hello world!")
+// [!include ~/snippets/example.ts:usage-1]
```
-## Import the snippet
+### Import the snippet
+
+Next, we will import the snippet into our Markdown file using the `// [!include ...]` marker.
-Next, we will import the snippet into our Markdown file using the `::snip` directive.
+````mdx [example.md]
+#### Usage
-```mdx [example.md]
-### Usage
+```ts
+import { writeFileSync } from 'node:fs'
+//$ [!include ~/snippets/example.ts] // [!code hl]
-::snip{path="~/snippets/snippet.ts"}
+writeFileSync('test.txt', blockNumber.toString())
```
+````
:::info
The `"~"` in the path refers to the [root (`config.rootDir`) directory](/docs/structure#root-directory) of the project.
:::
-## Output
+### Output
The resulting output will look like this:
-### Usage
+#### Usage
```ts
-import { writeFileSync } from "fs"
+import { writeFileSync } from 'node:fs'
+// [!include ~/snippets/example.ts:import]
-writeFileSync("myfile.txt", "Hello world!") // [!code focus]
+// [!include ~/snippets/example.ts:setup]
+
+// [!include ~/snippets/example.ts:usage-1]
+
+writeFileSync('test.txt', blockNumber.toString())
```
-## Bonus: Use code block features
+::::
+
+## Regions
+
+You can also include a specific region of a code snippet by using the `// [!region]` and `// [!endregion]` markers.
+
+```ts [docs/snippets/example.ts]
+//$ [!region import] // [!code focus]
+import { http, createPublicClient } from 'viem'
+import { mainnet } from 'viem/chains'
+//$ [!endregion import] // [!code focus]
+
+//$ [!region setup] // [!code focus]
+const client = createPublicClient({
+ chain: mainnet,
+ transport: http(),
+})
+//$ [!endregion setup] // [!code focus]
+
+//$ [!region usage] // [!code focus]
+const blockNumber = await client.getBlockNumber()
+//$ [!endregion usage] // [!code focus]
+```
+
+Then, we can include the regions in the Markdown with the `// [!include]` marker:
+
+````md [example.md]
+```ts
+import { writeFileSync } from 'node:fs'
+//$ [!include ~/snippets/example.ts:import] // [!code focus]
-We can include features like [line focus (`// [!code focus]`)](/docs/api/markdown#line-focus) in our code snippets.
+//$ [!include ~/snippets/example.ts:setup] // [!code focus]
-```ts [docs/snippets/snippet.ts]
-import { writeFileSync } from "fs"
+//$ [!include ~/snippets/example.ts:usage] // [!code focus]
-writeFileSync("myfile.txt", "Hello world!") !! [!code focus]
+writeFileSync('test.txt', blockNumber.toString())
```
+````
Which will result in the snippet being rendered like this:
-```ts
-import { writeFileSync } from "fs"
+```ts
+import { writeFileSync } from 'node:fs'
+// [!include ~/snippets/example.ts:import]
-writeFileSync("myfile.txt", "Hello world!") // [!code focus]
+// [!include ~/snippets/example.ts:setup]
+
+// [!include ~/snippets/example.ts:usage-1]
+
+writeFileSync('test.txt', blockNumber.toString())
```
-## Bonus: Add a title
+### Duplicate variable declarations
+
+When writing snippets, you may run into a scenario where you want to define multiple regions that share the same variable name.
-We can include a title for the code snippet by adding a title to the `::snip` directive.
+To avoid type errors, you can use the `_$` suffix to discriminate the variable name.
-```mdx [example.md]
-### Usage
+The rendered snippet will still use the original variable name (ie. the name before the `_$` suffix).
-::snip[Example.ts]{path="~/snippets/snippet.ts"}
+```ts
+// [!region import]
+import { http, createPublicClient } from 'viem'
+import { mainnet } from 'viem/chains'
+// [!endregion import]
+
+// [!region setup]
+const client = createPublicClient({
+ chain: mainnet,
+ transport: http(),
+})
+// [!endregion setup]
+
+// [!region usage-1] // [!code focus]
+const block_$1 = await client.getBlock() // [!code focus]
+// [!endregion usage-1] // [!code focus]
+
+// [!region usage-2] // [!code focus]
+const block_$2 = await client.getBlock({ blockNumber: 42069n }) // [!code focus]
+// [!endregion usage-2] // [!code focus]
+
+// [!region usage-3] // [!code focus]
+const block_$3 = await client.getBlock({ blockTag: 'latest' }) // [!code focus]
+// [!endregion usage-3] // [!code focus]
```
+## Tip: Code Block Markers
+
+We can also include markers like [line highlight (`// [!code hl]`)](/docs/api/markdown#line-highlights) in our code snippets.
+
+:::code-group
+
+```ts [docs/snippets/example.ts]
+// [!include ~/snippets/example.ts:import]
+
+// [!include ~/snippets/example.ts:setup]
+
+// [!include ~/snippets/example.ts:usage-2-docs]
+```
+
+````md [example.md]
+```ts
+import { writeFileSync } from 'node:fs'
+//$ [!include ~/snippets/example.ts]
+
+writeFileSync('test.txt', blockNumber.toString())
+```
+````
+
+:::
+
Which will result in the snippet being rendered like this:
-```ts [Example.ts]
-import { writeFileSync } from "fs"
+```ts
+import { writeFileSync } from 'node:fs'
+// [!include ~/snippets/example.ts:import]
+
+// [!include ~/snippets/example.ts:setup]
+
+// [!include ~/snippets/example.ts:usage-2]
-writeFileSync("myfile.txt", "Hello world!") // [!code focus]
+writeFileSync('test.txt', blockNumber.toString())
```
-## Bonus: Add Twoslash
+## Tip: Twoslash
+
+We can also include Twoslash markers in our code snippets.
+
+:::code-group
-We can include [Twoslash](/docs/guides/twoslash) for the code snippet by using the `::snip-twoslash` directive.
+```ts [docs/snippets/example.ts]
+// [!include ~/snippets/example.ts:import]
-```mdx [example.md]
-### Usage
+// [!include ~/snippets/example.ts:setup]
-::snip-twoslash[Example.ts]{path="~/snippets/snippet.ts"}
+// [!include ~/snippets/example.ts:usage-4]
```
+````md [example.md]
+```ts twoslash
+//$ [!include ~/snippets/example.ts]
+```
+````
+
+:::
+
Which will result in the snippet being rendered like this:
-```ts [Example.ts] twoslash
-///
-// ---cut---
-import { writeFileSync } from "fs"
+```ts twoslash
+// [!include ~/snippets/example.ts:import]
-writeFileSync("myfile.txt", "Hello world!") // [!code focus]
-```
+// [!include ~/snippets/example.ts:setup]
-::::
\ No newline at end of file
+// [!include ~/snippets/example.ts:usage-4]
+```
\ No newline at end of file
diff --git a/site/pages/docs/guides/markdown-snippets.mdx b/site/pages/docs/guides/markdown-snippets.mdx
index 24d727d0..0f06fdea 100644
--- a/site/pages/docs/guides/markdown-snippets.mdx
+++ b/site/pages/docs/guides/markdown-snippets.mdx
@@ -6,19 +6,21 @@ You can include other Markdown files in a Markdown file by making use of the `im
This only works with MDX (`.mdx`) files. If you want to take advantage of this feature and you are using a `.md` file, rename the file extension to `.mdx`.
:::
+## Quick Start
+
::::steps
-## Create a snippet
+### Create a snippet
First, we will create a snippet called `snippet.mdx` that we will import into another Markdown file.
```mdx [snippet.mdx]
-## Hello world
+### Hello world
This is my snippet.
```
-## Import the snippet
+### Import the snippet
Next, we will import the snippet into our MDX file using an `import{:ts}` statement. It compiles to a React component, so we can render it with `{:tsx}`.
@@ -32,7 +34,7 @@ This is an example of including a snippet in a Markdown file.
```
-## Output
+### Output
The resulting output will look like this:
@@ -46,7 +48,9 @@ This is an example of including a snippet in a Markdown file.
This is my snippet.
-## Bonus: Use Props!
+::::
+
+## Tip: Passing Props
As we are just rendering a React component, we can also pass props to `{:tsx}`. We can access those props in the MDX file with the `props` global variable.
@@ -66,6 +70,4 @@ This is an example of including a snippet in a Markdown file.
{props.content}
```
-:::
-
-::::
\ No newline at end of file
+:::
\ No newline at end of file
diff --git a/site/snippets/example.ts b/site/snippets/example.ts
new file mode 100644
index 00000000..f66b76f3
--- /dev/null
+++ b/site/snippets/example.ts
@@ -0,0 +1,38 @@
+// [!region import]
+import { http, createPublicClient } from 'viem'
+import { mainnet } from 'viem/chains'
+// [!endregion import]
+
+// [!region setup]
+const client = createPublicClient({
+ chain: mainnet,
+ transport: http(),
+})
+// [!endregion setup]
+
+// [!region usage-1]
+const blockNumber_$1 = await client.getBlockNumber()
+// [!endregion usage-1]
+
+// [!region usage-2-docs]
+const blockNumber_$2 = await client.getBlockNumber() //$ [!code hl] // [!code focus]
+// [!endregion usage-2-docs]
+
+// [!region usage-2]
+const blockNumber_$3 = await client.getBlockNumber() // [!code hl]
+// [!endregion usage-2]
+
+// [!region usage-3-docs]
+//$ [!code word:getBlockNumber] // [!code focus]
+const blockNumber_$4 = await client.getBlockNumber()
+// [!endregion usage-3-docs]
+
+// [!region usage-3]
+// [!code word:getBlockNumber]
+const blockNumber_$5 = await client.getBlockNumber()
+// [!endregion usage-3]
+
+// [!region usage-4]
+const blockNumber_$6 = await client.getBlockNumber()
+// ^?
+// [!endregion usage-4]
diff --git a/src/app/components/mdx/Pre.tsx b/src/app/components/mdx/Pre.tsx
index a2a6fa86..b169924a 100644
--- a/src/app/components/mdx/Pre.tsx
+++ b/src/app/components/mdx/Pre.tsx
@@ -13,10 +13,7 @@ export function Pre({
function recurseChildren(children: ReactNode): ReactNode {
if (!children) return children
- if (typeof children !== 'object') {
- if (typeof children === 'string') return children.replace('!!', '//')
- return children
- }
+ if (typeof children !== 'object') return children
if ('props' in children)
return {
...children,
diff --git a/src/app/styles/vars.css.ts b/src/app/styles/vars.css.ts
index b1d6588f..80bf9324 100644
--- a/src/app/styles/vars.css.ts
+++ b/src/app/styles/vars.css.ts
@@ -36,6 +36,7 @@ export const primitiveColorVars = createGlobalThemeContract(
borderRed: 'borderRed',
borderYellow: 'borderYellow',
heading: 'heading',
+ inverted: 'inverted',
shadow: 'shadow',
shadow2: 'shadow2',
text: 'text',
@@ -86,6 +87,7 @@ createGlobalTheme(':root', primitiveColorVars, {
borderRed: globalColors.redA.redA4,
borderYellow: globalColors.yellowA.yellowA5,
heading: globalColors.gray.gray12,
+ inverted: black,
shadow: globalColors.grayA.grayA3,
shadow2: globalColors.grayA.grayA2,
text: '#4c4c4c',
@@ -134,6 +136,7 @@ createGlobalTheme(':root.dark', primitiveColorVars, {
borderRed: globalColors.redA.redA4,
borderYellow: globalColors.yellowA.yellowA2,
heading: '#e9e9ea',
+ inverted: white,
shadow: globalColors.grayDarkA.grayA1,
shadow2: globalColors.blackA.blackA1,
text: '#cfcfcf',
diff --git a/src/vite/plugins/mdx.ts b/src/vite/plugins/mdx.ts
index 5ac860ca..f3352259 100644
--- a/src/vite/plugins/mdx.ts
+++ b/src/vite/plugins/mdx.ts
@@ -30,12 +30,13 @@ import { remarkCode } from './remark/code.js'
import { remarkDetails } from './remark/details.js'
import { remarkInferFrontmatter } from './remark/inferred-frontmatter.js'
import { remarkLinks } from './remark/links.js'
-import { remarkSnippets } from './remark/snippets.js'
import { remarkSponsors } from './remark/sponsors.js'
import { remarkSteps } from './remark/steps.js'
import { remarkStrongBlock } from './remark/strong-block.js'
import { remarkSubheading } from './remark/subheading.js'
import { remarkTwoslash } from './remark/twoslash.js'
+import { transformerDisplayNotation } from './shikiji/transformerDisplayNotation.js'
+import { transformerNotationInclude } from './shikiji/transformerNotationInclude.js'
import { transformerSplitIdentifiers } from './shikiji/transformerSplitIdentifiers.js'
import { twoslashRenderer } from './shikiji/twoslashRenderer.js'
import { twoslasher } from './shikiji/twoslasher.js'
@@ -57,7 +58,6 @@ export const getRemarkPlugins = ({ markdown }: RemarkPluginsParameters = {}) =>
remarkBlogPosts,
remarkCallout,
remarkCode,
- remarkSnippets,
remarkCodeGroup,
remarkDetails,
remarkSponsors,
@@ -72,10 +72,15 @@ export const remarkPlugins = getRemarkPlugins()
type RehypePluginsParameters = {
markdown?: ParsedConfig['markdown']
+ rootDir?: ParsedConfig['rootDir']
twoslash?: ParsedConfig['twoslash']
}
-export const getRehypePlugins = ({ markdown, twoslash = {} }: RehypePluginsParameters = {}) =>
+export const getRehypePlugins = ({
+ markdown,
+ rootDir = '',
+ twoslash = {},
+}: RehypePluginsParameters = {}) =>
[
rehypeSlug,
[
@@ -87,6 +92,8 @@ export const getRehypePlugins = ({ markdown, twoslash = {} }: RehypePluginsParam
transformerNotationFocus(),
transformerNotationHighlight(),
transformerNotationWordHighlight(),
+ transformerNotationInclude({ rootDir }),
+ transformerDisplayNotation(),
transformerTwoslash({
explicitTrigger: true,
renderer: twoslashRenderer(),
@@ -123,9 +130,9 @@ export const rehypePlugins = getRehypePlugins()
export async function mdx(): Promise {
const { config } = await resolveVocsConfig()
- const { markdown, twoslash } = config
+ const { markdown, rootDir, twoslash } = config
const remarkPlugins = getRemarkPlugins({ markdown })
- const rehypePlugins = getRehypePlugins({ markdown, twoslash })
+ const rehypePlugins = getRehypePlugins({ markdown, rootDir, twoslash })
return [
mdxPlugin({
providerImportSource: '@mdx-js/react',
diff --git a/src/vite/plugins/remark/snippets.ts b/src/vite/plugins/remark/snippets.ts
deleted file mode 100644
index ca532c86..00000000
--- a/src/vite/plugins/remark/snippets.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-///
-///
-
-import { readFileSync } from 'fs'
-import { resolve } from 'node:path'
-import type { Root, Text } from 'mdast'
-import { visit } from 'unist-util-visit'
-
-import type { ContainerDirective } from 'mdast-util-directive'
-import { resolveVocsConfig } from '../../utils/resolveVocsConfig.js'
-
-export function remarkSnippets() {
- return async (tree: Root) => {
- const { config } = await resolveVocsConfig()
- const { rootDir } = config
-
- visit(tree, (node, index, parent) => {
- if (node.type !== 'leafDirective') return
- if (node.name !== 'snip' && node.name !== 'snip-twoslash') return
- if (typeof index !== 'number') return
-
- const fileName = node.attributes?.path
- if (!fileName) return
-
- const path = resolve(rootDir, fileName.replace('~', '.'))
- const contents = readFileSync(path, { encoding: 'utf-8' }).replace(/\n$/, '')
-
- const title = (node.children[0] as Text)?.value
- const lang = path.split('.').pop() as string
-
- parent?.children.splice(index, 1, {
- type: 'code',
- lang,
- meta: title
- ? `${
- (parent as ContainerDirective).name === 'code-group'
- ? `[${title}]`
- : `title="${title}"`
- } ${node.name === 'snip-twoslash' ? 'twoslash' : ''}}`
- : '',
- value: contents,
- })
- })
- }
-}
diff --git a/src/vite/plugins/shikiji/transformerDisplayNotation.ts b/src/vite/plugins/shikiji/transformerDisplayNotation.ts
new file mode 100644
index 00000000..b5834e88
--- /dev/null
+++ b/src/vite/plugins/shikiji/transformerDisplayNotation.ts
@@ -0,0 +1,8 @@
+import type { ShikijiTransformer } from 'shikiji'
+
+export const transformerDisplayNotation = (): ShikijiTransformer => ({
+ name: 'display-notation',
+ postprocess(html) {
+ return html.replaceAll(/\/\/\$ \[(.*)\]/g, '// [$1]')
+ },
+})
diff --git a/src/vite/plugins/shikiji/transformerNotationInclude.ts b/src/vite/plugins/shikiji/transformerNotationInclude.ts
new file mode 100644
index 00000000..16855bdf
--- /dev/null
+++ b/src/vite/plugins/shikiji/transformerNotationInclude.ts
@@ -0,0 +1,68 @@
+import { readFileSync } from 'node:fs'
+import { resolve } from 'node:path'
+import type { ShikijiTransformer } from 'shikiji'
+
+const includeRegex = /\/\/ \[!include (.*)\]/
+const regionRegex = /\/\/ \[!region (.*)\]/
+const endRegionRegex = /\/\/ \[!endregion (.*)\]/
+
+export type TransformerNotationIncludeOptions = {
+ rootDir: string
+}
+
+export const transformerNotationInclude = ({
+ rootDir,
+}: TransformerNotationIncludeOptions): ShikijiTransformer => ({
+ name: 'includes',
+ preprocess(code) {
+ if (!code) return code
+
+ const includes = code.includes('// [!include')
+ if (!includes) return code
+
+ const lines = code.split('\n')
+ let i = 0
+ while (i < lines.length) {
+ const line = lines[i]
+ const match = line.match(includeRegex)
+ if (match) {
+ const [, value] = match
+ const [fileName, region] = value.split(':')
+ const path = resolve(rootDir, fileName.replace('~', '.'))
+ const contents = readFileSync(path, { encoding: 'utf-8' }).replace(/\n$/, '')
+ lines.splice(i, 1, extractRegion(contents, region))
+ }
+ i++
+ }
+ return lines.join('\n')
+ },
+})
+
+function extractRegion(code: string, region: string | undefined) {
+ const lines = []
+
+ let inRegion = !region
+ for (const line of code.split('\n')) {
+ const startRegionMatch = line.match(regionRegex)
+ const endRegionMatch = line.match(endRegionRegex)
+
+ // If we are in a region, ignore any other region markers.
+ if (inRegion && !startRegionMatch && !endRegionMatch) lines.push(line.replaceAll(/_\$\d*/g, ''))
+ // If we have have found a `// [!region ${region}]` marker, start including lines.
+ else if (startRegionMatch) {
+ const [, regionName] = startRegionMatch
+ if (regionName === region) {
+ inRegion = true
+ }
+ }
+ // If we have have found a `// [!endregion ${region}]` marker, stop including lines.
+ else if (endRegionMatch) {
+ const [, regionName] = endRegionMatch
+ if (regionName === region) {
+ inRegion = false
+ }
+ }
+ }
+
+ return lines.join('\n')
+}