Revert "chore: remove @vant/markdown-loader package"

This reverts commit 6eec7f3c2f.
This commit is contained in:
chenjiahan
2021-09-30 10:32:29 +08:00
parent 7c45604f8f
commit 6a054ca0e3
10 changed files with 508 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
module.exports = function cardWrapper(html) {
const group = html
.replace(/<h3/g, ':::<h3')
.replace(/<h2/g, ':::<h2')
.split(':::');
return group
.map(fragment => {
if (fragment.indexOf('<h3') !== -1) {
return `<div class="card">${fragment}</div>`;
}
return fragment;
})
.join('');
};

View File

@@ -0,0 +1,42 @@
const path = require('path');
const fs = require('fs');
const os = require('os');
const parser = require('./md-parser');
function hyphenate(str) {
return str.replace(/\B([A-Z])/g, '-$1').toLowerCase();
}
module.exports = function extraDemo(content) {
const isWin = /^win/.test(os.platform());
const markdownDir = path.dirname(this.resourcePath);
const demoLinks = [];
content = content.replace(
/<demo-code([\s\S]*?)>([\s\S]*?)<\/demo-code>/g,
function (_, attrs, link) {
link = link.trim(); // 去换行符
const tag = 'demo-code-' + hyphenate(path.basename(link, '.vue'));
let fullLink;
if (isWin) {
fullLink = path.posix.join(...markdownDir.split(path.sep), link);
} else {
fullLink = path.join(markdownDir, link);
}
demoLinks.indexOf(fullLink) === -1 && demoLinks.push(fullLink);
const demoContent = fs.readFileSync(fullLink, { encoding: 'utf8' });
const demoParseredContent = parser.render(
'```html\n' + demoContent + '\n```'
);
return `
<demo-playground${attrs}
origin-code="${escape(demoContent)}"
code-snippet="${escape(demoParseredContent)}">
<${tag} />
</demo-playground>
`;
}
);
return [content, demoLinks];
};

View File

@@ -0,0 +1,10 @@
const hljs = require('highlight.js');
module.exports = function highlight(str, lang) {
if (lang && hljs.getLanguage(lang)) {
// https://github.com/highlightjs/highlight.js/issues/2277
return hljs.highlight(str, { language: lang, ignoreIllegals: true }).value;
}
return '';
};

View File

@@ -0,0 +1,117 @@
const path = require('path');
const loaderUtils = require('loader-utils');
const frontMatter = require('front-matter');
const parser = require('./md-parser');
const linkOpen = require('./link-open');
const cardWrapper = require('./card-wrapper');
const extractDemo = require('./extract-demo');
const sideEffectTags = require('./side-effect-tags');
function camelize(str) {
return `-${str}`.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''));
}
const sharedVueOptions = `mounted() {
const anchors = [].slice.call(this.$el.querySelectorAll('h2, h3, h4, h5'));
anchors.forEach(anchor => {
anchor.addEventListener('click', this.scrollToAnchor);
});
},
methods: {
scrollToAnchor(event) {
if (event.target.id) {
this.$router.push({
name: this.$route.name,
hash: '#' + event.target.id
})
}
}
},
`;
function wrapper(content) {
let demoLinks;
[content, demoLinks] = extractDemo.call(this, content);
content = cardWrapper(content);
// 不包含 demo-code 的 md 文件,直接使绑定 HTML
if (demoLinks.length === 0) {
content = escape(content);
return `
<script>
import { h } from 'vue';
const content = unescape(\`${content}\`);
export default {
${sharedVueOptions}
render() {
return h('section', { innerHTML: content });
}
};
</script>
`;
}
// 包含 demo-code 的 md 文件,需要走模版渲染
let styles;
[content, styles] = sideEffectTags(content);
return `
<template>
<section v-once>
${content}
</section>
</template>
<script>
${demoLinks
.map((link) => {
return `import DemoCode${camelize(
path.basename(link, '.vue')
)} from '${link}';`;
})
.join('\n')}
export default {
components: {
${demoLinks
.map((link) => `DemoCode${camelize(path.basename(link, '.vue'))}`)
.join(',')}
},
${sharedVueOptions}
};
</script>
${styles.join('\n')}
`;
}
module.exports = function (source) {
let options = loaderUtils.getOptions(this) || {};
this.cacheable && this.cacheable();
options = {
wrapper,
linkOpen: true,
...options,
};
let fm;
if (options.enableMetaData) {
fm = frontMatter(source);
source = fm.body;
}
if (options.linkOpen) {
linkOpen(parser);
}
return options.wrapper.call(this, parser.render(source), fm);
};

View File

@@ -0,0 +1,18 @@
// add target="_blank" to all links
module.exports = function linkOpen(md) {
const defaultRender =
md.renderer.rules.link_open ||
function(tokens, idx, options, env, self) {
return self.renderToken(tokens, idx, options);
};
md.renderer.rules.link_open = function(tokens, idx, options, env, self) {
const aIndex = tokens[idx].attrIndex('target');
if (aIndex < 0) {
tokens[idx].attrPush(['target', '_blank']); // add new attribute
}
return defaultRender(tokens, idx, options, env, self);
};
};

View File

@@ -0,0 +1,14 @@
const MarkdownIt = require('markdown-it');
const markdownItAnchor = require('markdown-it-anchor');
const highlight = require('./highlight');
const { slugify } = require('transliteration');
const parser = new MarkdownIt({
html: true,
highlight,
}).use(markdownItAnchor, {
level: 2,
slugify,
});
module.exports = parser;

View File

@@ -0,0 +1,14 @@
module.exports = function sideEffectTags(content) {
const styles = [];
// 从模版中移除 script 标签
content = content.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/g, '');
// 从模版中移除 style 标签,并收集到 styles 数组中,以转移为 .vue 文件 的 style 标签
content = content.replace(/<style[\s\S]*?>([\s\S]*?)<\/style>/g, (_, css) => {
styles.push(`<style scoped>${css}</style>`);
return '';
});
return [content, styles];
};