使用WebPack将MD文件解析为Vue组件

196 阅读2分钟

本文适合对webpack有一丁点了解的同学,因为会涉及到loader的编写。不过不用害怕,因为webpack的loader并不复杂,我们只需要了解一点基础即可。传送门 编写一个loader

阅读官方文档后可以发现,其实我们只需要改变内容的输出即可,在这里要注意一下,loader的加载顺序是从后往前,也就是说,我们要想把md文件编译为vue组件,需要先使用markdown的解析器将md文件编译为html文件,再由vue-loader渲染成vue组件(其中需要注意一下,仓库中是将script标签放在前面,如果有时间可以写正则判断哪些script是属于vue组件的script内容)。大概的流程如图所示

image.png

流程图中 md2vue-loader 就是我们写逻辑代码的地方,比如可以自定义md语法,改变输出交给vue-loader,这样我们就完成了md转换成vue组件。接下来,我们详细的介绍 md2vue-loader。

首先我们要明确需求,如何将md文件转换为vue组件。如果还不知道md文件,请移步zh.wikipedia.org/wiki/Markdo…

还要明确一点就是,vue组件需要什么内容。请移步vue-loader.vuejs.org/zh/spec.htm…

那么,我们负责将md文件转换为templatescriptstyle(自定义块不考虑),其中 style可以从外部导入,md2vue-loader不负责这部分内容 。那我们如何提取其中html内容和javascript内容。

我们可以使用社区提供的markdown-it,仓库地址 github.com/markdown-it…markdown-it提供了render方法 ,代码如下

// md2vue-loader
module.exports = function (source) {
  var MarkdownIt = require('markdown-it'),
    md = new MarkdownIt();
  var result = md.render(source);
  return `<template><div>${result}</div></template>`;
}

到这里,我们已经可以将md文件转换为html文件并输出了。什么,不信?赶快开一个仓库去试一试,或者clone我的仓库试一试github.com/Linkontoask…

既然得到html内容了是不是大功告成了?不,当然不,之前我们说过vue组件不仅仅只有template ,还有更重要的javascript部分。我们这里使用一个很简单的方法提取html文件中 ,就是将script标签放在文件开头,然后再用indexOf方法判断,再使用 slice方法切割字符串、拼接后输出。具体代码如下:

module.exports = function (source) {
  var MarkdownIt = require('markdown-it'),
    md = new MarkdownIt();

  const content = md.render(source);

  let start = 0, pageScript = '', output = '';

  if (content.indexOf('<script>') === 0) {
    start = content.indexOf('</script>') + '</script>'.length;
    pageScript = content.slice(0, start);
  }

  output = content.slice(start);

  return `
    <template>
      <section class="markdown-body">
        ${output}
      </section>
    </template>
    ${pageScript}
  `;
};

在这里,md2vue-loader基本已经完成,还有一些功能,比如标题的ID、永久链接、锚点的类名等等。

所有代码请移步到 github.com/Linkontoask…

这里贴一个截止到2019-12-17代码

// index.js
const { getOptions } =  require('loader-utils');
const validateOptions = require('schema-utils');
const MarkdownIt = require('markdown-it');
const Hljs = require('highlight.js');
const slugify = require('transliteration').slugify;

const schema = {
  type: 'object',
  properties: {
    html: {
      type: "boolean"
    },
    permalink: {
      type: "boolean"
    }
  }
};

function mdOption (options = {}) {
  return new MarkdownIt({
    html: options.html,
    highlight: function (str, lang) {
      if (lang && Hljs.getLanguage(lang)) {
        try {
          return Hljs.highlight(lang, str, true).value;
        } catch (e) {}
      }
      return str;
    }
  }).use(require('markdown-it-anchor'), {
    slugify: slugify,
    permalink: options.permalink
  });
}

/**
 * @param source
 * @returns {string}
 */
module.exports = function (source) {
  const options = getOptions(this);

  validateOptions(schema, options);

  const md = mdOption(options);

  const content = md.render(source);

  let start = 0, pageScript = '', output = '';

  if (content.indexOf('<script>') === 0) {
    start = content.indexOf('</script>') + '</script>'.length;
    pageScript = content.slice(0, start);
  }

  output = content.slice(start);

  return `
    <template>
      <section class="markdown-body">
        ${output}
      </section>
    </template>
    ${pageScript}
  `;
};

配置参数参考源码,可自行更改。

// webpack.config.js
module: {
    rules: [
      {
        test: /.md$/,
        use: [
          'vue-loader',
          {
            loader: path.resolve(__dirname, '../index.js'),
            options: {
              html: true // 是否渲染原生标签,如果要使用script标签务必设置为true
            }
          }
        ],
      }
    ]
  }

md2vue-loader参考Element实现思路,还有值得优化的部分。

友情链接

  • gyron.cc 一个很简单的前端框架