Introduction
Want to show TypeScript code with hover-over type info on your Astro site? Here’s how.
Before You Start
Set up an Astro project. Need help? See the Astro quickstart guide.
Set Up Shiki for Syntax Highlighting
Astro includes Shiki. Let’s configure it:
- Edit
astro.config.mjs
:
import { function defineConfig(config: AstroUserConfig): AstroUserConfig
See the full Astro Configuration API Documentation
https://astro.build/configdefineConfig } from "astro/config";
export default function defineConfig(config: AstroUserConfig): AstroUserConfig
See the full Astro Configuration API Documentation
https://astro.build/configdefineConfig({
AstroUserConfig.markdown?: {
shikiConfig?: Partial<ShikiConfig>;
syntaxHighlight?: "shiki" | "prism" | false;
remarkPlugins?: RemarkPlugins;
rehypePlugins?: RehypePlugins;
gfm?: boolean;
smartypants?: boolean;
remarkRehype?: RemarkRehype;
} | undefined
markdown: {
shikiConfig?: Partial<ShikiConfig> | undefined
shikiConfig: {
themes?: Record<string, ThemePresets | ThemeRegistration | ThemeRegistrationRaw> | undefined
themes: { light: "min-light"
light: "min-light", dark: "tokyo-night"
dark: "tokyo-night" },
},
},
});
- Add a border to code blocks in your CSS:
pre:has(code) {
@apply border border-skin-line;
}
Add Twoslash for Type Information
- Install packages:
npm i -D twoslash twoslash-vue
- Update
astro.config.mjs
:
import { function createTransformerFactory(defaultTwoslasher: TwoslashShikiFunction | TwoslashGenericFunction, defaultRenderer?: TwoslashRenderer): (options?: TransformerTwoslashOptions) => ShikiTransformer
createTransformerFactory, function rendererRich(options?: RendererRichOptions): TwoslashRenderer
An alternative renderer that providers better prefixed class names,
with syntax highlight for the info text.rendererRich } from '@shikijs/twoslash/core'
import { function createTwoslasher(createOptions?: CreateTwoslashVueOptions): TwoslashInstance
Create a twoslasher instance that add additional support for Vue SFC.createTwoslasher } from 'twoslash-vue'
import { function defineConfig(config: AstroUserConfig): AstroUserConfig
See the full Astro Configuration API Documentation
https://astro.build/configdefineConfig } from "astro/config";
export default function defineConfig(config: AstroUserConfig): AstroUserConfig
See the full Astro Configuration API Documentation
https://astro.build/configdefineConfig({
AstroUserConfig.markdown?: {
shikiConfig?: Partial<ShikiConfig>;
syntaxHighlight?: "shiki" | "prism" | false;
remarkPlugins?: RemarkPlugins;
rehypePlugins?: RehypePlugins;
gfm?: boolean;
smartypants?: boolean;
remarkRehype?: RemarkRehype;
} | undefined
markdown: {
shikiConfig?: Partial<ShikiConfig> | undefined
shikiConfig: {
themes?: Record<string, ThemePresets | ThemeRegistration | ThemeRegistrationRaw> | undefined
themes: { light: "min-light"
light: "min-light", dark: "tokyo-night"
dark: "tokyo-night" },
transformers?: ShikiTransformer[] | undefined
transformers: [
function createTransformerFactory(defaultTwoslasher: TwoslashShikiFunction | TwoslashGenericFunction, defaultRenderer?: TwoslashRenderer): (options?: TransformerTwoslashOptions) => ShikiTransformer
createTransformerFactory(function createTwoslasher(createOptions?: CreateTwoslashVueOptions): TwoslashInstance
Create a twoslasher instance that add additional support for Vue SFC.createTwoslasher())({
TransformerTwoslashOptions.langs?: string[] | undefined
Languages to apply this transformer tolangs: ['ts', 'tsx', 'vue'],
TransformerTwoslashOptions.renderer?: TwoslashRenderer | undefined
Custom renderers to decide how each info should be renderedrenderer: function rendererRich(options?: RendererRichOptions): TwoslashRenderer
An alternative renderer that providers better prefixed class names,
with syntax highlight for the info text.rendererRich({
RendererRichOptions.lang?: string | undefined
Language for syntax highlight.lang: 'ts',
}),
}),
],
},
},
});
Use Interactive Snippets
Create TypeScript snippets in Markdown:
interface User {
User.name: string
name: string;
User.age: number
age: number;
}
const const user: User
user: User = {
User.name: string
name: "John Doe",
User.age: number
age: 30
};
var console: Console
The `console` module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
* A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream.
* A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and
[`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without calling `require('console')`.
_**Warning**_: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for
more information.
Example using the global `console`:
```js
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
```
Example using the `Console` class:
```js
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
```console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html)
(the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)).
```js
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
```
See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.log(const user: User
user.User.name: string
name);
The // ^?
shows where type info appears on hover.
Vue snippets work too:
<script setup lang="ts">
import { function ref<T>(value: T): [T] extends [Ref] ? IfAny<T, Ref<T>, T> : Ref<UnwrapRef<T>, UnwrapRef<T> | T> (+1 overload)
Takes an inner value and returns a reactive and mutable ref object, which
has a single property `.value` that points to the inner value.ref } from 'vue'
const { const title: string
title } = const defineProps: <{
title: string;
}>() => DefineProps<LooseRequired<{
title: string;
}>, never> (+2 overloads)
Vue `<script setup>` compiler macro for declaring component props. The
expected argument is the same as the component `props` option.
Example runtime declaration:
```js
// using Array syntax
const props = defineProps(['foo', 'bar'])
// using Object syntax
const props = defineProps({
foo: String,
bar: {
type: Number,
required: true
}
})
```
Equivalent type-based declaration:
```ts
// will be compiled into equivalent runtime declarations
const props = defineProps<{
foo?: string
bar: number
}>()
```defineProps<{
title: string
title: string
}>()
const const count: Ref<number, number>
count = ref<number>(value: number): Ref<number, number> (+1 overload)
Takes an inner value and returns a reactive and mutable ref object, which
has a single property `.value` that points to the inner value.ref(0)
const const increment: () => void
increment = () => {
const count: Ref<number, number>
count.- value
v
}
const const decrement: () => void
decrement = () => {
const count: Ref<number, number>
count.Ref<number, number>.value: number
value--
}
</script>
<template>
<div: HTMLAttributes & ReservedProps
div>
<h2: HTMLAttributes & ReservedProps
h2>{{ const title: string
title }}</h2: HTMLAttributes & ReservedProps
h2>
<p: HTMLAttributes & ReservedProps
p>Count: {{ const count: Ref<number, number>
count }}</p: HTMLAttributes & ReservedProps
p>
<button: ButtonHTMLAttributes & ReservedProps
button @onClick?: ((payload: MouseEvent) => void) | undefined
click="const increment: () => void
increment">Increment</button: ButtonHTMLAttributes & ReservedProps
button>
<button: ButtonHTMLAttributes & ReservedProps
button @onClick?: ((payload: MouseEvent) => void) | undefined
click="const decrement: () => void
decrement">Decrement</button: ButtonHTMLAttributes & ReservedProps
button>
</div: HTMLAttributes & ReservedProps
div>
</template>
For autocompletion, use ^|
:
const increment = () => {
count.v
// ^|
}
What You’ve Gained
Your Astro site now has:
- Syntax highlighting
- Hover-over type info
- Light/dark mode code blocks
Your readers will thank you.