手写 plugin

46 阅读2分钟

image.png

plugins/md-to-html-plugin/compiler.js

const { randomNum } = require('./utils');

const reg_mark = /^(.+?)\s/;
const reg_sharp = /^\#/;
const reg_crossbar = /^\-/;
const reg_number = /^\d/;

function createTree (mdArr) {
  let _htmlPool = {};
  let _lastMark = '';
  let _key = 0;

  mdArr.forEach((mdFragment) => {
    const matched = mdFragment.match(reg_mark);
    
    if (matched) {
      const mark = matched[1];
      const input = matched['input'];

      if (reg_sharp.test(mark)) {
        const tag = `h${mark.length}`;
        const tagContent = input.replace(reg_mark, '');

        if (_lastMark === mark) {
          _htmlPool[`${tag}-${_key}`].tags = [..._htmlPool[`${tag}-${_key}`].tags, `<${tag}>${tagContent}</${tag}>`];
        } else {
          _lastMark = mark;
          _key = randomNum();
          _htmlPool[`${tag}-${_key}`] = {
            type: 'single',
            tags: [`<${tag}>${tagContent}</${tag}>`]
          }
        }
      }

      if (reg_crossbar.test(mark)) {
        const tagContent = input.replace(reg_mark, '');
        const tag = `li`;

        if (reg_crossbar.test(_lastMark)) {
          _htmlPool[`ul-${_key}`].tags = [..._htmlPool[`ul-${_key}`].tags, `<${tag}>${tagContent}</${tag}>`]
        } else {
          _lastMark = mark;
          _key = randomNum();
          _htmlPool[`ul-${_key}`] = {
            type: 'wrap',
            tags: [`<${tag}>${tagContent}</${tag}>`]
          }
        }
      }

      if (reg_number.test(mark)) {
        const tagContent = input.replace(reg_mark, '');
        const tag = `li`;

        if (reg_number.test(_lastMark)) {
          _htmlPool[`ol-${_key}`].tags = [..._htmlPool[`ol-${_key}`].tags, `<${tag}>${tagContent}</${tag}>`]
        } else {
          console.log(_lastMark, mark);
          _lastMark = mark;
          _key = randomNum();

          _htmlPool[`ol-${_key}`] = {
            type: 'wrap',
            tags: [`<${tag}>${tagContent}</${tag}>`]
          }
        }
      }
    }
  });

  return _htmlPool;
}


function compileHTML (_mdArr) {
  const _htmlPool = createTree(_mdArr);
  let _htmlStr = '';
  let item;

  for (let k in _htmlPool) {
    item = _htmlPool[k];

    if (item.type === 'single') {
      item.tags.forEach((tag) => {
        _htmlStr += tag;
      });
    } else if (item.type === 'wrap') {
      let _list = `<${k.split('-')[0]}>`;

      item.tags.forEach((tag) => {
        _list += tag;
      });
      _list += `</${k.split('-')[0]}>`;
      _htmlStr += _list;
    }
  }
  
  return _htmlStr;
}

module.exports = {
  compileHTML
}

/**
 * {
  'h1-1606831820464': { type: 'single', tags: [ '<h1>这是一个h1的标题</h1>' ] },
  'ul-1606831824302': {
    type: 'wrap',
    tags: [
      '<li>这是UL列表第1项</li>',
      '<li>这是UL列表第2项</li>',
      '<li>这是UL列表第3项</li>',
      '<li>这是UL列表第4项</li>'
    ]
  },
  'h2-1606831827189': { type: 'single', tags: [ '<h2>这是一个h2的标题</h2>' ] },
  'ol-1606831822499': {
    type: 'wrap',
    tags: [
      '<li>这是OL列表第1项</li>',
      '<li>这是OL列表第2项</li>',
      '<li>这是OL列表第3项</li>',
      '<li>这是OL列表第4项</li>'
    ]
  }
}
 */

/**
 * 
 * # 这是一个h1的标题

- 这是UL列表第1项
- 这是UL列表第2项
- 这是UL列表第3项
- 这是UL列表第4项

## 这时一个h2的标题

1. 这是OL列表第1项
2. 这是OL列表第2项
3. 这是OL列表第3项
4. 这是OL列表第4项
 */

/**
 * {
 *   h1: {
 *     type: 'single',
 *     tags: ['<h1>这是一个h1的标题</h1>']
 *   },
 *   ul: {
 *     type: 'wrap',
 *     tags: [
 *       '<li>这是UL列表第1项</li>',
 *       '<li>这是UL列表第1项</li>',
 *       '<li>这是UL列表第1项</li>',
 *       '<li>这是UL列表第1项</li>'
 *     ]
 *   }
 * }
 * 
 */

plugins/md-to-html-plugin/index.js

const { readFileSync } = require('fs');
const { resolve } = require('path');
const { compileHTML } = require('./compiler');

const INNER_MARK = '<!-- inner -->';

class MdToHtmlPlugin {
  constructor ({ template, filename }) {
    
    if (!template) {
      throw new Error('The config for "template" must be configured');
    }

    this.template = template;
    this.filename = filename ? filename : 'md.html';
  }

  apply (compiler) {
    compiler.hooks.emit.tap('md-to-html-plugin', (compilation) => {
      const _assets = compilation.assets;
      const _mdContent = readFileSync(this.template, 'utf8');
      const _templateHTML = readFileSync(resolve(__dirname, 'template.html'), 'utf8');
      const _mdContentArr = _mdContent.split('\n');
      const _htmlStr = compileHTML(_mdContentArr);

      const _finalHTML = _templateHTML.replace(INNER_MARK, _htmlStr);

      _assets[this.filename] = {
        source () {
          return _finalHTML;
        },
        size () {
          return _finalHTML.length;
        }
      }
    })
  }
}

module.exports = MdToHtmlPlugin;

plugins/md-to-html-plugin/template.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <!-- inner -->
</body>
</html>

plugins/md-to-html-plugin/utils.js

function randomNum () {
  return new Date().getTime() + parseInt(Math.random() * 10000);
}

module.exports = {
  randomNum
}

test.md

# 这是一个h1的标题

- 这是UL列表第1项
- 这是UL列表第2项
- 这是UL列表第3项
- 这是UL列表第4项

## 这是一个h2的标题

1. 这是OL列表第1项
2. 这是OL列表第2项
3. 这是OL列表第3项
4. 这是OL列表第4项

package.json

{
  "name": "md-to-html-plugin",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.30.0",
    "webpack-cli": "^3.3.0",
    "webpack-dev-server": "^3.7.2"
  }
}

webpack.config.js

// webpack.config.js
const { resolve } = require('path')
const MdToHtmlPlugin = require('./plugins/md-to-html-plugin')
module.exports = {
    mode: 'development',
    entry: resolve(__dirname, 'src/app.js'),
    output: {
        path: resolve(__dirname, 'dist'),
        filename: 'app.js'
    },
    plugins: [
        new MdToHtmlPlugin({
            template: resolve(__dirname, 'test.md'),
            filename: 'test.html'
        })
    ]
}