vuejs / vitepress

Vite & Vue powered static site generator.
https://vitepress.dev
MIT License
12.89k stars 2.08k forks source link

No way to input multiline data into Vue Component #423

Closed ottopaulsen closed 1 year ago

ottopaulsen commented 2 years ago

Describe the bug

I cannot find any way to get a multiline text as input to a Vue component. In Vuepress 2 I managed to do this using a prop, but this seems not to be possible in Vitepress.

Ideally I would use the slot, but that did not work in Vuepress either. Using a prop named src is good enough.

My purpose is to make a Vue component that can display Mermaid diagrams. It works fine in Vuepress 2, but I cannot get it to work in Vitepress.

This is the bug I am getting:

[plugin:vite:vue] Cannot read property 'length' of undefined
/Users/ottopaulsen/tdev/vitepress-mermaid/docs/index.md
    at posToNumber (/Users/ottopaulsen/tdev/vitepress-mermaid/node_modules/vite/dist/node/chunks/dep-be032392.js:4079:27)
    at generateCodeFrame (/Users/ottopaulsen/tdev/vitepress-mermaid/node_modules/vite/dist/node/chunks/dep-be032392.js:4104:13)
    at formatError (/Users/ottopaulsen/tdev/vitepress-mermaid/node_modules/vite/dist/node/chunks/dep-be032392.js:42247:33)
    at TransformContext.error (/Users/ottopaulsen/tdev/vitepress-mermaid/node_modules/vite/dist/node/chunks/dep-be032392.js:42202:19)
    at /Users/ottopaulsen/tdev/vitepress-mermaid/node_modules/@vitejs/plugin-vue/dist/index.js:4447:45
    at Array.forEach (<anonymous>)
    at transformMain (/Users/ottopaulsen/tdev/vitepress-mermaid/node_modules/@vitejs/plugin-vue/dist/index.js:4447:12)
    at TransformContext.transform (/Users/ottopaulsen/tdev/vitepress-mermaid/node_modules/@vitejs/plugin-vue/dist/index.js:4804:16)
    at Object.transform (/Users/ottopaulsen/tdev/vitepress-mermaid/node_modules/vite/dist/node/chunks/dep-be032392.js:42413:53)
    at async doTransform (/Users/ottopaulsen/tdev/vitepress-mermaid/node_modules/vite/dist/node/chunks/dep-be032392.js:56760:29

Reproduction

  1. Create a Vitepress project as described in the doc.
  2. Install Mermaid: npm install -s mermaid
  3. Create component MyMermaid as shown below (in the docs/components folder)
  4. Fill in the index.md file with data shown below
  5. Start dev server and open the page

MyMermaid.vue:

<template>
  <div class="mermaid">
    {{ source }}
  </div>
</template>

<script>
export default {
  props: {
    type: {type: String, required: true},
    src: {type: String, required: true}
  },
  computed: {
    source() {
      return this.type + "\n" + this.src.replace(/\n */g, "\n ");
    }
  },
  beforeMount() {
    import("mermaid/dist/mermaid").then(m => {
      m.initialize({
        startOnLoad: true
      });
      m.init();
    });
  },

};
</script>

index.md:

<script setup>
import MyMermaid from '/components/MyMermaid.vue'
</script>

# Hello VitePress

<MyMermaid type="sequenceDiagram" src="
  Alice->>John: Hello John, how are you?
  loop Healthcheck
    John->>John: Fight against hypochondria
  end
  Note right of John: Rational thoughts!
  John-->>Alice: Great!
  John->>Bob: How about you?
  Bob-->>John: Jolly good!
"/>

Expected behavior

I would expect to get the sequence diagram nicely displayed.

System Info

System:
    OS: macOS 11.6
    CPU: (16) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 1.76 GB / 16.00 GB
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 14.18.0 - /usr/local/bin/node
    Yarn: 1.22.15 - /usr/local/bin/yarn
    npm: 6.14.15 - /usr/local/bin/npm
  Browsers:
    Chrome: 94.0.4606.81
    Safari: 15.0

Additional context

The above describes my purpose. However, the problem seems to be related to having a prop containing both multiple lines and the > character.

This much simpler scenario also gives the same bug:

MyComponent.vue:

<template>
  <div>
    src prop coming here:
    {{src}}
  </div>
</template>

<script>
export default {
  props: {
    src: String
  }
};
</script>

index.md:

<script setup>
import MyComponent from '/components/MyComponent.vue'
</script>

# Hello VitePress

<MyComponent src=" This is the src text >
"></MyComponent>

If you remove either the > or the newline from the src prop, it will not fail.

Validations

kiaking commented 2 years ago

For multiline to work, use back tick and you should be able to do this.

<MyMermaid
  type="sequenceDiagram"
  :src="`
    AliceJohn: Hello John, how are you?
    loop Healthcheck
    ...
  `"
/>

Now the containing > is different story. When we add >, we get

[plugin:vite:vue] Attribute name cannot contain U+0022 ("), U+0027 ('), and U+003C (<).

So this sounds expected error 🤔

@patak-dev Do you know how we can work around this issue?

ottopaulsen commented 2 years ago

Well, I have found a very nice solution to this, but then I am using Vuepress, not Vitepress:

First I have this component:

// Mermaid.vue

<template>
  <div class="mermaid" v-html="svg"></div>
</template>

<script setup>
import mermaid from "mermaid";
import { useDarkMode } from "@vuepress/theme-default/lib/client/composables/useDarkMode";
import { ref, watchEffect } from "vue";

const props = defineProps({
  src: null,
});

const isDarkMode = useDarkMode();
const svg = ref(null);
watchEffect(() => {
  if (__VUEPRESS_SSR__ || props.src === "undefined") {
    return;
  }
  const config = {
    startOnLoad: false,
    theme: "base",
    themeVariables: {
      primaryTextColor: "#000F3C",
      primaryColor: isDarkMode.value ? "#000F3C" : "#E8FDFF",
    },
  };
  mermaid.initialize(config);
  const id = Math.floor(Math.random() * 100000);
  mermaid.render("mermaid-" + id, props.src, (svgCode) => {
    svg.value = svgCode;
  });
});
</script>

Then I have this container that I install as a plugin:

// mermaid-container.js

const { containerPlugin } = require("@vuepress/plugin-container");

module.exports = containerPlugin({
  type: "mermaid",
  marker: "`",
  render: (tokens, idx, options, env, slf) => {
    if (tokens[idx].nesting === 1) {
      // opening tag
      const content = tokens.filter(
        (t, i) => i > idx && t.level === 2 && t.nesting === 0
      )[0]?.content;
      return `<Mermaid src="${content}">`;
    } else {
      // closing tag
      return "</Mermaid>\n";
    }
  },
});

Now you can write mermaid diagrams like this:

``` mermaid
sequenceDiagram
User->>Server: Request page
``` 

Which is perfect!

I am using Vuepress 2.0.0-beta.45.

kiaking commented 2 years ago

Ah, interesting. Cool. So essentially, we need to have Markdown plugin to do this. So maybe you could already do the same with VitePress by passing in markdown option to config.js? Not sure but...

I'll not be working on with this since I think it's a bit low priority in regards to releasing 1.0.0, but I guess this is nice feature to at least document 👍

So I'll keep this issue open as enhancement for now and see if anyone from the community can work on with it.

patak-dev commented 2 years ago

Just leaving a note here that https://sli.dev also supports mermaid out of the box, so maybe a good idea to see how they integrated it: https://sli.dev/guide/syntax.html#diagrams

brc-dd commented 1 year ago

Use some plugin like https://github.com/emersonbottero/vitepress-plugin-mermaid (this one is used in mermaid's next docs too).

Also, in your md plugin you're doing `<Mermaid src="${content}">`, which will break in many cases. It is better to encode it, pass it as prop, then decode it in component.