Search code examples
vue.jsmarkdownvue-routervue-router4

Pass markdown files to Vue component through router


I'm using VueJS. I have externally generated markdowns, and I want to generate static pages out of them thanks to a vue component. I'm using the vite-plugin-md to convert imported md files into a component.

Working example

Config (vite.config.js)

import Markdown from "vite-plugin-vue-markdown"

The router (index.ts)

  {
    path: "/my_page_1",
    name: "My Page 1",
    component: () => import("@/pages/markdown.vue"),
  }

The component (markdown.vue)

<template>
  <MardownPage/>
</template>

<script lang="ts" setup>
import MardownPage from "@/assets/markdown/my_page_1.md"
</script>

Expected behavior

In the working example, the component is tied to a unique markdown.

I actually want to use the same component for many different markdown pages. I feel the expected way to do this is to have something like the following in the router :

  {
    path: "/my_page_1",
    name: "My Page 1",
    component: () => import("@/pages/markdown.vue"),
    meta: {
      path: "@/assets/markdown/page_1.md",
    },
  },
  {
    path: "/my_page_2",
    name: "My Page 2",
    component: () => import("@/pages/markdown.vue"),
    meta: {
      path: "@/assets/markdown/page_2.md",
    },
  }

Current (non-working) behavior

On the component side, I've written the following

import {useRoute} from "vue-router";
import { defineAsyncComponent } from 'vue'

const route = useRoute();

console.log(route.meta.path);

const AsyncComp = defineAsyncComponent(() =>
  import(route.meta.path)
)

While the path appears in the console, it does return an error related to the dynamic importation of the module (error loading dynamically imported module).

I've tested many options related to lazy loading, dynamic routes, promises, ... (which I'm not 100% familiar and skilled with) but haven't worked my way around to do so.


Solution

  • Looking at what you currently have and what you described as the desired output, I would suggest starting at looking at defineAsyncComponent. This function needs to be provided with a resolved promised. You are currently attempting to run import dynamically at runtime & it might also have something to do with aliases in your path.

    I setup the following project:

    vite.config.ts

    import { fileURLToPath, URL } from 'node:url'
    
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import Markdown from 'vite-plugin-vue-markdown'
    import path from 'node:path'
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [
        vue({
          include: [/\.vue$/, /\.md$/],
        }),
        Markdown(),
      ],
      resolve: {
        alias: {
          '@': fileURLToPath(new URL('./src', import.meta.url)),
          'components': path.resolve(__dirname, './src/components'),
        }
      }
    })
    

    src/router/index.ts

    const router = createRouter({
      history: createWebHistory(import.meta.env.BASE_URL),
      routes: [
        // ...
        {
          path: '/test',
          name: 'test',
          component: () => import('@/views/MarkdownView.vue'),
          meta: {
            path: 'components/Test.md',
            markdownComponent: defineAsyncComponent(() => import('components/Test.md'))
          },
        }
      ]
    })
    

    src/views/MarkdownView.vue

    <template>
        <AsyncMarkdown />
    </template>
    <script setup lang="ts">
    import { defineAsyncComponent } from 'vue'
    import { useRoute } from 'vue-router';
    
    const route = useRoute()
    
    console.log(route.meta.path) // components/Test.md
    
    // DOES NOT WORK
    const AsyncMarkdown = defineAsyncComponent(() => import(route.meta.path))
    
    // WORKS
    const AsyncMarkdown = route.meta.markdownComponent
    </script>
    

    If I change the route.meta.path to the relative path ../components/Test.md, it will then load the component.

    Possible Solutions

    1. Load the defineAsyncComponent() in your router
    2. Add the full/relative path without alias to the route.meta.page variable.