Skip to content

Plugin

  1. vitepress 中的 markdown 的配置
  2. vite 插件

Demo

  1. markdown-it 的处理方式

  2. vite 插件处理方式

  3. md -> vue

  4. <code src='' />

vue
<template>
  <component-demo
    demo-file-name="<!--DEMO_FILE_NAME-->"
    relative-url="<!--URL-->"
    title="<!--TITLE_SLOT-->"
    code="<!--CODE_SLOT-->"
  >
    <template #title>
      <!-- TITLE_SLOT -->
    </template>
    <template #content>
      <!-- CONTENT_SLOT -->
    </template>
    <template #demo>
      <div class="demo-card__view">
        <!-- DEMO_SLOT -->
      </div>
    </template>
  </component-demo>
</template>

<!-- SCRIPT_SLOT -->

<!-- STYLE_SLOT -->
js
const fs = require('node:fs')
const path = require('node:path')
const marked = require('marked')
const createRenderer = require('./md-renderer')
const mdRenderer = createRenderer()

const demoBlock = fs
  .readFileSync(path.resolve(__dirname, 'ComponentDemoTemplate.vue'))
  .toString()

function getPartsOfDemo(tokens) {
  let template = null
  let script = null
  let style = null
  let title = null
  const contentTokens = []
  contentTokens.links = tokens.links
  for (const token of tokens) {
    if (token.type === 'heading' && token.depth === 1) {
      title = token.text
    }
    else if (
      token.type === 'code'
      && (token.lang === 'template' || token.lang === 'html')
    ) {
      template = token.text
    }
    else if (
      token.type === 'code'
      && (token.lang === 'script' || token.lang === 'js')
    ) {
      script = token.text
    }
    else if (
      token.type === 'code'
      && (token.lang === 'style' || token.lang === 'css')
    ) {
      style = token.text
    }
    else {
      contentTokens.push(token)
    }
  }
  return {
    template,
    script,
    style,
    title,
    content: marked.parser(contentTokens, {
      renderer: mdRenderer,
    }),
  }
}

function mergeParts(parts) {
  const mergedParts = {
    ...parts,
  }
  mergedParts.title = parts.title
  mergedParts.content = parts.content
  mergedParts.code = ''
  if (parts.template) {
    mergedParts.code += `<template>\n${parts.template
      .split('\n')
      .map(line => (line.length ? `  ${line}` : line))
      .join('\n')}\n</template>`
  }
  if (parts.script) {
    if (parts.template)
      mergedParts.code += '\n\n'
    mergedParts.code += `<script>
${parts.script}
</script>`
  }
  if (parts.style) {
    if (parts.template || parts.script)
      mergedParts.code += '\n\n'
    mergedParts.code += `<style>
${parts.style}
</style>`
  }
  mergedParts.code = encodeURIComponent(mergedParts.code)
  return mergedParts
}

const cssRuleRegex = /([^{}]*)(\{[^}]*\})/g

// simulate scss style
// to remove dep of sass
// xxx {
//   mystyle
// }
function genStyle(sourceStyle) {
  let match
  let matched = false
  const rules = []

  // eslint-disable-next-line no-cond-assign
  while ((match = cssRuleRegex.exec(sourceStyle)) !== null) {
    matched = true
    const selector = match[1]
    const body = match[2]
    rules.push(
      selector
        .split(',')
        .map(part => `.demo-card__view ${part}, .naive-ui-doc ${part}`)
        .join(',') + body,
    )
  }
  if (!matched)
    return null
  return `<style scoped>\n${rules.join('\n')}</style>`
}

function genVueComponent(parts, fileName, relativeUrl, noRunning = false) {
  const demoFileNameReg = /<!--DEMO_FILE_NAME-->/g
  const relativeUrlReg = /<!--URL-->/g
  const titleReg = /<!--TITLE_SLOT-->/g
  const contentReg = /<!--CONTENT_SLOT-->/
  const codeReg = /<!--CODE_SLOT-->/
  const scriptReg = /<!--SCRIPT_SLOT-->/
  const styleReg = /<!--STYLE_SLOT-->/
  const demoReg = /<!--DEMO_SLOT-->/
  let src = demoBlock
  src = src.replace(demoFileNameReg, fileName)
  src = src.replace(relativeUrlReg, relativeUrl)
  if (parts.content) {
    src = src.replace(contentReg, parts.content)
  }
  if (parts.title) {
    src = src.replace(titleReg, parts.title)
  }
  if (parts.code) {
    src = src.replace(codeReg, parts.code)
  }
  if (parts.script && !noRunning) {
    src = src.replace(scriptReg, `<script>\n${parts.script}\n</script>`)
  }
  if (parts.style) {
    const style = genStyle(parts.style)
    if (style !== null) {
      src = src.replace(styleReg, style)
    }
  }
  if (parts.template) {
    src = src.replace(demoReg, parts.template)
  }
  return src.trim()
}

function getFileName(resourcePath) {
  const dirs = resourcePath.split('/')
  const fileNameWithExtension = dirs[dirs.length - 1]
  return [fileNameWithExtension.split('.')[0], fileNameWithExtension]
}

function convertMd2Demo(text, { resourcePath, relativeUrl }) {
  const noRunning = /<!--no-running-->/.test(text)
  const tokens = marked.lexer(text)
  const parts = getPartsOfDemo(tokens)
  const mergedParts = mergeParts(parts)
  const [fileName] = getFileName(resourcePath)
  const vueComponent = genVueComponent(
    mergedParts,
    fileName,
    relativeUrl,
    noRunning,
  )
  return vueComponent
}

module.exports = convertMd2Demo
markdown
:::demo Progress 组件设置`percentage`属性即可,表示进度条对应的百分比,**必填**,必须在 0-100。通过 `format` 属性来指定进度条文字内容。

```html
<el-progress :percentage="50"></el-progress>
<el-progress :percentage="100" :format="format"></el-progress>

<script>
  export default {
    methods: {
      format(percentage) {
        return percentage === 100 ? '满' : `${percentage}%`;
      },
    },
  };
</script>
```

:::

## markdown-it
```javascript
// default mode
var md = require('markdown-it')();

// enable everything
var md = require('markdown-it')({
  html: true,
  linkify: true,
  typographer: true
});

// full options list (defaults)
var md = require('markdown-it')({
  html:         false,        // Enable HTML tags in source
  xhtmlOut:     false,        // Use '/' to close single tags (
).
                              // This is only for full CommonMark compatibility.
  breaks:       false,        // Convert '\n' in paragraphs into <br>
  langPrefix:   'language-',  // CSS language prefix for fenced blocks. Can be
                              // useful for external highlighters.
  linkify:      false,        // Autoconvert URL-like text to links

  // Enable some language-neutral replacement + quotes beautification
  // For the full list of replacements, see https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js
  typographer:  false,

  // Double + single quotes replacement pairs, when typographer enabled,
  // and smartquotes on. Could be either a String or an Array.
  //
  // For example, you can use '«»„“' for Russian, '„“‚‘' for German,
  // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
  quotes: '“”‘’',

  // Highlighter function. Should return escaped HTML,
  // or '' if the source string is not changed and should be escaped externally.
  // If result starts with <pre... internal wrapper is skipped.
  highlight: function (/*str, lang*/) { return ''; }
});

reference