Smoothly animated code blocks with Shiki
npm install shiki-magic-move[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![bundle][bundle-src]][bundle-href]
[![JSDocs][jsdocs-src]][jsdocs-href]
[![License][license-src]][license-href]
Smoothly animated code blocks with Shiki. Online Demo.
Shiki Magic Move is a low-level library for animating code blocks, and uses Shiki as the syntax highlighter. You usually want to use it with a high-level integration like Slidev.
At the core of the shiki-magic-move package is a framework-agnostic core, and renderer — there are also framework wrappers for Vue, React, and Svelte.
Each of the framework wrappers provides the following components:
- ShikiMagicMove - the main component to wrap the code block
- ShikiMagicMovePrecompiled - animations for compiled tokens, without the dependency on Shiki
- ShikiMagicMoveRenderer - the low-level renderer component
The ShikiMagicMove component requires you to provide a Shiki highlighter instance, and the styles are also required, and provided by shiki-magic-move. Whenever the code changes, the component will animate the changes.
You're going to need Shiki Magic Move for animating the code blocks, and Shiki for syntax highlighting.
``bash`
npm i shiki-magic-move shiki
Import shiki-magic-move/vue, and pass the highlighter instance to the ShikiMagicMove component.
`vue
theme="nord"
:highlighter="highlighter"
:code="code"
:options="{ duration: 800, stagger: 0.3, lineNumbers: true }"
/>
`
Import shiki-magic-move/react, and pass the highlighter instance to the ShikiMagicMove component.
`tsx
import type { HighlighterCore } from 'shiki'
import { useEffect, useState } from 'react'
import { createHighlighter } from 'shiki'
import { ShikiMagicMove } from 'shiki-magic-move/react'
import 'shiki-magic-move/dist/style.css'
function App() {
const [code, setCode] = useState(const hello = 'world')
const [highlighter, setHighlighter] = useState
useEffect(() => {
async function initializeHighlighter() {
const highlighter = await createHighlighter({
themes: ['nord'],
langs: ['javascript', 'typescript'],
})
setHighlighter(highlighter)
}
initializeHighlighter()
}, [])
function animate() {
setCode(let hi = 'hello')
}
return (
$3
Import
shiki-magic-move/solid, and pass the highlighter instance to the ShikiMagicMove component.`tsx
import { createHighlighter, } from 'shiki'
import { ShikiMagicMove } from 'shiki-magic-move/solid'
import { createResource, createSignal } from 'solid-js'import 'shiki-magic-move/dist/style.css'
function App() {
const [code, setCode] = createSignal(
const hello = 'world') const [highlighter] = createResource(async () => {
const newHighlighter = await createHighlighter({
themes: Object.keys(bundledThemes),
langs: Object.keys(bundledLanguages),
})
return newHighlighter
})
function animate() {
setCode(
let hi = 'hello')
} return (
{highlighter => (
<>
lang="ts"
theme="nord"
highlighter={highlighter()}
code={code()}
options={{ duration: 800, stagger: 0.3, lineNumbers: true }}
/>
>
)}
)
}
`$3
Import
shiki-magic-move/svelte, and pass the highlighter instance to the ShikiMagicMove component.`svelte
{#await highlighter then highlighter}
lang='ts'
theme='nord'
{highlighter}
{code}
options={{ duration: 800, stagger: 0.3, lineNumbers: true }}
/>
{/await}
`$3
ShikiMagicMovePrecompiled is a lighter version of ShikiMagicMove that doesn't require Shiki. It's useful when you want to animate the compiled tokens directly. For example, in Vue:`vue
:steps="compiledSteps"
:step="step"
/>
`To get the compiled tokens, you can run this somewhere else and serialize them into the component:
`ts
import { createHighlighter } from 'shiki'
import { codeToKeyedTokens, createMagicMoveMachine } from 'shiki-magic-move/core'const shiki = await createHighlighter({
theme: 'nord',
langs: ['javascript', 'typescript'],
})
const codeSteps = [
const hello = 'world',
let hi = 'hello',
]const machine = createMagicMoveMachine(
code => codeToKeyedTokens(shiki, code, {
lang: 'ts',
theme: 'nord',
}),
{
// options
}
)
const compiledSteps = codeSteps.map(code => machine.commit(code).current)
// Pass
compiledSteps to the precompiled component
// If you do this on server-side or build-time, you can serialize compiledSteps into JSON
``You can read The Magic In Shiki Magic Move to understand how Shiki Magic Move works.
MIT License © 2023-PRESENT Anthony Fu
[npm-version-src]: https://img.shields.io/npm/v/shiki-magic-move?style=flat&colorA=080f12&colorB=1fa669
[npm-version-href]: https://npmjs.com/package/shiki-magic-move
[npm-downloads-src]: https://img.shields.io/npm/dm/shiki-magic-move?style=flat&colorA=080f12&colorB=1fa669
[npm-downloads-href]: https://npmjs.com/package/shiki-magic-move
[bundle-src]: https://img.shields.io/bundlephobia/minzip/shiki-magic-move?style=flat&colorA=080f12&colorB=1fa669&label=minzip
[bundle-href]: https://bundlephobia.com/result?p=shiki-magic-move
[license-src]: https://img.shields.io/github/license/shikijs/shiki-magic-move.svg?style=flat&colorA=080f12&colorB=1fa669
[license-href]: https://github.com/shikijs/shiki-magic-move/blob/main/LICENSE
[jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=1fa669
[jsdocs-href]: https://www.jsdocs.io/package/shiki-magic-move