-
Notifications
You must be signed in to change notification settings - Fork 4.8k
/
index.js
129 lines (109 loc) Β· 3.68 KB
/
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
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
'use strict'
/**
* Module dependencies.
*/
const { EventEmitter } = require('events')
const { getOptions } = require('loader-utils')
const { fs, path, hash, parseFrontmatter, inferTitle, extractHeaders } = require('@vuepress/shared-utils')
const LRU = require('lru-cache')
const md = require('@vuepress/markdown')
const cache = new LRU({ max: 1000 })
const devCache = new LRU({ max: 1000 })
/**
* Expose markdown loader.
*/
module.exports = function (src) {
const isProd = process.env.NODE_ENV === 'production'
const isServer = this.target === 'node'
const options = getOptions(this)
const loader = Object.create(this)
const { sourceDir, extractHeaders: extractHeadersPattern = ['h2', 'h3'] } = options
let { markdown } = options
if (!markdown) {
markdown = md()
}
// we implement a manual cache here because this loader is chained before
// vue-loader, and will be applied on the same file multiple times when
// selecting the individual blocks.
const file = this.resourcePath
const key = hash(file + src)
const cached = cache.get(key)
if (cached && (isProd || /\?vue/.test(this.resourceQuery))) {
return cached
}
const frontmatter = parseFrontmatter(src)
const content = frontmatter.content
if (!isProd && !isServer) {
const inferredTitle = inferTitle(frontmatter.data, frontmatter.content)
const headers = extractHeaders(content, extractHeadersPattern, markdown)
delete frontmatter.content
// diff frontmatter and title, since they are not going to be part of the
// returned component, changes in frontmatter do not trigger proper updates
const cachedData = devCache.get(file)
if (cachedData && (
cachedData.inferredTitle !== inferredTitle
|| JSON.stringify(cachedData.frontmatterData) !== JSON.stringify(frontmatter.data)
|| headersChanged(cachedData.headers, headers)
)) {
// frontmatter changed... need to do a full reload
module.exports.frontmatterEmitter.emit('update', file)
}
devCache.set(file, {
headers,
frontmatterData: frontmatter.data,
inferredTitle
})
}
// the render method has been augmented to allow plugins to
// register data during render
const {
html,
data: { hoistedTags, links },
dataBlockString
} = markdown.render(content, {
loader,
frontmatter: frontmatter.data,
relativePath: path.relative(sourceDir, file).replace(/\\/g, '/')
})
// check if relative links are valid
links && links.forEach(link => {
link = decodeURIComponent(link)
const shortname = link
.replace(/#.*$/, '')
.replace(/\.html$/, '.md')
const filename = shortname
.replace(/\/$/, '/README.md')
.replace(/^\//, sourceDir + '/')
const altname = shortname
.replace(/\/$/, '/index.md')
.replace(/^\//, sourceDir + '/')
const dir = path.dirname(this.resourcePath)
const file = path.resolve(dir, filename)
const altfile = altname !== filename ? path.resolve(dir, altname) : null
if (!fs.existsSync(file) && (!altfile || !fs.existsSync(altfile))) {
this.emitWarning(
new Error(
`\nFile for relative link "${link}" does not exist.\n`
+ `(Resolved file: ${file})\n`
)
)
}
})
const res = (
`<template>\n`
+ `<ContentSlotsDistributor :slot-key="$parent.slotKey">${html}</ContentSlotsDistributor>\n`
+ `</template>\n`
+ (hoistedTags || []).join('\n')
+ `\n${dataBlockString}\n`
)
cache.set(key, res)
return res
}
function headersChanged (a, b) {
if (a.length !== b.length) return true
return a.some((h, i) => (
h.title !== b[i].title
|| h.level !== b[i].level
))
}
module.exports.frontmatterEmitter = new EventEmitter()