Rewriting vue prism component in vue 3
Tags: vue3, vue, prismjs, typescript
Published: 2020-11-12 16:03:50

With Vue 3 released, there will be many libraries running into port their vue 2 projects into vue 3. Recently while working on creating a library on vue 3 I needed a syntax highlighter for demo purpose, so thought of writing one with vue 3 Setup API.

for this to work we need prismjs so let's add prismJs library;

`yarn add prismjs`

We would be requiring code that is supposed to be used as a highlighter and the language in which the code would be.
```
import * as Vue from 'vue';
import Prism from 'prismjs';

export default Vue.defineComponent({
  props: {
    code: {
      type: String,
    },
    inline: {
      type: Boolean,
      default: false,
    },
    language: {
      type: String,
      default: 'markup',
    },
  }
})
```

Now let's see how can we used the setup function to access props and children. Setup function provides props and setupContext as parameters, we can easily destructure setupContext to access attrs and slots.
```
...
setup(props, { slots, attrs }: { slots: Slots; attrs: Data }) {
    const { h } = Vue;
    const slotsData = (slots && slots.default && slots.default()) || [];
    const code = props.code || (slotsData.length > 0 ? slotsData[0].children : '');
    const { inline, language } = props;
    const prismLanguage = Prism.languages[language];
    const className = `language-${language}`;

...
```
The above code will be access to props and children's passed to the prismJs.Also, `h` which was passed to render function but now it has to be imported from vue.

With this done let's see how can we pass `{{code}}` as well as `language` to prismJs so that it can return HTML back to us, that can be used in the render function.

```
    const d = Prism.highlight(code, prismLanguage);

```
with everything in place, lets add our render function with these data.

```
...
return (): VNode =>
      h('pre', { ...attrs, class: [attrs.class, className] }, [
        h('code', {
          class: className,
          innerHTML: d,
        }),
      ]);
...
```
In 3.x, the entire VNode props structure is flattened. you can read more about render function [Vue 3 Render Function](https://v3.vuejs.org/guide/migration/render-function-api.html#_2-x-syntax-3).

So this is how our code will look on completion.
```
// prismcomponent/index.ts
import * as Vue from 'vue';
import Prism from 'prismjs';
import { Slots, VNode } from 'vue';

declare type Data = Record<string, unknown>;

export default Vue.defineComponent({
  props: {
    code: {
      type: String,
    },
    inline: {
      type: Boolean,
      default: false,
    },
    language: {
      type: String,
      default: 'markup',
    },
  },
  setup(props, { slots, attrs }: { slots: Slots; attrs: Data }) {
    const { h } = Vue;
    const slotsData = (slots && slots.default && slots.default()) || [];
    const code = props.code || (slotsData.length > 0 ? slotsData[0].children : '');
    const { inline, language } = props;
    const prismLanguage = Prism.languages[language];
    const className = `language-${language}`;

    if (inline) {
      return (): VNode =>
        h('code', { ...attrs, class: [attrs.class, className], innerHTML: Prism.highlight(code, prismLanguage) });
    }

    const d = Prism.highlight(code, prismLanguage);
    return (): VNode =>
      h('pre', { ...attrs, class: [attrs.class, className] }, [
        h('code', {
          class: className,
          innerHTML: d,
        }),
      ]);
  },
});
```

So in other components, all we need to add is 
```
<template>
...
<Prism language="javascript" class="codesnippet">
  {{ code }}
</Prism>
...
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import Prism from '../prismcomponent';
import 'prismjs';
import 'prismjs/themes/prism.css';

export default defineComponent({
...
setup() {
    const code = `const c = a+b`;
    return {
      code,
    };
  },
...
})
</script>
```