start

  • 实现一个 markdown 文件转 html 的功能

文件目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
├── package.json
├── node_modules
├── dist
├── plugins
│ └── md-to-html-plugin
│ ├── compiler.js
│ ├── index.js
│ ├── template.html
│ └── utils.js
├── src
│ └── app.js
├── test.md
└── webpack.config.js

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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: "main.js"
},
plugins: [
new MdToHtmlPlugin({
template: resolve(__dirname, 'test.md'),
filename: 'test.html'
})
]
}

test.md

1
2
3
4
5
6
7
8
9
10
11
12
13
# 这是h1

- 这是ul列表第1
- 这是ul列表第2
- 这是ul列表第3
- 这是ul列表第4

## 这是h2

1. 这是ol列表第1
2. 这是ol列表第2
3. 这是ol列表第3
4. 这是ol列表第4

package.json

1
2
3
4
5
6
7
8
9
10
11
 "scripts": {
"build": "webpack",
},
"devDependencies": {
"@babel/core": "^7.12.9",
"babel-loader": "^8.2.2",
"html-webpack-plugin": "^4.5.0",
"webpack": "^4.30.0",
"webpack-cli": "^3.30",
"webpack-dev-server": "^3.7.2"
}

src/app.js

  • 不写内容

plugins/md-tohtml-plugin

compiler.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
const { randomNum } = require('./utils.js')
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) => {
// console.log(mdFragment)
const matched = mdFragment.match(reg_mark)
// console.log(matched);
if (matched) {
const mark = matched[1]

const input = matched['input']
// console.log(input)

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

// console.log(tag,tagContent);

//reg_sharp.test(_lastMark
if ((_lastMark === mark)) {
_htmlPool[tag].tags = [..._htmlPool[`${tag}-${_key}`], `<${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, '');
// console.log(tagContent);
const tag = `li`;
if (reg_crossbar.test(_lastMark)) {
_htmlPool[`ul-${_key}`].tags = [..._htmlPool[`ul-${_key}`].tags, `<${tag}>${tagContent}</${tag}>`]
} else {
_key = randomNum();
_lastMark = mark;
_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}>`]
}
}
}
}

})

// console.log(_htmlPool);
return _htmlPool;
}

function compileHTML(_mdArr) {
// console.log(_mdArr)
const _htmlPool = createTree(_mdArr)
// console.log(_htmlPool);
let _htmlStr = '';
let item;
for (let k in _htmlPool) {
console.log(k, _htmlPool[k]);
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-hash: {
* type:'single,
* tags: ['<h1>这是一个h1</h1>']
* },
* ul-hash: {
* type: 'wrap',
* tags:[
* '<li>这是ul的第1项</li>'
* '<li>这是ul的第2项</li>'
* '<li>这是ul的第3项</li>'
* '<li>这是ul的第4项</li>'
* ]
* }
* }
*/

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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:有相应的钩子集合
apply(compiler) {
// tap(一般叫插件名,(compilation)=>{}) , compilation.assets
compiler.hooks.emit.tap('md-to-html-plugin', (compilation) => {
const _assets = compilation.assets;
console.log(_assets);
// readFileSync 同步
const _mdContent = readFileSync(this.template, 'utf8');
const _templateHtml = readFileSync(resolve(__dirname, "template.html"), 'utf-8')
// console.log(_mdContent);
// 变为数组
const _mdContentArr = _mdContent.split('\n');
// console.log(_mdContentArr);
// 替换为标签
const _htmlStr = compileHTML(_mdContentArr)
// console.log(_htmlStr)
const _fileHtml = _templateHtml.replace(INNER_MARK, _htmlStr)

// 写入文件
_assets[this.filename] = {
source() {
return _fileHtml;
},
size() {
return _fileHtml.length;
}
}
})
}
}

module.exports = MdToHtmlPlugin;

template.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang='zh-CN'>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
</head>
<body>
<!--inner-->
</body>
</html>

utils.js

1
2
3
4
5
6
7
function randomNum() {
return new Date().getTime() + parseInt(Math.random() * 10000)
}

module.exports = {
randomNum
}

仓库地址