# 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](https://shiki-magic-move.netlify.app/). Shiki Magic Move is a low-level library for animating code blocks, and uses [Shiki](https://shiki.style/) as the syntax highlighter. You usually want to use it with a high-level integration like [Slidev](https://sli.dev/guide/syntax#shiki-magic-move). At the core of the `shiki-magic-move` package is a framework-agnostic [core](./src/core.ts), and [renderer](./src/renderer.ts) — there are also framework wrappers for [Vue](./src/vue), [React](./src/react), and [Svelte](./src/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. ## Installation 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 ``` ## Usage ### Vue Import `shiki-magic-move/vue`, and pass the highlighter instance to the `ShikiMagicMove` component. ```vue ``` ### React Import `shiki-magic-move/react`, and pass the highlighter instance to the `ShikiMagicMove` component. ```tsx import { useEffect, useState } from 'react' import { getHighlighter, type HighlighterCore } 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 getHighlighter({ themes: ['nord'], langs: ['javascript', 'typescript'], }) setHighlighter(highlighter) } initializeHighlighter() }, []) function animate() { setCode(`let hi = 'hello'`) } return (
{highlighter && ( <> )}
) } ``` ### Solid Import `shiki-magic-move/solid`, and pass the highlighter instance to the `ShikiMagicMove` component. ```tsx import { getHighlighter, type HighlighterCore } 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 => ( <> )}
) } ``` ### Svelte Import `shiki-magic-move/svelte`, and pass the highlighter instance to the `ShikiMagicMove` component. ```svelte {#await highlighter then highlighter} {/await} ``` ### `ShikiMagicMovePrecompiled` `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 ``` To get the compiled tokens, you can run this somewhere else and serialize them into the component: ```ts import { getHighlighter } from 'shiki' import { codeToKeyedTokens, createMagicMoveMachine } from 'shiki-magic-move/core' const shiki = await getHighlighter({ 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 ``` ## How it works You can read [The Magic In Shiki Magic Move](https://antfu.me/posts/shiki-magic-move) to understand how Shiki Magic Move works. ## Sponsors

## License [MIT](./LICENSE) License © 2023-PRESENT [Anthony Fu](https://github.com/antfu) [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