feature: vor und nachteile

This commit is contained in:
Hikyu
2026-05-31 13:54:31 +02:00
parent 552a947a1c
commit e3dad17446
23270 changed files with 3024794 additions and 200 deletions
+2
View File
@@ -0,0 +1,2 @@
open_collective: antfu
github: [antfu]
@@ -0,0 +1,24 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: lts/*
- run: npx changelogithub
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
@@ -0,0 +1,42 @@
name: Test
on:
push:
branches:
- main
- master
pull_request:
branches:
- main
- master
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [18.x, lts/*]
os: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Setup
run: npm i -g pnpm @antfu/ni
- name: Install
run: nci
- name: Build
run: nr build
- name: Test
run: nr test --if-present
+41
View File
@@ -0,0 +1,41 @@
{
// Enable the ESlint flat config support
"eslint.experimental.useFlatConfig": true,
// Disable the default formatter, use eslint instead
"prettier.enable": false,
"editor.formatOnSave": false,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "never"
},
// Silent the stylistic rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off" },
{ "rule": "*-indent", "severity": "off" },
{ "rule": "*-spacing", "severity": "off" },
{ "rule": "*-spaces", "severity": "off" },
{ "rule": "*-order", "severity": "off" },
{ "rule": "*-dangle", "severity": "off" },
{ "rule": "*-newline", "severity": "off" },
{ "rule": "*quotes", "severity": "off" },
{ "rule": "*semi", "severity": "off" }
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml"
]
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-PRESENT Anthony Fu<https://github.com/antfu>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+341
View File
@@ -0,0 +1,341 @@
# unplugin-vue-markdown
[![NPM version](https://img.shields.io/npm/v/unplugin-vue-markdown?color=a1b858)](https://www.npmjs.com/package/unplugin-vue-markdown)
Compile Markdown to Vue component.
- 📚 Use Markdown as Vue components.
- 💚 Use Vue components in Markdown.
- 🔌 Supports Vite, Webpack, Vue CLI and more, powered by [unplugin](https://github.com/unjs/unplugin).
- ⚡️ The same transformation as [VitePress](https://vitepress.vuejs.org/).
## Install
```bash
npm i unplugin-vue-markdown
```
<details>
<summary>Vite</summary><br>
```ts
// vite.config.ts
import Vue from '@vitejs/plugin-vue'
import Markdown from 'unplugin-vue-markdown/vite'
export default defineConfig({
plugins: [
Vue({
include: [/\.vue$/, /\.md$/], // <-- allows Vue to compile Markdown files
}),
Markdown({ /* options */ }),
],
})
```
Example: [`examples/vite`](./examples/vite/)
<br>
</details>
<details>
<summary>Webpack</summary><br>
```ts
// webpack.config.js
const Markdown = require('unplugin-vue-markdown/webpack')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
/* ... */
module: {
rules: [
// ... other rules
{
test: /\.(vue|md)$/,
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin(),
Markdown({ /* options */ })
]
}
```
<br>
</details>
<details>
<summary>Vue CLI</summary><br>
```ts
// vue.config.js
const Markdown = require('unplugin-vue-markdown/webpack')
module.exports = {
parallel: false, // Disable thread-loader which will cause errors, we are still investigating the root cause
chainWebpack: (config) => {
config.module
.rule('vue')
.test(/\.(vue|md)$/) // <-- allows Vue to compile Markdown files
config
.plugin('markdown')
.use(Markdown({
markdownItUses: [
prism,
],
}))
},
}
```
Example: [`examples/vue-cli`](./examples/vue-cli/)
<br>
</details>
## Import Markdown as Vue components
```html
<template>
<HelloWorld />
</template>
<script>
import HelloWorld from './README.md'
export default {
components: {
HelloWorld,
},
}
</script>
```
## Use Vue Components inside Markdown
You can even use Vue components inside your markdown, for example
```html
<Counter :init='5'/>
```
<Counter :init='5'/>
Note you can either register the components globally, or use the `<script setup>` tag to register them locally.
```ts
import { createApp } from 'vue'
import App from './App.vue'
import Counter from './Counter.vue'
const app = createApp(App)
// register global
app.component('Counter', Counter) // <--
app.mount()
```
```html
<script setup>
import { Counter } from './Counter.vue'
</script>
<Counter :init='5'/>
```
Or you can use [`unplugin-vue-components`](#work-with-unplugin-vue-components) for auto components registration.
## Frontmatter
Frontmatter will be parsed and inject into Vue's instance data `frontmatter` field.
For example:
```md
---
name: My Cool App
---
# Hello World
This is {{frontmatter.name}}
```
Will be rendered as
```html
<h1>Hello World</h1>
<p>This is My Cool App</p>
```
It will also be passed to the wrapper component's props if you have set `wrapperComponent` option.
## Document head and meta
To manage document head and meta, you would need to install [`@unhead/vue`](https://unhead.harlanzw.com/integrations/vue/setup) and do some setup.
```bash
npm i @unhead/vue
```
```js
// vite.config.js
import Vue from '@vitejs/plugin-vue'
import Markdown from 'unplugin-vue-markdown/vite'
export default {
plugins: [
Vue({
include: [/\.vue$/, /\.md$/],
}),
Markdown({
headEnabled: true // <--
})
]
}
```
```js
// src/main.js
import { createHead } from '@unhead/vue' // <--
import { createApp } from 'vue'
const app = createApp(App)
const head = createHead() // <--
app.use(head) // <--
```
Then you can use frontmatter to control the head. For example:
```yaml
---
title: My Cool App
meta:
- name: description
content: Hello World
---
```
For more options available, please refer to [`@unhead/vue`'s docs](https://unhead.harlanzw.com/integrations/vue/setup).
## Options
`unplugin-vue-markdown` uses [`markdown-it`](https://github.com/markdown-it/markdown-it) under the hood, see [`markdown-it`'s docs](https://markdown-it.github.io/markdown-it/) for more details
```ts
// vite.config.js
import MarkdownItAnchor from 'markdown-it-anchor'
import MarkdownItPrism from 'markdown-it-prism'
import Markdown from 'unplugin-vue-markdown/vite'
export default {
plugins: [
Markdown({
// default options passed to markdown-it
// see: https://markdown-it.github.io/markdown-it/
markdownItOptions: {
html: true,
linkify: true,
typographer: true,
},
// A function providing the Markdown It instance gets the ability to apply custom settings/plugins
markdownItSetup(md) {
// for example
md.use(MarkdownItAnchor)
md.use(MarkdownItPrism)
},
// Class names for the wrapper div
wrapperClasses: 'markdown-body'
})
],
}
```
See [the tsdoc](./src/types.ts) for more advanced options
## Example
See the [/examples](./examples).
Or the pre-configured Markdown template [Vitesse](https://github.com/antfu/vitesse).
## Integrations
### Work with [vite-plugin-pages](https://github.com/hannoeru/vite-plugin-pages)
```ts
import Vue from '@vitejs/plugin-vue'
import Markdown from 'unplugin-vue-markdown/vite'
import Pages from 'vite-plugin-pages'
export default {
plugins: [
Vue({
include: [/\.vue$/, /\.md$/],
}),
Pages({
extensions: ['vue', 'md'],
}),
Markdown()
],
}
```
Put your markdown under `./src/pages/xx.md`, then you can access the page via route `/xx`.
### Work with [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components)
`unplugin-vue-components` allows you to do on-demand components auto-importing without worrying about registration.
```ts
import Vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import Markdown from 'unplugin-vue-markdown/vite'
export default {
plugins: [
Vue({
include: [/\.vue$/, /\.md$/],
}),
Markdown(),
// should be placed after `Markdown()`
Components({
// allow auto load markdown components under `./src/components/`
extensions: ['vue', 'md'],
// allow auto import and register components used in markdown
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
})
],
}
```
Components under `./src/components` can be directly used in markdown components, and markdown components can also be put under `./src/components` to be auto imported.
## TypeScript Shim
```ts
declare module '*.vue' {
import type { ComponentOptions } from 'vue'
const Component: ComponentOptions
export default Component
}
declare module '*.md' {
import type { ComponentOptions } from 'vue'
const Component: ComponentOptions
export default Component
}
```
## License
MIT License © 2020-PRESENT [Anthony Fu](https://github.com/antfu)
+349
View File
@@ -0,0 +1,349 @@
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
// src/index.ts
import { createFilter } from "@rollup/pluginutils";
import { createUnplugin } from "unplugin";
// node_modules/.pnpm/@antfu+utils@0.7.10/node_modules/@antfu/utils/dist/index.mjs
function toArray(array) {
array = array ?? [];
return Array.isArray(array) ? array : [array];
}
function uniq(array) {
return Array.from(new Set(array));
}
var VOID = Symbol("p-void");
// src/core/markdown.ts
import { componentPlugin } from "@mdit-vue/plugin-component";
import { frontmatterPlugin } from "@mdit-vue/plugin-frontmatter";
import MarkdownIt from "markdown-it-async";
// src/core/head.ts
var headProperties = [
"title",
"meta",
"link",
"base",
"style",
"script",
"htmlAttrs",
"bodyAttrs"
];
function preprocessHead(frontmatter, options) {
if (!options.headEnabled)
return frontmatter;
const head = options.headField ? frontmatter[options.headField] || {} : frontmatter;
const meta = head.meta = head.meta || [];
if (head.title) {
if (!meta.find((i) => i.property === "og:title"))
meta.push({ property: "og:title", content: head.title });
if (!meta.find((i) => i.name === "twitter:title"))
meta.push({ name: "twitter:title", content: head.title });
}
if (head.description) {
if (!meta.find((i) => i.name === "description"))
meta.push({ name: "description", content: head.description });
if (!meta.find((i) => i.property === "og:description"))
meta.push({ property: "og:description", content: head.description });
if (!meta.find((i) => i.name === "twitter:description"))
meta.push({ name: "twitter:description", content: head.description });
}
if (head.image) {
if (!meta.find((i) => i.property === "og:image"))
meta.push({ property: "og:image", content: head.image });
if (!meta.find((i) => i.name === "twitter:image"))
meta.push({ name: "twitter:image", content: head.image });
if (!meta.find((i) => i.property === "twitter:card"))
meta.push({ name: "twitter:card", content: "summary_large_image" });
}
const result = {};
for (const [key, value] of Object.entries(head)) {
if (headProperties.includes(key))
result[key] = value;
}
return Object.entries(result).length === 0 ? null : result;
}
// src/core/markdown.ts
var scriptSetupRE = /<\s*script([^>]*)\bsetup\b([^>]*)>([\s\S]*)<\/script>/g;
var defineExposeRE = /defineExpose\s*\(/g;
var EXPORTS_KEYWORDS = [
"class",
"default",
"export",
"function",
"import",
"let",
"var",
"const",
"from",
"as",
"return",
"if",
"else",
"switch",
"case",
"break",
"for",
"while",
"do"
];
function extractScriptSetup(html) {
const scripts = [];
html = html.replace(scriptSetupRE, (_, attr1, attr2, code) => {
scripts.push({
code,
attr: `${attr1} ${attr2}`.trim()
});
return "";
});
return { html, scripts };
}
function extractCustomBlock(html, options) {
const blocks = [];
for (const tag of options.customSfcBlocks) {
html = html.replace(new RegExp(`<${tag}[^>]*\\b[^>]*>[^<]*<\\/${tag}>`, "gm"), (code) => {
blocks.push(code);
return "";
});
}
return { html, blocks };
}
function createMarkdown(options) {
const isVue2 = options.vueVersion.startsWith("2.");
const markdown = MarkdownIt({
html: true,
linkify: true,
typographer: true,
...options.markdownItOptions
});
markdown.use(componentPlugin, options.componentOptions);
if (options.frontmatter || options.excerpt) {
markdown.use(frontmatterPlugin, {
...options.frontmatterOptions,
grayMatterOptions: {
excerpt: options.excerpt,
...options.frontmatterOptions.grayMatterOptions
}
});
}
markdown.linkify.set({ fuzzyLink: false });
options.markdownItUses.forEach((e) => {
const [plugin, options2] = toArray(e);
markdown.use(plugin, options2);
});
const setupPromise = (async () => {
await options.markdownItSetup(markdown);
})();
return async (id, raw) => {
await setupPromise;
const {
wrapperClasses,
wrapperComponent,
transforms,
headEnabled,
frontmatterPreprocess
} = options;
raw = raw.trimStart();
raw = transforms.before?.(raw, id) ?? raw;
const env = { id };
let html = await markdown.renderAsync(raw, env);
const { excerpt = "", frontmatter: data = null } = env;
const wrapperClassesResolved = toArray(
typeof wrapperClasses === "function" ? wrapperClasses(id, raw) : wrapperClasses
).filter(Boolean).join(" ");
if (wrapperClassesResolved)
html = `<div class="${wrapperClassesResolved}">${html}</div>`;
else
html = `<div>${html}</div>`;
const wrapperComponentName = typeof wrapperComponent === "function" ? wrapperComponent(id, raw) : wrapperComponent;
if (wrapperComponentName) {
const attrs2 = [
options.frontmatter && ':frontmatter="frontmatter"',
options.excerpt && ':excerpt="excerpt"'
].filter(Boolean).join(" ");
html = `<${wrapperComponentName} ${attrs2}>${html}</${wrapperComponentName}>`;
}
html = transforms.after?.(html, id) ?? html;
if (options.escapeCodeTagInterpolation) {
html = html.replace(/<code(.*?)>/g, "<code$1 v-pre>");
}
const hoistScripts = extractScriptSetup(html);
html = hoistScripts.html;
const customBlocks = extractCustomBlock(html, options);
html = customBlocks.html;
const scriptLines = [];
let frontmatterExportsLines = [];
let excerptExportsLine = "";
let excerptKeyOverlapping = false;
function hasExplicitExports() {
return defineExposeRE.test(hoistScripts.scripts.map((i) => i.code).join(""));
}
if (options.frontmatter) {
if (options.excerpt && data) {
if (data.excerpt !== void 0)
excerptKeyOverlapping = true;
data.excerpt = excerpt;
}
const { head, frontmatter } = frontmatterPreprocess(data || {}, options, id, preprocessHead);
if (options.excerpt && !excerptKeyOverlapping && frontmatter.excerpt !== void 0)
delete frontmatter.excerpt;
scriptLines.push(`const frontmatter = ${JSON.stringify(frontmatter)}`);
if (options.exportFrontmatter) {
frontmatterExportsLines = Object.entries(frontmatter).map(([key, value]) => {
if (EXPORTS_KEYWORDS.includes(key))
key = `_${key}`;
return `export const ${key} = ${JSON.stringify(value)}`;
});
}
if (!isVue2 && options.exposeFrontmatter && !hasExplicitExports())
scriptLines.push("defineExpose({ frontmatter })");
if (!isVue2 && headEnabled && head) {
if (headEnabled === "vueuse")
throw new Error("unplugin-vue-markdown no longer supports @vueuse/head. Change `headEnabled` to `true` and install `@unhead/vue` instead.");
scriptLines.push(`const head = ${JSON.stringify(head)}`);
scriptLines.unshift(`import { useHead } from "@unhead/vue"`);
scriptLines.push("useHead(head)");
}
scriptLines.push(...transforms.extraScripts?.(frontmatter, id) || []);
}
if (options.excerpt) {
scriptLines.push(`const excerpt = ${JSON.stringify(excerpt)}`);
if (!excerptKeyOverlapping)
excerptExportsLine = `export const excerpt = ${JSON.stringify(excerpt)}
`;
if (!isVue2 && options.exposeExcerpt && !hasExplicitExports())
scriptLines.push("defineExpose({ excerpt })");
}
scriptLines.push(...hoistScripts.scripts.map((i) => i.code));
let attrs = uniq(hoistScripts.scripts.map((i) => i.attr)).join(" ").trim();
if (attrs)
attrs = ` ${attrs}`;
const scripts = isVue2 ? [
`<script${attrs}>`,
...scriptLines,
...frontmatterExportsLines,
excerptExportsLine,
"export default { data() { return { frontmatter } } }",
"</script>"
] : [
`<script setup${attrs}>`,
...scriptLines,
"</script>",
...frontmatterExportsLines.length || excerptExportsLine ? [
`<script${attrs}>`,
...frontmatterExportsLines,
excerptExportsLine,
"</script>"
] : []
];
const code = [
`<template>${html}</template>`,
...scripts.map((i) => i.trim()).filter(Boolean),
...customBlocks.blocks
].join("\n");
return {
code,
map: { mappings: "" }
};
};
}
// src/core/utils.ts
import { createRequire } from "node:module";
var _require = typeof __require === "undefined" ? createRequire(import.meta.url) : __require;
function getVueVersion(defaultVersion = "3.2.0") {
try {
let v = _require("vue");
if (v.default)
v = v.default;
return v.version || defaultVersion;
} catch {
return defaultVersion;
}
}
// src/core/options.ts
function resolveOptions(userOptions) {
const defaultOptions = {
headEnabled: false,
headField: "",
frontmatter: true,
excerpt: false,
exposeFrontmatter: true,
exposeExcerpt: false,
exportFrontmatter: true,
escapeCodeTagInterpolation: true,
customSfcBlocks: ["route", "i18n", "style"],
componentOptions: {},
frontmatterOptions: {},
markdownItOptions: {},
markdownItUses: [],
markdownItSetup: () => {
},
wrapperComponent: null,
transforms: {},
vueVersion: userOptions.vueVersion || getVueVersion(),
wrapperClasses: "markdown-body",
include: null,
exclude: null,
frontmatterPreprocess: (frontmatter, options2, _id, defaults) => {
return {
head: defaults(frontmatter, options2),
frontmatter
};
}
};
const options = {
...defaultOptions,
...userOptions
};
return options;
}
// src/index.ts
var cssIdRE = /\.(css|postcss|sass|scss|less|stylus|styl)($|\?)/;
var unpluginFactory = (userOptions = {}) => {
const options = resolveOptions(userOptions);
const markdownToVue = createMarkdown(options);
const filter = createFilter(
userOptions.include || /\.md$|\.md\?vue/,
userOptions.exclude || cssIdRE
);
return {
name: "unplugin-vue-markdown",
enforce: "pre",
transformInclude(id) {
return filter(id);
},
async transform(raw, id) {
try {
return await markdownToVue(id, raw);
} catch (e) {
this.error(e);
}
},
vite: {
async handleHotUpdate(ctx) {
if (!filter(ctx.file))
return;
const defaultRead = ctx.read;
ctx.read = async function() {
return (await markdownToVue(ctx.file, await defaultRead())).code;
};
}
}
};
};
var src_default = /* @__PURE__ */ createUnplugin(unpluginFactory);
export {
unpluginFactory,
src_default
};
@@ -0,0 +1,347 @@
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
var _chunkPMBWJGAHcjs = require('./chunk-PMBWJGAH.cjs');
// src/index.ts
var _pluginutils = require('@rollup/pluginutils');
var _unplugin = require('unplugin');
// node_modules/.pnpm/@antfu+utils@0.7.10/node_modules/@antfu/utils/dist/index.mjs
function toArray(array) {
array = _nullishCoalesce(array, () => ( []));
return Array.isArray(array) ? array : [array];
}
function uniq(array) {
return Array.from(new Set(array));
}
var VOID = Symbol("p-void");
// src/core/markdown.ts
var _plugincomponent = require('@mdit-vue/plugin-component');
var _pluginfrontmatter = require('@mdit-vue/plugin-frontmatter');
var _markdownitasync = require('markdown-it-async'); var _markdownitasync2 = _interopRequireDefault(_markdownitasync);
// src/core/head.ts
var headProperties = [
"title",
"meta",
"link",
"base",
"style",
"script",
"htmlAttrs",
"bodyAttrs"
];
function preprocessHead(frontmatter, options) {
if (!options.headEnabled)
return frontmatter;
const head = options.headField ? frontmatter[options.headField] || {} : frontmatter;
const meta = head.meta = head.meta || [];
if (head.title) {
if (!meta.find((i) => i.property === "og:title"))
meta.push({ property: "og:title", content: head.title });
if (!meta.find((i) => i.name === "twitter:title"))
meta.push({ name: "twitter:title", content: head.title });
}
if (head.description) {
if (!meta.find((i) => i.name === "description"))
meta.push({ name: "description", content: head.description });
if (!meta.find((i) => i.property === "og:description"))
meta.push({ property: "og:description", content: head.description });
if (!meta.find((i) => i.name === "twitter:description"))
meta.push({ name: "twitter:description", content: head.description });
}
if (head.image) {
if (!meta.find((i) => i.property === "og:image"))
meta.push({ property: "og:image", content: head.image });
if (!meta.find((i) => i.name === "twitter:image"))
meta.push({ name: "twitter:image", content: head.image });
if (!meta.find((i) => i.property === "twitter:card"))
meta.push({ name: "twitter:card", content: "summary_large_image" });
}
const result = {};
for (const [key, value] of Object.entries(head)) {
if (headProperties.includes(key))
result[key] = value;
}
return Object.entries(result).length === 0 ? null : result;
}
// src/core/markdown.ts
var scriptSetupRE = /<\s*script([^>]*)\bsetup\b([^>]*)>([\s\S]*)<\/script>/g;
var defineExposeRE = /defineExpose\s*\(/g;
var EXPORTS_KEYWORDS = [
"class",
"default",
"export",
"function",
"import",
"let",
"var",
"const",
"from",
"as",
"return",
"if",
"else",
"switch",
"case",
"break",
"for",
"while",
"do"
];
function extractScriptSetup(html) {
const scripts = [];
html = html.replace(scriptSetupRE, (_, attr1, attr2, code) => {
scripts.push({
code,
attr: `${attr1} ${attr2}`.trim()
});
return "";
});
return { html, scripts };
}
function extractCustomBlock(html, options) {
const blocks = [];
for (const tag of options.customSfcBlocks) {
html = html.replace(new RegExp(`<${tag}[^>]*\\b[^>]*>[^<]*<\\/${tag}>`, "gm"), (code) => {
blocks.push(code);
return "";
});
}
return { html, blocks };
}
function createMarkdown(options) {
const isVue2 = options.vueVersion.startsWith("2.");
const markdown = _markdownitasync2.default.call(void 0, {
html: true,
linkify: true,
typographer: true,
...options.markdownItOptions
});
markdown.use(_plugincomponent.componentPlugin, options.componentOptions);
if (options.frontmatter || options.excerpt) {
markdown.use(_pluginfrontmatter.frontmatterPlugin, {
...options.frontmatterOptions,
grayMatterOptions: {
excerpt: options.excerpt,
...options.frontmatterOptions.grayMatterOptions
}
});
}
markdown.linkify.set({ fuzzyLink: false });
options.markdownItUses.forEach((e) => {
const [plugin, options2] = toArray(e);
markdown.use(plugin, options2);
});
const setupPromise = (async () => {
await options.markdownItSetup(markdown);
})();
return async (id, raw) => {
await setupPromise;
const {
wrapperClasses,
wrapperComponent,
transforms,
headEnabled,
frontmatterPreprocess
} = options;
raw = raw.trimStart();
raw = _nullishCoalesce(_optionalChain([transforms, 'access', _2 => _2.before, 'optionalCall', _3 => _3(raw, id)]), () => ( raw));
const env = { id };
let html = await markdown.renderAsync(raw, env);
const { excerpt = "", frontmatter: data = null } = env;
const wrapperClassesResolved = toArray(
typeof wrapperClasses === "function" ? wrapperClasses(id, raw) : wrapperClasses
).filter(Boolean).join(" ");
if (wrapperClassesResolved)
html = `<div class="${wrapperClassesResolved}">${html}</div>`;
else
html = `<div>${html}</div>`;
const wrapperComponentName = typeof wrapperComponent === "function" ? wrapperComponent(id, raw) : wrapperComponent;
if (wrapperComponentName) {
const attrs2 = [
options.frontmatter && ':frontmatter="frontmatter"',
options.excerpt && ':excerpt="excerpt"'
].filter(Boolean).join(" ");
html = `<${wrapperComponentName} ${attrs2}>${html}</${wrapperComponentName}>`;
}
html = _nullishCoalesce(_optionalChain([transforms, 'access', _4 => _4.after, 'optionalCall', _5 => _5(html, id)]), () => ( html));
if (options.escapeCodeTagInterpolation) {
html = html.replace(/<code(.*?)>/g, "<code$1 v-pre>");
}
const hoistScripts = extractScriptSetup(html);
html = hoistScripts.html;
const customBlocks = extractCustomBlock(html, options);
html = customBlocks.html;
const scriptLines = [];
let frontmatterExportsLines = [];
let excerptExportsLine = "";
let excerptKeyOverlapping = false;
function hasExplicitExports() {
return defineExposeRE.test(hoistScripts.scripts.map((i) => i.code).join(""));
}
if (options.frontmatter) {
if (options.excerpt && data) {
if (data.excerpt !== void 0)
excerptKeyOverlapping = true;
data.excerpt = excerpt;
}
const { head, frontmatter } = frontmatterPreprocess(data || {}, options, id, preprocessHead);
if (options.excerpt && !excerptKeyOverlapping && frontmatter.excerpt !== void 0)
delete frontmatter.excerpt;
scriptLines.push(`const frontmatter = ${JSON.stringify(frontmatter)}`);
if (options.exportFrontmatter) {
frontmatterExportsLines = Object.entries(frontmatter).map(([key, value]) => {
if (EXPORTS_KEYWORDS.includes(key))
key = `_${key}`;
return `export const ${key} = ${JSON.stringify(value)}`;
});
}
if (!isVue2 && options.exposeFrontmatter && !hasExplicitExports())
scriptLines.push("defineExpose({ frontmatter })");
if (!isVue2 && headEnabled && head) {
if (headEnabled === "vueuse")
throw new Error("unplugin-vue-markdown no longer supports @vueuse/head. Change `headEnabled` to `true` and install `@unhead/vue` instead.");
scriptLines.push(`const head = ${JSON.stringify(head)}`);
scriptLines.unshift(`import { useHead } from "@unhead/vue"`);
scriptLines.push("useHead(head)");
}
scriptLines.push(..._optionalChain([transforms, 'access', _6 => _6.extraScripts, 'optionalCall', _7 => _7(frontmatter, id)]) || []);
}
if (options.excerpt) {
scriptLines.push(`const excerpt = ${JSON.stringify(excerpt)}`);
if (!excerptKeyOverlapping)
excerptExportsLine = `export const excerpt = ${JSON.stringify(excerpt)}
`;
if (!isVue2 && options.exposeExcerpt && !hasExplicitExports())
scriptLines.push("defineExpose({ excerpt })");
}
scriptLines.push(...hoistScripts.scripts.map((i) => i.code));
let attrs = uniq(hoistScripts.scripts.map((i) => i.attr)).join(" ").trim();
if (attrs)
attrs = ` ${attrs}`;
const scripts = isVue2 ? [
`<script${attrs}>`,
...scriptLines,
...frontmatterExportsLines,
excerptExportsLine,
"export default { data() { return { frontmatter } } }",
"</script>"
] : [
`<script setup${attrs}>`,
...scriptLines,
"</script>",
...frontmatterExportsLines.length || excerptExportsLine ? [
`<script${attrs}>`,
...frontmatterExportsLines,
excerptExportsLine,
"</script>"
] : []
];
const code = [
`<template>${html}</template>`,
...scripts.map((i) => i.trim()).filter(Boolean),
...customBlocks.blocks
].join("\n");
return {
code,
map: { mappings: "" }
};
};
}
// src/core/utils.ts
var _module = require('module');
var _require = typeof _chunkPMBWJGAHcjs.__require === "undefined" ? _module.createRequire.call(void 0, _chunkPMBWJGAHcjs.importMetaUrl) : _chunkPMBWJGAHcjs.__require;
function getVueVersion(defaultVersion = "3.2.0") {
try {
let v = _require("vue");
if (v.default)
v = v.default;
return v.version || defaultVersion;
} catch (e2) {
return defaultVersion;
}
}
// src/core/options.ts
function resolveOptions(userOptions) {
const defaultOptions = {
headEnabled: false,
headField: "",
frontmatter: true,
excerpt: false,
exposeFrontmatter: true,
exposeExcerpt: false,
exportFrontmatter: true,
escapeCodeTagInterpolation: true,
customSfcBlocks: ["route", "i18n", "style"],
componentOptions: {},
frontmatterOptions: {},
markdownItOptions: {},
markdownItUses: [],
markdownItSetup: () => {
},
wrapperComponent: null,
transforms: {},
vueVersion: userOptions.vueVersion || getVueVersion(),
wrapperClasses: "markdown-body",
include: null,
exclude: null,
frontmatterPreprocess: (frontmatter, options2, _id, defaults) => {
return {
head: defaults(frontmatter, options2),
frontmatter
};
}
};
const options = {
...defaultOptions,
...userOptions
};
return options;
}
// src/index.ts
var cssIdRE = /\.(css|postcss|sass|scss|less|stylus|styl)($|\?)/;
var unpluginFactory = (userOptions = {}) => {
const options = resolveOptions(userOptions);
const markdownToVue = createMarkdown(options);
const filter = _pluginutils.createFilter.call(void 0,
userOptions.include || /\.md$|\.md\?vue/,
userOptions.exclude || cssIdRE
);
return {
name: "unplugin-vue-markdown",
enforce: "pre",
transformInclude(id) {
return filter(id);
},
async transform(raw, id) {
try {
return await markdownToVue(id, raw);
} catch (e) {
this.error(e);
}
},
vite: {
async handleHotUpdate(ctx) {
if (!filter(ctx.file))
return;
const defaultRead = ctx.read;
ctx.read = async function() {
return (await markdownToVue(ctx.file, await defaultRead())).code;
};
}
}
};
};
var src_default = /* @__PURE__ */ _unplugin.createUnplugin.call(void 0, unpluginFactory);
exports.unpluginFactory = unpluginFactory; exports.src_default = src_default;
@@ -0,0 +1,15 @@
"use strict";Object.defineProperty(exports, "__esModule", {value: true});var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
// node_modules/.pnpm/tsup@8.3.5_jiti@2.4.1_postcss@8.4.49_tsx@4.19.2_typescript@5.7.2_yaml@2.6.1/node_modules/tsup/assets/cjs_shims.js
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
exports.__require = __require; exports.importMetaUrl = importMetaUrl;
+12
View File
@@ -0,0 +1,12 @@
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkNXNBWILFcjs = require('./chunk-NXNBWILF.cjs');
require('./chunk-PMBWJGAH.cjs');
// src/esbuild.ts
var _unplugin = require('unplugin');
var esbuild_default = _unplugin.createEsbuildPlugin.call(void 0, _chunkNXNBWILFcjs.unpluginFactory);
module.exports = esbuild_default;
exports.default = module.exports;
+11
View File
@@ -0,0 +1,11 @@
import * as esbuild from 'esbuild';
import { Options } from './types.cjs';
import '@mdit-vue/plugin-component';
import '@mdit-vue/plugin-frontmatter';
import '@mdit-vue/types';
import '@rollup/pluginutils';
import 'markdown-it-async';
declare const _default: (options: Options) => esbuild.Plugin;
export { _default as default };
+11
View File
@@ -0,0 +1,11 @@
import * as esbuild from 'esbuild';
import { Options } from './types.js';
import '@mdit-vue/plugin-component';
import '@mdit-vue/plugin-frontmatter';
import '@mdit-vue/types';
import '@rollup/pluginutils';
import 'markdown-it-async';
declare const _default: (options: Options) => esbuild.Plugin;
export { _default as default };
+10
View File
@@ -0,0 +1,10 @@
import {
unpluginFactory
} from "./chunk-GIGIIUWE.js";
// src/esbuild.ts
import { createEsbuildPlugin } from "unplugin";
var esbuild_default = createEsbuildPlugin(unpluginFactory);
export {
esbuild_default as default
};
+10
View File
@@ -0,0 +1,10 @@
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkNXNBWILFcjs = require('./chunk-NXNBWILF.cjs');
require('./chunk-PMBWJGAH.cjs');
module.exports = _chunkNXNBWILFcjs.src_default; exports.unpluginFactory = _chunkNXNBWILFcjs.unpluginFactory;
exports.default = module.exports;
+13
View File
@@ -0,0 +1,13 @@
import * as unplugin from 'unplugin';
import { UnpluginFactory } from 'unplugin';
import { Options } from './types.cjs';
import '@mdit-vue/plugin-component';
import '@mdit-vue/plugin-frontmatter';
import '@mdit-vue/types';
import '@rollup/pluginutils';
import 'markdown-it-async';
declare const unpluginFactory: UnpluginFactory<Options>;
declare const _default: unplugin.UnpluginInstance<Options, boolean>;
export { _default as default, unpluginFactory };
+13
View File
@@ -0,0 +1,13 @@
import * as unplugin from 'unplugin';
import { UnpluginFactory } from 'unplugin';
import { Options } from './types.js';
import '@mdit-vue/plugin-component';
import '@mdit-vue/plugin-frontmatter';
import '@mdit-vue/types';
import '@rollup/pluginutils';
import 'markdown-it-async';
declare const unpluginFactory: UnpluginFactory<Options>;
declare const _default: unplugin.UnpluginInstance<Options, boolean>;
export { _default as default, unpluginFactory };
+8
View File
@@ -0,0 +1,8 @@
import {
src_default,
unpluginFactory
} from "./chunk-GIGIIUWE.js";
export {
src_default as default,
unpluginFactory
};
+12
View File
@@ -0,0 +1,12 @@
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkNXNBWILFcjs = require('./chunk-NXNBWILF.cjs');
require('./chunk-PMBWJGAH.cjs');
// src/rollup.ts
var _unplugin = require('unplugin');
var rollup_default = _unplugin.createRollupPlugin.call(void 0, _chunkNXNBWILFcjs.unpluginFactory);
module.exports = rollup_default;
exports.default = module.exports;
+11
View File
@@ -0,0 +1,11 @@
import * as rollup from 'rollup';
import { Options } from './types.cjs';
import '@mdit-vue/plugin-component';
import '@mdit-vue/plugin-frontmatter';
import '@mdit-vue/types';
import '@rollup/pluginutils';
import 'markdown-it-async';
declare const _default: (options: Options) => rollup.Plugin<any> | rollup.Plugin<any>[];
export { _default as default };
+11
View File
@@ -0,0 +1,11 @@
import * as rollup from 'rollup';
import { Options } from './types.js';
import '@mdit-vue/plugin-component';
import '@mdit-vue/plugin-frontmatter';
import '@mdit-vue/types';
import '@rollup/pluginutils';
import 'markdown-it-async';
declare const _default: (options: Options) => rollup.Plugin<any> | rollup.Plugin<any>[];
export { _default as default };
+10
View File
@@ -0,0 +1,10 @@
import {
unpluginFactory
} from "./chunk-GIGIIUWE.js";
// src/rollup.ts
import { createRollupPlugin } from "unplugin";
var rollup_default = createRollupPlugin(unpluginFactory);
export {
rollup_default as default
};
+12
View File
@@ -0,0 +1,12 @@
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkNXNBWILFcjs = require('./chunk-NXNBWILF.cjs');
require('./chunk-PMBWJGAH.cjs');
// src/rspack.ts
var _unplugin = require('unplugin');
var rspack_default = _unplugin.createRspackPlugin.call(void 0, _chunkNXNBWILFcjs.unpluginFactory);
module.exports = rspack_default;
exports.default = module.exports;
+10
View File
@@ -0,0 +1,10 @@
import { Options } from './types.cjs';
import '@mdit-vue/plugin-component';
import '@mdit-vue/plugin-frontmatter';
import '@mdit-vue/types';
import '@rollup/pluginutils';
import 'markdown-it-async';
declare const _default: (options: Options) => RspackPluginInstance;
export { _default as default };
+10
View File
@@ -0,0 +1,10 @@
import { Options } from './types.js';
import '@mdit-vue/plugin-component';
import '@mdit-vue/plugin-frontmatter';
import '@mdit-vue/types';
import '@rollup/pluginutils';
import 'markdown-it-async';
declare const _default: (options: Options) => RspackPluginInstance;
export { _default as default };
+10
View File
@@ -0,0 +1,10 @@
import {
unpluginFactory
} from "./chunk-GIGIIUWE.js";
// src/rspack.ts
import { createRspackPlugin } from "unplugin";
var rspack_default = createRspackPlugin(unpluginFactory);
export {
rspack_default as default
};
+2
View File
@@ -0,0 +1,2 @@
"use strict";require('./chunk-PMBWJGAH.cjs');
exports.default = module.exports;
+173
View File
@@ -0,0 +1,173 @@
import { ComponentPluginOptions } from '@mdit-vue/plugin-component';
import { FrontmatterPluginOptions } from '@mdit-vue/plugin-frontmatter';
import { MarkdownItEnv } from '@mdit-vue/types';
import { FilterPattern } from '@rollup/pluginutils';
import { MarkdownItAsyncOptions, PluginSimple, PluginWithOptions, MarkdownItAsync } from 'markdown-it-async';
declare function preprocessHead<T extends Frontmatter>(frontmatter: T, options: ResolvedOptions): any;
/** a `<meta />` property in HTML is defined with the following name/values */
interface MetaProperty {
key?: string;
/**
* the "name" property used by Facebook and other providers who
* use the Opengraph standards
*/
property?: string;
/**
* used by google to identify the "name" of the name/value pair
*/
itemprop?: string;
/**
* used by Twitter to indicate the "name" field in a meta properties
* name/value pairing
*/
name?: string;
/**
* The value of the meta property
*/
content?: any;
[key: string]: unknown;
}
/**
* Frontmatter content is represented as key/value dictionary
*/
interface Frontmatter {
title?: string;
name?: string;
description?: string;
meta?: MetaProperty[];
[key: string]: unknown;
}
interface Options {
/**
* Explicitly set the Vue version
*
* @default auto detected
*/
vueVersion?: string;
/**
* Enable head support, need to install @unhead/vue and register to App in main.js
*
* @default true
*/
headEnabled?: boolean;
/**
* The head field in frontmatter used to be used for @unhead/vue
*
* When an empty string is passed, it will use the root properties of the frontmatter
*
* @default ''
*/
headField?: string;
/**
* Parse for frontmatter
*
* @default true
*/
frontmatter?: boolean;
/**
* Parse for excerpt
*
* If `true`, it will be passed to `frontmatterPreprocess` as `frontmatter.excerpt`, replacing the `excerpt` key in frontmatter, if there's any
*
* @default false
*/
excerpt?: boolean;
/**
* Remove custom SFC block
*
* @default ['route', 'i18n']
*/
customSfcBlocks?: string[];
/**
* Options passed to [@mdit-vue/plugin-component](https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-component)
*/
componentOptions?: ComponentPluginOptions;
/**
* Options passed to [@mdit-vue/plugin-frontmatter](https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-frontmatter)
*/
frontmatterOptions?: FrontmatterPluginOptions;
/**
* Custom function to provide defaults to the frontmatter and
* move certain attributes into the "meta" category.
*
* Note: _overriding this will remove built-in functionality setting
* "meta" properties and the built-in "head" support. Do this only
* if you know what you're doing._
*/
frontmatterPreprocess?: (frontmatter: Frontmatter, options: ResolvedOptions, id: string, defaultHeadProcess: typeof preprocessHead) => {
head: Record<string, any>;
frontmatter: Frontmatter;
};
/**
* Expose frontmatter via expose API
*
* @default true
*/
exposeFrontmatter?: boolean;
/**
* Expose excerpt via expose API
*
* @default false
*/
exposeExcerpt?: boolean;
/**
* Export frontmatter in component module
*
* @default true
*/
exportFrontmatter?: boolean;
/**
* Add `v-pre` to `<code>` tag to escape curly brackets interpolation
*
* @see https://github.com/unplugin/unplugin-vue-markdown/issues/14
* @default true
*/
escapeCodeTagInterpolation?: boolean;
/**
* Options passed to Markdown It
*/
markdownItOptions?: MarkdownItAsyncOptions;
/**
* Plugins for Markdown It
*/
markdownItUses?: (PluginSimple | [PluginSimple | PluginWithOptions<any>, any] | any)[];
/**
* A function providing the Markdown It instance gets the ability to apply custom
* settings/plugins
*/
markdownItSetup?: (MarkdownIt: MarkdownItAsync) => void | Promise<void>;
/**
* Class names for wrapper div
*
* @default 'markdown-body'
*/
wrapperClasses?: string | string[] | undefined | null | ((id: string, code: string) => string | string[] | undefined | null);
/**
* Component name to wrapper with
*
* @default undefined
*/
wrapperComponent?: string | undefined | null | ((id: string, code: string) => string | undefined | null);
/**
* Custom tranformations apply before and after the markdown transformation
*/
transforms?: {
before?: (code: string, id: string) => string;
after?: (code: string, id: string) => string;
/**
* Return extra code to be injected into the `<script>` tag
*/
extraScripts?: (frontmatter: Record<string, any>, id: string) => string[];
};
include?: FilterPattern;
exclude?: FilterPattern;
}
interface ResolvedOptions extends Required<Options> {
}
interface MarkdownEnv extends MarkdownItEnv {
id: string;
}
export type { Frontmatter, MarkdownEnv, MetaProperty, Options, ResolvedOptions };
+173
View File
@@ -0,0 +1,173 @@
import { ComponentPluginOptions } from '@mdit-vue/plugin-component';
import { FrontmatterPluginOptions } from '@mdit-vue/plugin-frontmatter';
import { MarkdownItEnv } from '@mdit-vue/types';
import { FilterPattern } from '@rollup/pluginutils';
import { MarkdownItAsyncOptions, PluginSimple, PluginWithOptions, MarkdownItAsync } from 'markdown-it-async';
declare function preprocessHead<T extends Frontmatter>(frontmatter: T, options: ResolvedOptions): any;
/** a `<meta />` property in HTML is defined with the following name/values */
interface MetaProperty {
key?: string;
/**
* the "name" property used by Facebook and other providers who
* use the Opengraph standards
*/
property?: string;
/**
* used by google to identify the "name" of the name/value pair
*/
itemprop?: string;
/**
* used by Twitter to indicate the "name" field in a meta properties
* name/value pairing
*/
name?: string;
/**
* The value of the meta property
*/
content?: any;
[key: string]: unknown;
}
/**
* Frontmatter content is represented as key/value dictionary
*/
interface Frontmatter {
title?: string;
name?: string;
description?: string;
meta?: MetaProperty[];
[key: string]: unknown;
}
interface Options {
/**
* Explicitly set the Vue version
*
* @default auto detected
*/
vueVersion?: string;
/**
* Enable head support, need to install @unhead/vue and register to App in main.js
*
* @default true
*/
headEnabled?: boolean;
/**
* The head field in frontmatter used to be used for @unhead/vue
*
* When an empty string is passed, it will use the root properties of the frontmatter
*
* @default ''
*/
headField?: string;
/**
* Parse for frontmatter
*
* @default true
*/
frontmatter?: boolean;
/**
* Parse for excerpt
*
* If `true`, it will be passed to `frontmatterPreprocess` as `frontmatter.excerpt`, replacing the `excerpt` key in frontmatter, if there's any
*
* @default false
*/
excerpt?: boolean;
/**
* Remove custom SFC block
*
* @default ['route', 'i18n']
*/
customSfcBlocks?: string[];
/**
* Options passed to [@mdit-vue/plugin-component](https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-component)
*/
componentOptions?: ComponentPluginOptions;
/**
* Options passed to [@mdit-vue/plugin-frontmatter](https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-frontmatter)
*/
frontmatterOptions?: FrontmatterPluginOptions;
/**
* Custom function to provide defaults to the frontmatter and
* move certain attributes into the "meta" category.
*
* Note: _overriding this will remove built-in functionality setting
* "meta" properties and the built-in "head" support. Do this only
* if you know what you're doing._
*/
frontmatterPreprocess?: (frontmatter: Frontmatter, options: ResolvedOptions, id: string, defaultHeadProcess: typeof preprocessHead) => {
head: Record<string, any>;
frontmatter: Frontmatter;
};
/**
* Expose frontmatter via expose API
*
* @default true
*/
exposeFrontmatter?: boolean;
/**
* Expose excerpt via expose API
*
* @default false
*/
exposeExcerpt?: boolean;
/**
* Export frontmatter in component module
*
* @default true
*/
exportFrontmatter?: boolean;
/**
* Add `v-pre` to `<code>` tag to escape curly brackets interpolation
*
* @see https://github.com/unplugin/unplugin-vue-markdown/issues/14
* @default true
*/
escapeCodeTagInterpolation?: boolean;
/**
* Options passed to Markdown It
*/
markdownItOptions?: MarkdownItAsyncOptions;
/**
* Plugins for Markdown It
*/
markdownItUses?: (PluginSimple | [PluginSimple | PluginWithOptions<any>, any] | any)[];
/**
* A function providing the Markdown It instance gets the ability to apply custom
* settings/plugins
*/
markdownItSetup?: (MarkdownIt: MarkdownItAsync) => void | Promise<void>;
/**
* Class names for wrapper div
*
* @default 'markdown-body'
*/
wrapperClasses?: string | string[] | undefined | null | ((id: string, code: string) => string | string[] | undefined | null);
/**
* Component name to wrapper with
*
* @default undefined
*/
wrapperComponent?: string | undefined | null | ((id: string, code: string) => string | undefined | null);
/**
* Custom tranformations apply before and after the markdown transformation
*/
transforms?: {
before?: (code: string, id: string) => string;
after?: (code: string, id: string) => string;
/**
* Return extra code to be injected into the `<script>` tag
*/
extraScripts?: (frontmatter: Record<string, any>, id: string) => string[];
};
include?: FilterPattern;
exclude?: FilterPattern;
}
interface ResolvedOptions extends Required<Options> {
}
interface MarkdownEnv extends MarkdownItEnv {
id: string;
}
export type { Frontmatter, MarkdownEnv, MetaProperty, Options, ResolvedOptions };
View File
+12
View File
@@ -0,0 +1,12 @@
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkNXNBWILFcjs = require('./chunk-NXNBWILF.cjs');
require('./chunk-PMBWJGAH.cjs');
// src/vite.ts
var _unplugin = require('unplugin');
var vite_default = _unplugin.createVitePlugin.call(void 0, _chunkNXNBWILFcjs.unpluginFactory);
module.exports = vite_default;
exports.default = module.exports;
+11
View File
@@ -0,0 +1,11 @@
import * as vite from 'vite';
import { Options } from './types.cjs';
import '@mdit-vue/plugin-component';
import '@mdit-vue/plugin-frontmatter';
import '@mdit-vue/types';
import '@rollup/pluginutils';
import 'markdown-it-async';
declare const _default: (options: Options) => vite.Plugin<any> | vite.Plugin<any>[];
export { _default as default };
+11
View File
@@ -0,0 +1,11 @@
import * as vite from 'vite';
import { Options } from './types.js';
import '@mdit-vue/plugin-component';
import '@mdit-vue/plugin-frontmatter';
import '@mdit-vue/types';
import '@rollup/pluginutils';
import 'markdown-it-async';
declare const _default: (options: Options) => vite.Plugin<any> | vite.Plugin<any>[];
export { _default as default };
+10
View File
@@ -0,0 +1,10 @@
import {
unpluginFactory
} from "./chunk-GIGIIUWE.js";
// src/vite.ts
import { createVitePlugin } from "unplugin";
var vite_default = createVitePlugin(unpluginFactory);
export {
vite_default as default
};
+12
View File
@@ -0,0 +1,12 @@
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkNXNBWILFcjs = require('./chunk-NXNBWILF.cjs');
require('./chunk-PMBWJGAH.cjs');
// src/webpack.ts
var _unplugin = require('unplugin');
var webpack_default = _unplugin.createWebpackPlugin.call(void 0, _chunkNXNBWILFcjs.unpluginFactory);
module.exports = webpack_default;
exports.default = module.exports;
+3
View File
@@ -0,0 +1,3 @@
declare const _default: any;
export { _default as default };
+3
View File
@@ -0,0 +1,3 @@
declare const _default: any;
export { _default as default };
+10
View File
@@ -0,0 +1,10 @@
import {
unpluginFactory
} from "./chunk-GIGIIUWE.js";
// src/webpack.ts
import { createWebpackPlugin } from "unplugin";
var webpack_default = createWebpackPlugin(unpluginFactory);
export {
webpack_default as default
};
+15
View File
@@ -0,0 +1,15 @@
// @ts-check
import antfu from '@antfu/eslint-config'
export default antfu(
{
ignores: [
// eslint ignore globs here
],
},
{
rules: {
// overrides
},
},
)
+17
View File
@@ -0,0 +1,17 @@
<script setup lang='ts'>
import { ref } from 'vue'
import ComponentA from './README.md'
const current = ref()
</script>
<template>
<router-view v-slot="{ Component }">
<component :is="Component" ref="current" />
</router-view>
<div v-if="current">
accessing frontmatter from outside via expose API:
{{ current.frontmatter }}
</div>
<ComponentA style="padding: 20px 40px" />
</template>
@@ -0,0 +1,26 @@
<script setup lang="ts">
import { ref } from 'vue'
const props = defineProps<{ init?: number }>()
const counter = ref(props.init || 0)
</script>
<template>
<div class="counter">
<div>Counter: {{ counter }}</div>
<button @click="counter += 1">
Inc
</button>
<button @click="counter -= 1">
Dec
</button>
</div>
</template>
<style scoped>
.counter {
border: 2px dashed #3eaf7c;
padding: 5px 10px 10px 10px;
}
</style>
@@ -0,0 +1,26 @@
<script setup lang="ts">
import { ref } from 'vue'
const props = defineProps<{ init?: number }>()
const counter = ref(props.init || 0)
</script>
<template>
<div class="counter">
<div>Counter 2: {{ counter }}</div>
<button @click="counter += 1">
Inc
</button>
<button @click="counter -= 1">
Dec
</button>
</div>
</template>
<style scoped>
.counter {
border: 2px dashed skyblue;
padding: 5px 10px 10px 10px;
}
</style>
@@ -0,0 +1,341 @@
# unplugin-vue-markdown
[![NPM version](https://img.shields.io/npm/v/unplugin-vue-markdown?color=a1b858)](https://www.npmjs.com/package/unplugin-vue-markdown)
Compile Markdown to Vue component.
- 📚 Use Markdown as Vue components.
- 💚 Use Vue components in Markdown.
- 🔌 Supports Vite, Webpack, Vue CLI and more, powered by [unplugin](https://github.com/unjs/unplugin).
- ⚡️ The same transformation as [VitePress](https://vitepress.vuejs.org/).
## Install
```bash
npm i unplugin-vue-markdown
```
<details>
<summary>Vite</summary><br>
```ts
// vite.config.ts
import Vue from '@vitejs/plugin-vue'
import Markdown from 'unplugin-vue-markdown/vite'
export default defineConfig({
plugins: [
Vue({
include: [/\.vue$/, /\.md$/], // <-- allows Vue to compile Markdown files
}),
Markdown({ /* options */ }),
],
})
```
Example: [`examples/vite`](./examples/vite/)
<br>
</details>
<details>
<summary>Webpack</summary><br>
```ts
// webpack.config.js
const Markdown = require('unplugin-vue-markdown/webpack')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
/* ... */
module: {
rules: [
// ... other rules
{
test: /\.(vue|md)$/,
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin(),
Markdown({ /* options */ })
]
}
```
<br>
</details>
<details>
<summary>Vue CLI</summary><br>
```ts
// vue.config.js
const Markdown = require('unplugin-vue-markdown/webpack')
module.exports = {
parallel: false, // Disable thread-loader which will cause errors, we are still investigating the root cause
chainWebpack: (config) => {
config.module
.rule('vue')
.test(/\.(vue|md)$/) // <-- allows Vue to compile Markdown files
config
.plugin('markdown')
.use(Markdown({
markdownItUses: [
prism,
],
}))
},
}
```
Example: [`examples/vue-cli`](./examples/vue-cli/)
<br>
</details>
## Import Markdown as Vue components
```html
<template>
<HelloWorld />
</template>
<script>
import HelloWorld from './README.md'
export default {
components: {
HelloWorld,
},
}
</script>
```
## Use Vue Components inside Markdown
You can even use Vue components inside your markdown, for example
```html
<Counter :init='5'/>
```
<Counter :init='5'/>
Note you can either register the components globally, or use the `<script setup>` tag to register them locally.
```ts
import { createApp } from 'vue'
import App from './App.vue'
import Counter from './Counter.vue'
const app = createApp(App)
// register global
app.component('Counter', Counter) // <--
app.mount()
```
```html
<script setup>
import { Counter } from './Counter.vue'
</script>
<Counter :init='5'/>
```
Or you can use [`unplugin-vue-components`](#work-with-unplugin-vue-components) for auto components registration.
## Frontmatter
Frontmatter will be parsed and inject into Vue's instance data `frontmatter` field.
For example:
```md
---
name: My Cool App
---
# Hello World
This is {{frontmatter.name}}
```
Will be rendered as
```html
<h1>Hello World</h1>
<p>This is My Cool App</p>
```
It will also be passed to the wrapper component's props if you have set `wrapperComponent` option.
## Document head and meta
To manage document head and meta, you would need to install [`@unhead/vue`](https://unhead.harlanzw.com/integrations/vue/setup) and do some setup.
```bash
npm i @unhead/vue
```
```js
// vite.config.js
import Vue from '@vitejs/plugin-vue'
import Markdown from 'unplugin-vue-markdown/vite'
export default {
plugins: [
Vue({
include: [/\.vue$/, /\.md$/],
}),
Markdown({
headEnabled: true // <--
})
]
}
```
```js
// src/main.js
import { createHead } from '@unhead/vue' // <--
import { createApp } from 'vue'
const app = createApp(App)
const head = createHead() // <--
app.use(head) // <--
```
Then you can use frontmatter to control the head. For example:
```yaml
---
title: My Cool App
meta:
- name: description
content: Hello World
---
```
For more options available, please refer to [`@unhead/vue`'s docs](https://unhead.harlanzw.com/integrations/vue/setup).
## Options
`unplugin-vue-markdown` uses [`markdown-it`](https://github.com/markdown-it/markdown-it) under the hood, see [`markdown-it`'s docs](https://markdown-it.github.io/markdown-it/) for more details
```ts
// vite.config.js
import MarkdownItAnchor from 'markdown-it-anchor'
import MarkdownItPrism from 'markdown-it-prism'
import Markdown from 'unplugin-vue-markdown/vite'
export default {
plugins: [
Markdown({
// default options passed to markdown-it
// see: https://markdown-it.github.io/markdown-it/
markdownItOptions: {
html: true,
linkify: true,
typographer: true,
},
// A function providing the Markdown It instance gets the ability to apply custom settings/plugins
markdownItSetup(md) {
// for example
md.use(MarkdownItAnchor)
md.use(MarkdownItPrism)
},
// Class names for the wrapper div
wrapperClasses: 'markdown-body'
})
],
}
```
See [the tsdoc](./src/types.ts) for more advanced options
## Example
See the [/examples](./examples).
Or the pre-configured Markdown template [Vitesse](https://github.com/antfu/vitesse).
## Integrations
### Work with [vite-plugin-pages](https://github.com/hannoeru/vite-plugin-pages)
```ts
import Vue from '@vitejs/plugin-vue'
import Markdown from 'unplugin-vue-markdown/vite'
import Pages from 'vite-plugin-pages'
export default {
plugins: [
Vue({
include: [/\.vue$/, /\.md$/],
}),
Pages({
extensions: ['vue', 'md'],
}),
Markdown()
],
}
```
Put your markdown under `./src/pages/xx.md`, then you can access the page via route `/xx`.
### Work with [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components)
`unplugin-vue-components` allows you to do on-demand components auto-importing without worrying about registration.
```ts
import Vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import Markdown from 'unplugin-vue-markdown/vite'
export default {
plugins: [
Vue({
include: [/\.vue$/, /\.md$/],
}),
Markdown(),
// should be placed after `Markdown()`
Components({
// allow auto load markdown components under `./src/components/`
extensions: ['vue', 'md'],
// allow auto import and register components used in markdown
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
})
],
}
```
Components under `./src/components` can be directly used in markdown components, and markdown components can also be put under `./src/components` to be auto imported.
## TypeScript Shim
```ts
declare module '*.vue' {
import type { ComponentOptions } from 'vue'
const Component: ComponentOptions
export default Component
}
declare module '*.md' {
import type { ComponentOptions } from 'vue'
const Component: ComponentOptions
export default Component
}
```
## License
MIT License © 2020-PRESENT [Anthony Fu](https://github.com/antfu)
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite MD</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/4.0.0/github-markdown.min.css" integrity="sha512-Oy18vBnbSJkXTndr2n6lDMO5NN31UljR8e/ICzVPrGpSud4Gkckb8yUpqhKuUNoE+o9gAb4O/rAxxw1ojyUVzg==" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/LukeAskew/prism-github@master/prism-github.css" />
</head>
<body>
<div id="app"></div>
<script type="module" src="./main.ts"></script>
</body>
</html>
+29
View File
@@ -0,0 +1,29 @@
import { createHead } from '@unhead/vue'
import routes from 'pages-generated'
import { createApp } from 'vue'
import {
createRouter,
createWebHistory,
} from 'vue-router'
import App from './App.vue'
import Counter from './Counter.vue'
import 'prismjs'
import 'prismjs/components/prism-bash'
import 'prismjs/components/prism-typescript'
import 'prismjs/components/prism-javascript'
import 'prismjs/components/prism-markup'
import 'prismjs/components/prism-markup-templating'
const app = createApp(App)
const head = createHead()
const router = createRouter({
history: createWebHistory(),
routes,
})
app.use(head)
app.use(router)
app.component('Counter', Counter)
app.mount('#app')
@@ -0,0 +1,24 @@
{
"type": "module",
"private": true,
"scripts": {
"dev": "cross-env DEBUG=unplugin-vue-markdown:* vite",
"build": "cross-env DEBUG=unplugin-vue-markdown:* vite build"
},
"dependencies": {
"@unhead/vue": "^1.11.13",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"cross-env": "^7.0.3",
"markdown-it-prism": "^2.3.0",
"prismjs": "^1.29.0",
"typescript": "^5.7.2",
"unplugin-vue-markdown": "workspace:*",
"vite": "^6.0.1",
"vite-plugin-inspect": "^0.10.2",
"vite-plugin-pages": "^0.32.4"
}
}
@@ -0,0 +1,35 @@
---
title: Hello
meta:
- name: description
content: Hello World
test: test
---
# Hello world!
Front matter: {{ frontmatter }}
<style>
h1 {
color: cadetblue;
}
</style>
<script setup>
import Counter2 from '../Counter2.vue'
</script>
<Counter />
<br>
<Counter2 />
<router-link to="/">Home</router-link>
<route>
{
meta: {
layout: 'home'
}
}
</route>
@@ -0,0 +1,11 @@
<template>
route test
</template>
<route>
{
meta: {
layout: 'home'
}
}
</route>
@@ -0,0 +1,31 @@
import Vue from '@vitejs/plugin-vue'
import prism from 'markdown-it-prism'
import Markdown from 'unplugin-vue-markdown/vite'
import { defineConfig } from 'vite'
import Inspect from 'vite-plugin-inspect'
import Pages from 'vite-plugin-pages'
export default defineConfig({
plugins: [
Vue({
include: [/\.vue$/, /\.md$/],
}),
Markdown({
markdownItOptions: {
},
headEnabled: true,
markdownItUses: [
prism,
],
}),
Pages({
pagesDir: 'pages',
extensions: ['vue', 'md'],
}),
Inspect(),
],
build: {
sourcemap: true,
},
})
@@ -0,0 +1,13 @@
declare module '*.vue' {
import type { ComponentOptions } from 'vue'
const Component: ComponentOptions
export default Component
}
declare module '*.md' {
import type { ComponentOptions } from 'vue'
const Component: ComponentOptions
export default Component
}
@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
],
}
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}
@@ -0,0 +1,26 @@
{
"name": "example-vue-cli",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.39.0",
"vue": "^3.5.13"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-service": "~5.0.8",
"unplugin-vue-markdown": "workspace:*"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
@@ -0,0 +1,25 @@
<script>
import Markdown from './Hi.md'
export default {
name: 'App',
components: {
Markdown,
},
}
</script>
<template>
<Markdown />
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
@@ -0,0 +1,3 @@
# Hello
Hey
@@ -0,0 +1,4 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
@@ -0,0 +1,21 @@
const { defineConfig } = require('@vue/cli-service')
const prism = require('markdown-it-prism')
const Markdown = require('unplugin-vue-markdown/webpack')
module.exports = defineConfig({
transpileDependencies: true,
parallel: false,
chainWebpack: (config) => {
config.module
.rule('vue')
.test(/\.(vue|md)$/)
config
.plugin('markdown')
.use(Markdown({
markdownItUses: [
prism,
],
}))
},
})
+110
View File
@@ -0,0 +1,110 @@
{
"name": "unplugin-vue-markdown",
"type": "module",
"version": "0.28.0",
"packageManager": "pnpm@9.15.0",
"description": "Compile Markdown to Vue component",
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
"license": "MIT",
"funding": "https://github.com/sponsors/antfu",
"homepage": "https://github.com/unplugin/unplugin-vue-markdown",
"repository": {
"type": "git",
"url": "https://github.com/unplugin/unplugin-vue-markdown"
},
"bugs": "https://github.com/unplugin/unplugin-vue-markdown/issues",
"keywords": [
"vite",
"unplugin",
"markdown-it"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./vite": {
"types": "./dist/vite.d.ts",
"import": "./dist/vite.js",
"require": "./dist/vite.cjs"
},
"./webpack": {
"types": "./dist/webpack.d.ts",
"import": "./dist/webpack.js",
"require": "./dist/webpack.cjs"
},
"./rollup": {
"types": "./dist/rollup.d.ts",
"import": "./dist/rollup.js",
"require": "./dist/rollup.cjs"
},
"./esbuild": {
"types": "./dist/esbuild.d.ts",
"import": "./dist/esbuild.js",
"require": "./dist/esbuild.cjs"
},
"./types": {
"types": "./dist/types.d.ts",
"import": "./dist/types.js",
"require": "./dist/types.cjs"
},
"./*": "./*"
},
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"typesVersions": {
"*": {
"*": [
"./dist/*",
"./*"
]
}
},
"scripts": {
"dev": "tsup --watch",
"example:dev": "npm -C examples/vite run dev",
"example:build": "npm -C examples/vite run build",
"build": "tsup",
"build:fix": "esno scripts/postbuild.ts",
"test": "vitest",
"test:update": "vitest -u",
"lint": "eslint .",
"prepublishOnly": "npm run build",
"release": "bumpp && npm publish"
},
"peerDependencies": {
"vite": "^2.0.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0"
},
"dependencies": {
"@mdit-vue/plugin-component": "^2.1.3",
"@mdit-vue/plugin-frontmatter": "^2.1.3",
"@mdit-vue/types": "^2.1.0",
"@rollup/pluginutils": "^5.1.4",
"@types/markdown-it": "^14.1.2",
"markdown-it": "^14.1.0",
"markdown-it-async": "^0.1.3",
"unplugin": "^2.1.0"
},
"devDependencies": {
"@antfu/eslint-config": "^3.12.0",
"@antfu/ni": "^0.23.1",
"@antfu/utils": "^0.7.10",
"@types/node": "^22.10.2",
"@unhead/vue": "^1.11.14",
"@vue/test-utils": "^2.4.6",
"bumpp": "^9.9.1",
"chalk": "^5.3.0",
"eslint": "^9.17.0",
"esno": "^4.8.0",
"fast-glob": "^3.3.2",
"rollup": "^4.28.1",
"tsup": "^8.3.5",
"typescript": "^5.7.2",
"vite": "^6.0.3",
"vitest": "^2.1.8",
"vue": "^3.5.13",
"webpack": "^5.97.1"
}
}
+2
View File
@@ -0,0 +1,2 @@
packages:
- examples/*
+23
View File
@@ -0,0 +1,23 @@
import { promises as fs } from 'node:fs'
import { basename, dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import chalk from 'chalk'
import fg from 'fast-glob'
async function run() {
// fix cjs exports
const files = await fg('*.cjs', {
ignore: ['chunk-*'],
absolute: true,
cwd: resolve(dirname(fileURLToPath(import.meta.url)), '../dist'),
})
for (const file of files) {
console.log(chalk.cyan.inverse(' POST '), `Fix ${basename(file)}`)
let code = await fs.readFile(file, 'utf8')
code = code.replace('exports.default =', 'module.exports =')
code += 'exports.default = module.exports;'
await fs.writeFile(file, code)
}
}
run()
+62
View File
@@ -0,0 +1,62 @@
import type { Frontmatter, MetaProperty, ResolvedOptions } from '../types'
const headProperties = [
'title',
'meta',
'link',
'base',
'style',
'script',
'htmlAttrs',
'bodyAttrs',
]
export function preprocessHead<T extends Frontmatter>(frontmatter: T, options: ResolvedOptions) {
if (!options.headEnabled)
return frontmatter
const head: Frontmatter = options.headField
? (frontmatter[options.headField] as Frontmatter || {})
: frontmatter
const meta = (head.meta = head.meta || []) as MetaProperty[]
if (head.title) {
if (!meta.find((i: any) => i.property === 'og:title'))
meta.push({ property: 'og:title', content: head.title })
if (!meta.find((i: any) => i.name === 'twitter:title'))
meta.push({ name: 'twitter:title', content: head.title })
}
if (head.description) {
if (!meta.find((i: any) => i.name === 'description'))
meta.push({ name: 'description', content: head.description })
if (!meta.find((i: any) => i.property === 'og:description'))
meta.push({ property: 'og:description', content: head.description })
if (!meta.find((i: any) => i.name === 'twitter:description'))
meta.push({ name: 'twitter:description', content: head.description })
}
if (head.image) {
if (!meta.find((i: any) => i.property === 'og:image'))
meta.push({ property: 'og:image', content: head.image })
if (!meta.find((i: any) => i.name === 'twitter:image'))
meta.push({ name: 'twitter:image', content: head.image })
if (!meta.find((i: any) => i.property === 'twitter:card'))
meta.push({ name: 'twitter:card', content: 'summary_large_image' })
}
const result: any = {}
for (const [key, value] of Object.entries(head)) {
if (headProperties.includes(key))
result[key] = value
}
return Object.entries(result).length === 0 ? null : result
}
+250
View File
@@ -0,0 +1,250 @@
import type { TransformResult } from 'vite'
import type { MarkdownEnv, ResolvedOptions } from '../types'
import { toArray, uniq } from '@antfu/utils'
import { componentPlugin } from '@mdit-vue/plugin-component'
import { frontmatterPlugin } from '@mdit-vue/plugin-frontmatter'
import MarkdownIt from 'markdown-it-async'
import { preprocessHead } from './head'
const scriptSetupRE = /<\s*script([^>]*)\bsetup\b([^>]*)>([\s\S]*)<\/script>/g
const defineExposeRE = /defineExpose\s*\(/g
const EXPORTS_KEYWORDS = [
'class',
'default',
'export',
'function',
'import',
'let',
'var',
'const',
'from',
'as',
'return',
'if',
'else',
'switch',
'case',
'break',
'for',
'while',
'do',
]
interface ScriptMeta {
code: string
attr: string
}
function extractScriptSetup(html: string) {
const scripts: ScriptMeta[] = []
html = html.replace(scriptSetupRE, (_, attr1, attr2, code) => {
scripts.push({
code,
attr: `${attr1} ${attr2}`.trim(),
})
return ''
})
return { html, scripts }
}
function extractCustomBlock(html: string, options: ResolvedOptions) {
const blocks: string[] = []
for (const tag of options.customSfcBlocks) {
html = html.replace(new RegExp(`<${tag}[^>]*\\b[^>]*>[^<]*<\\/${tag}>`, 'gm'), (code) => {
blocks.push(code)
return ''
})
}
return { html, blocks }
}
export function createMarkdown(options: ResolvedOptions) {
const isVue2 = options.vueVersion.startsWith('2.')
const markdown = MarkdownIt({
html: true,
linkify: true,
typographer: true,
...options.markdownItOptions,
})
markdown.use(componentPlugin, options.componentOptions)
if (options.frontmatter || options.excerpt) {
markdown.use(frontmatterPlugin, {
...options.frontmatterOptions,
grayMatterOptions: {
excerpt: options.excerpt,
...options.frontmatterOptions.grayMatterOptions,
},
})
}
markdown.linkify.set({ fuzzyLink: false })
options.markdownItUses.forEach((e) => {
const [plugin, options] = toArray(e)
markdown.use(plugin, options)
})
const setupPromise = (async () => {
await options.markdownItSetup(markdown)
})()
return async (id: string, raw: string): Promise<TransformResult> => {
await setupPromise
const {
wrapperClasses,
wrapperComponent,
transforms,
headEnabled,
frontmatterPreprocess,
} = options
raw = raw.trimStart()
raw = transforms.before?.(raw, id) ?? raw
const env: MarkdownEnv = { id }
let html = await markdown.renderAsync(raw, env)
const { excerpt = '', frontmatter: data = null } = env
const wrapperClassesResolved = toArray(
typeof wrapperClasses === 'function'
? wrapperClasses(id, raw)
: wrapperClasses,
)
.filter(Boolean)
.join(' ')
if (wrapperClassesResolved)
html = `<div class="${wrapperClassesResolved}">${html}</div>`
else
html = `<div>${html}</div>`
const wrapperComponentName = typeof wrapperComponent === 'function'
? wrapperComponent(id, raw)
: wrapperComponent
if (wrapperComponentName) {
const attrs = [
options.frontmatter && ':frontmatter="frontmatter"',
options.excerpt && ':excerpt="excerpt"',
].filter(Boolean).join(' ')
html = `<${wrapperComponentName} ${attrs}>${html}</${wrapperComponentName}>`
}
html = transforms.after?.(html, id) ?? html
if (options.escapeCodeTagInterpolation) {
// escape curly brackets interpolation in <code>, #14
html = html.replace(/<code(.*?)>/g, '<code$1 v-pre>')
}
const hoistScripts = extractScriptSetup(html)
html = hoistScripts.html
const customBlocks = extractCustomBlock(html, options)
html = customBlocks.html
const scriptLines: string[] = []
let frontmatterExportsLines: string[] = []
let excerptExportsLine = ''
let excerptKeyOverlapping = false
function hasExplicitExports() {
return defineExposeRE.test(hoistScripts.scripts.map(i => i.code).join(''))
}
if (options.frontmatter) {
if (options.excerpt && data) {
if (data.excerpt !== undefined)
excerptKeyOverlapping = true
data.excerpt = excerpt
}
const { head, frontmatter } = frontmatterPreprocess(data || {}, options, id, preprocessHead)
if (options.excerpt && !excerptKeyOverlapping && frontmatter.excerpt !== undefined)
delete frontmatter.excerpt
scriptLines.push(`const frontmatter = ${JSON.stringify(frontmatter)}`)
if (options.exportFrontmatter) {
frontmatterExportsLines = Object.entries(frontmatter)
.map(([key, value]) => {
if (EXPORTS_KEYWORDS.includes(key))
key = `_${key}`
return `export const ${key} = ${JSON.stringify(value)}`
})
}
if (!isVue2 && options.exposeFrontmatter && !hasExplicitExports())
scriptLines.push('defineExpose({ frontmatter })')
if (!isVue2 && headEnabled && head) {
// @ts-expect-error legacy option
if (headEnabled === 'vueuse')
throw new Error('unplugin-vue-markdown no longer supports @vueuse/head. Change `headEnabled` to `true` and install `@unhead/vue` instead.')
scriptLines.push(`const head = ${JSON.stringify(head)}`)
scriptLines.unshift(`import { useHead } from "@unhead/vue"`)
scriptLines.push('useHead(head)')
}
scriptLines.push(...transforms.extraScripts?.(frontmatter, id) || [])
}
if (options.excerpt) {
scriptLines.push(`const excerpt = ${JSON.stringify(excerpt)}`)
if (!excerptKeyOverlapping)
excerptExportsLine = `export const excerpt = ${JSON.stringify(excerpt)}\n`
if (!isVue2 && options.exposeExcerpt && !hasExplicitExports())
scriptLines.push('defineExpose({ excerpt })')
}
scriptLines.push(...hoistScripts.scripts.map(i => i.code))
let attrs = uniq(hoistScripts.scripts.map(i => i.attr)).join(' ').trim()
if (attrs)
attrs = ` ${attrs}`
const scripts = isVue2
? [
`<script${attrs}>`,
...scriptLines,
...frontmatterExportsLines,
excerptExportsLine,
'export default { data() { return { frontmatter } } }',
'</script>',
]
: [
`<script setup${attrs}>`,
...scriptLines,
'</script>',
...((frontmatterExportsLines.length || excerptExportsLine)
? [
`<script${attrs}>`,
...frontmatterExportsLines,
excerptExportsLine,
'</script>',
]
: []),
]
const code = [
`<template>${html}</template>`,
...scripts.map(i => i.trim()).filter(Boolean),
...customBlocks.blocks,
].join('\n')
return {
code,
map: { mappings: '' } as any,
}
}
}
+40
View File
@@ -0,0 +1,40 @@
import type { Options, ResolvedOptions } from '../types'
import { getVueVersion } from './utils'
export function resolveOptions(userOptions: Options): ResolvedOptions {
const defaultOptions: ResolvedOptions = {
headEnabled: false,
headField: '',
frontmatter: true,
excerpt: false,
exposeFrontmatter: true,
exposeExcerpt: false,
exportFrontmatter: true,
escapeCodeTagInterpolation: true,
customSfcBlocks: ['route', 'i18n', 'style'],
componentOptions: {},
frontmatterOptions: {},
markdownItOptions: {},
markdownItUses: [],
markdownItSetup: () => {},
wrapperComponent: null,
transforms: {},
vueVersion: userOptions.vueVersion || getVueVersion(),
wrapperClasses: 'markdown-body',
include: null,
exclude: null,
frontmatterPreprocess: (frontmatter, options, _id, defaults) => {
return {
head: defaults(frontmatter, options),
frontmatter,
}
},
}
const options = {
...defaultOptions,
...userOptions,
}
return options as ResolvedOptions
}
+17
View File
@@ -0,0 +1,17 @@
import { createRequire } from 'node:module'
const _require = typeof require === 'undefined'
? createRequire(import.meta.url)
: require
export function getVueVersion(defaultVersion = '3.2.0') {
try {
let v = _require('vue')
if (v.default)
v = v.default
return v.version || defaultVersion
}
catch {
return defaultVersion
}
}
+4
View File
@@ -0,0 +1,4 @@
import { createEsbuildPlugin } from 'unplugin'
import { unpluginFactory } from '.'
export default createEsbuildPlugin(unpluginFactory)
+47
View File
@@ -0,0 +1,47 @@
import type { UnpluginFactory } from 'unplugin'
import type { Options } from './types'
import { createFilter } from '@rollup/pluginutils'
import { createUnplugin } from 'unplugin'
import { createMarkdown } from './core/markdown'
import { resolveOptions } from './core/options'
const cssIdRE = /\.(css|postcss|sass|scss|less|stylus|styl)($|\?)/
export const unpluginFactory: UnpluginFactory<Options> = (userOptions = {}) => {
const options = resolveOptions(userOptions)
const markdownToVue = createMarkdown(options)
const filter = createFilter(
userOptions.include || /\.md$|\.md\?vue/,
userOptions.exclude || cssIdRE,
)
return {
name: 'unplugin-vue-markdown',
enforce: 'pre',
transformInclude(id) {
return filter(id)
},
async transform(raw, id) {
try {
return await markdownToVue(id, raw)
}
catch (e: any) {
this.error(e)
}
},
vite: {
async handleHotUpdate(ctx) {
if (!filter(ctx.file))
return
const defaultRead = ctx.read
ctx.read = async function () {
return (await markdownToVue(ctx.file, await defaultRead())).code
}
},
},
}
}
export default /* #__PURE__ */ createUnplugin(unpluginFactory)
+4
View File
@@ -0,0 +1,4 @@
import { createRollupPlugin } from 'unplugin'
import { unpluginFactory } from '.'
export default createRollupPlugin(unpluginFactory)
+4
View File
@@ -0,0 +1,4 @@
import { createRspackPlugin } from 'unplugin'
import { unpluginFactory } from '.'
export default createRspackPlugin(unpluginFactory)
+206
View File
@@ -0,0 +1,206 @@
import type { ComponentPluginOptions } from '@mdit-vue/plugin-component'
import type { FrontmatterPluginOptions } from '@mdit-vue/plugin-frontmatter'
import type { MarkdownItEnv } from '@mdit-vue/types'
import type { FilterPattern } from '@rollup/pluginutils'
import type {
MarkdownItAsync,
MarkdownItAsyncOptions,
PluginSimple as MarkdownItPluginSimple,
PluginWithOptions as MarkdownItPluginWithOptions,
} from 'markdown-it-async'
import type { preprocessHead } from './core/head'
/** a `<meta />` property in HTML is defined with the following name/values */
export interface MetaProperty {
key?: string
/**
* the "name" property used by Facebook and other providers who
* use the Opengraph standards
*/
property?: string
/**
* used by google to identify the "name" of the name/value pair
*/
itemprop?: string
/**
* used by Twitter to indicate the "name" field in a meta properties
* name/value pairing
*/
name?: string
/**
* The value of the meta property
*/
content?: any
[key: string]: unknown
}
/**
* Frontmatter content is represented as key/value dictionary
*/
export interface Frontmatter {
title?: string
name?: string
description?: string
meta?: MetaProperty[]
[key: string]: unknown
}
export interface Options {
/**
* Explicitly set the Vue version
*
* @default auto detected
*/
vueVersion?: string
/**
* Enable head support, need to install @unhead/vue and register to App in main.js
*
* @default true
*/
headEnabled?: boolean
/**
* The head field in frontmatter used to be used for @unhead/vue
*
* When an empty string is passed, it will use the root properties of the frontmatter
*
* @default ''
*/
headField?: string
/**
* Parse for frontmatter
*
* @default true
*/
frontmatter?: boolean
/**
* Parse for excerpt
*
* If `true`, it will be passed to `frontmatterPreprocess` as `frontmatter.excerpt`, replacing the `excerpt` key in frontmatter, if there's any
*
* @default false
*/
excerpt?: boolean
/**
* Remove custom SFC block
*
* @default ['route', 'i18n']
*/
customSfcBlocks?: string[]
/**
* Options passed to [@mdit-vue/plugin-component](https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-component)
*/
componentOptions?: ComponentPluginOptions
/**
* Options passed to [@mdit-vue/plugin-frontmatter](https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-frontmatter)
*/
frontmatterOptions?: FrontmatterPluginOptions
/**
* Custom function to provide defaults to the frontmatter and
* move certain attributes into the "meta" category.
*
* Note: _overriding this will remove built-in functionality setting
* "meta" properties and the built-in "head" support. Do this only
* if you know what you're doing._
*/
frontmatterPreprocess?: (
frontmatter: Frontmatter,
options: ResolvedOptions,
id: string,
defaultHeadProcess: typeof preprocessHead,
) => {
head: Record<string, any>
frontmatter: Frontmatter
}
/**
* Expose frontmatter via expose API
*
* @default true
*/
exposeFrontmatter?: boolean
/**
* Expose excerpt via expose API
*
* @default false
*/
exposeExcerpt?: boolean
/**
* Export frontmatter in component module
*
* @default true
*/
exportFrontmatter?: boolean
/**
* Add `v-pre` to `<code>` tag to escape curly brackets interpolation
*
* @see https://github.com/unplugin/unplugin-vue-markdown/issues/14
* @default true
*/
escapeCodeTagInterpolation?: boolean
/**
* Options passed to Markdown It
*/
markdownItOptions?: MarkdownItAsyncOptions
/**
* Plugins for Markdown It
*/
markdownItUses?: (
| MarkdownItPluginSimple
| [MarkdownItPluginSimple | MarkdownItPluginWithOptions<any>, any]
| any
)[]
/**
* A function providing the Markdown It instance gets the ability to apply custom
* settings/plugins
*/
markdownItSetup?: (MarkdownIt: MarkdownItAsync) => void | Promise<void>
/**
* Class names for wrapper div
*
* @default 'markdown-body'
*/
wrapperClasses?: string | string[] | undefined | null | ((id: string, code: string) => string | string[] | undefined | null)
/**
* Component name to wrapper with
*
* @default undefined
*/
wrapperComponent?: string | undefined | null | ((id: string, code: string) => string | undefined | null)
/**
* Custom tranformations apply before and after the markdown transformation
*/
transforms?: {
before?: (code: string, id: string) => string
after?: (code: string, id: string) => string
/**
* Return extra code to be injected into the `<script>` tag
*/
extraScripts?: (frontmatter: Record<string, any>, id: string) => string[]
}
include?: FilterPattern
exclude?: FilterPattern
}
export interface ResolvedOptions extends Required<Options> {}
export interface MarkdownEnv extends MarkdownItEnv {
id: string
}
+4
View File
@@ -0,0 +1,4 @@
import { createVitePlugin } from 'unplugin'
import { unpluginFactory } from '.'
export default createVitePlugin(unpluginFactory)
+4
View File
@@ -0,0 +1,4 @@
import { createWebpackPlugin } from 'unplugin'
import { unpluginFactory } from '.'
export default createWebpackPlugin(unpluginFactory) as any
@@ -0,0 +1,43 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`excerpt > raw excerpt 1`] = `
"<template><div class="markdown-body"><p>This is an excerpt which is kept as <strong>raw Markdown</strong>.</p>
<!-- more -->
<h1>Hello</h1>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
</div></template>
<script setup>
const frontmatter = {"title":"Hey"}
defineExpose({ frontmatter })
const excerpt = "\\nThis is an excerpt which is kept as **raw Markdown**.\\n\\n"
</script>
<script>
export const title = "Hey"
export const excerpt = "\\nThis is an excerpt which is kept as **raw Markdown**.\\n\\n"
</script>"
`;
exports[`excerpt > rendered excerpt 1`] = `
"<template><div class="markdown-body"><p>This is an excerpt which has been rendered to <strong>HTML</strong>.</p>
<!-- more -->
<h1>Hello</h1>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
</div></template>
<script setup>
const frontmatter = {"title":"Hey"}
defineExpose({ frontmatter })
const excerpt = "<p>This is an excerpt which has been rendered to <strong>HTML</strong>.</p>\\n"
</script>
<script>
export const title = "Hey"
export const excerpt = "<p>This is an excerpt which has been rendered to <strong>HTML</strong>.</p>\\n"
</script>"
`;
@@ -0,0 +1,128 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`transform > basic 1`] = `
"<template><div class="markdown-body"><h1>Hello</h1>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
</div></template>
<script setup>
const frontmatter = {"title":"Hey"}
defineExpose({ frontmatter })
</script>
<script>
export const title = "Hey"
</script>"
`;
exports[`transform > code escape 1`] = `
"<template><div class="markdown-body"><p>Hello <code v-pre>{{ world }}</code></p>
<pre><code class="language-js" v-pre>console.log(\`{{ world }}\`)
</code></pre>
</div></template>
<script setup>
const frontmatter = {}
defineExpose({ frontmatter })
</script>"
`;
exports[`transform > couldn't expose frontmatter 1`] = `
"<template><div class="markdown-body">
</div></template>
<script setup>
const frontmatter = {"title":"Hey"}
defineExpose({ test: 'test'})
</script>
<script>
export const title = "Hey"
</script>"
`;
exports[`transform > escapeCodeTagInterpolation 1`] = `
"<template><div class="markdown-body"><div>{{hello}}</div>
<pre><code class="language-ts" v-pre>&lt;div&gt;{{hello}}&lt;/div&gt;
</code></pre>
</div></template>
<script setup>
const frontmatter = {}
defineExpose({ frontmatter })
</script>"
`;
exports[`transform > export keyword frontmatters 1`] = `
"<template><div class="markdown-body"><p>Hello</p>
</div></template>
<script setup>
const frontmatter = {"class":"text","default":"foo"}
defineExpose({ frontmatter })
</script>
<script>
export const _class = "text"
export const _default = "foo"
</script>"
`;
exports[`transform > exposes frontmatter 1`] = `
"<template><div class="markdown-body"><h1>Hello</h1>
</div></template>
<script setup>
const frontmatter = {"title":"Hey"}
defineExpose({ frontmatter })
</script>
<script>
export const title = "Hey"
</script>"
`;
exports[`transform > frontmatter interpolation 1`] = `
"<template><div class="markdown-body"><h1>Hello World</h1>
<p>This is {{frontmatter.name}}</p>
</div></template>
<script setup>
const frontmatter = {"name":"My Cool App"}
defineExpose({ frontmatter })
</script>
<script>
export const name = "My Cool App"
</script>"
`;
exports[`transform > script setup 1`] = `
"<template><div class="markdown-body"><h1>Hello</h1>
</div></template>
<script setup lang="ts">
const frontmatter = {}
defineExpose({ frontmatter })
import Foo from './Foo.vue'
</script>"
`;
exports[`transform > style 1`] = `
"<template><div class="markdown-body"><h1>Hello</h1>
</div></template>
<script setup>
const frontmatter = {}
defineExpose({ frontmatter })
</script>
<style>h1 { color: red }</style>"
`;
exports[`transform > vue directives 1`] = `
"<template><div class="markdown-body">
<p><button @click="onClick"></button></p>
</div></template>
<script setup lang="ts">
const frontmatter = {"name":"My Cool App"}
defineExpose({ frontmatter })
function onClick() {
// ...
}
</script>
<script lang="ts">
export const name = "My Cool App"
</script>"
`;
+60
View File
@@ -0,0 +1,60 @@
import { describe, expect, it } from 'vitest'
import { createMarkdown } from '../src/core/markdown'
import { resolveOptions } from '../src/core/options'
describe('excerpt', () => {
it('rendered excerpt', async () => {
const options = resolveOptions({
excerpt: true,
frontmatterOptions: {
grayMatterOptions: {
excerpt: true,
excerpt_separator: '<!-- more -->',
},
},
})
const markdownToVue = createMarkdown(options)
const md = `---
title: Hey
---
This is an excerpt which has been rendered to **HTML**.
<!-- more -->
# Hello
- A
- B
- C`
expect((await markdownToVue('', md)).code).toMatchSnapshot()
})
it('raw excerpt', async () => {
const options = resolveOptions({
excerpt: true,
frontmatterOptions: {
renderExcerpt: false,
grayMatterOptions: {
excerpt: true,
excerpt_separator: '<!-- more -->',
},
},
})
const markdownToVue = createMarkdown(options)
const md = `---
title: Hey
---
This is an excerpt which is kept as **raw Markdown**.
<!-- more -->
# Hello
- A
- B
- C`
expect((await markdownToVue('', md)).code).toMatchSnapshot()
})
})
@@ -0,0 +1,12 @@
---
title: Hello World
description: testing is the path to true happiness
---
# My H1
lorem ipsum
## Transformed
base: {{ title }}
@@ -0,0 +1,53 @@
import type { MetaProperty, ResolvedOptions } from '../src/types'
import { readFile } from 'node:fs/promises'
import { describe, expect, it } from 'vitest'
import { createMarkdown } from '../src/core/markdown'
import { resolveOptions } from '../src/core/options'
const frontmatterPreprocess: ResolvedOptions['frontmatterPreprocess'] = (fm) => {
const frontmatter = {
title: 'default title',
description: 'default description',
...fm,
}
const meta: MetaProperty[] = [
{ property: 'og:title', name: 'twitter:title', itemprop: 'title', content: frontmatter.title },
{
property: 'og:description',
name: 'twitter:description',
itemprop: 'description',
content: frontmatter.description,
},
]
return {
head: { ...frontmatter, meta },
frontmatter: { ...frontmatter, meta },
}
}
describe('provide bespoke frontmatter processor', () => {
it('inline markdown is used over default properties', async () => {
const markdownToVue = createMarkdown(resolveOptions({ frontmatterPreprocess }))
const md = (await markdownToVue('', await readFile('test/fixtures/simple.md', 'utf-8'))).code
// Positive tests
expect(
md.includes('Hello World'),
'the title attribute is retained over the default \'title\' value',
).toBeTruthy()
expect(
md.includes('testing is the path to true happiness'),
'description property is also retained',
).toBeTruthy()
// Negative tests
expect(
md.includes('default title'),
'the title attribute is retained over the default \'title\' value',
).toBeFalsy()
expect(md.includes('default description'), 'default description is ignored').toBeFalsy()
// Meta props
expect(md.includes('og:title')).toBeTruthy()
expect(md.includes('og:description')).toBeTruthy()
})
})
+127
View File
@@ -0,0 +1,127 @@
import { describe, expect, it } from 'vitest'
import { createMarkdown } from '../src/core/markdown'
import { resolveOptions } from '../src/core/options'
describe('transform', async () => {
const options = resolveOptions({})
const markdownToVue = createMarkdown(options)
it('basic', async () => {
const md = `---
title: Hey
---
# Hello
- A
- B
- C
`
expect((await markdownToVue('', md)).code).toMatchSnapshot()
})
it('style', async () => {
const md = `
# Hello
<style>h1 { color: red }</style>
`
expect((await markdownToVue('', md)).code).toMatchSnapshot()
})
it('script setup', async () => {
const md = `
# Hello
<script setup lang="ts">
import Foo from './Foo.vue'
</script>
`
expect((await markdownToVue('', md)).code).toMatchSnapshot()
})
it('exposes frontmatter', async () => {
const md = `---
title: Hey
---
# Hello`
expect((await markdownToVue('', md)).code).toMatchSnapshot()
})
it('couldn\'t expose frontmatter', async () => {
const md = `---
title: Hey
---
<script setup>
defineExpose({ test: 'test'})
</script>
`
expect((await markdownToVue('', md)).code).toMatchSnapshot()
})
it('escapeCodeTagInterpolation', async () => {
const md = `
<div>{{hello}}</div>
\`\`\`ts
<div>{{hello}}</div>
\`\`\`
`
expect((await markdownToVue('', md)).code).toMatchSnapshot()
})
it('frontmatter interpolation', async () => {
const md = `
---
name: 'My Cool App'
---
# Hello World
This is {{frontmatter.name}}
`
expect((await markdownToVue('', md)).code).toMatchSnapshot()
})
it('vue directives', async () => {
const md = `
---
name: 'My Cool App'
---
<script setup lang="ts">
function onClick() {
// ...
}
</script>
<button @click="onClick"></button>
`
expect((await markdownToVue('', md)).code).toMatchSnapshot()
})
it('export keyword frontmatters', async () => {
const md = `
---
class: 'text'
default: 'foo'
---
Hello
`
expect((await markdownToVue('', md)).code).toMatchSnapshot()
})
it('code escape', async () => {
const md = `
Hello \`{{ world }}\`
\`\`\`js
console.log(\`{{ world }}\`)
\`\`\`
`
expect((await markdownToVue('', md)).code).toMatchSnapshot()
})
})
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "es2020",
"jsx": "preserve",
"lib": ["esnext", "DOM"],
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"strict": true,
"strictNullChecks": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"exclude": [
"examples/**",
"dist/**"
]
}
+13
View File
@@ -0,0 +1,13 @@
import type { Options } from 'tsup'
export default <Options>{
entryPoints: [
'src/*.ts',
],
clean: true,
format: ['cjs', 'esm'],
dts: true,
shims: true,
splitting: true,
onSuccess: 'npm run build:fix',
}