diff --git a/lib/prepare/resolveOptions.js b/lib/prepare/resolveOptions.js index 76fa2fcc78..6968f5e168 100644 --- a/lib/prepare/resolveOptions.js +++ b/lib/prepare/resolveOptions.js @@ -9,6 +9,7 @@ const { extractHeaders, parseFrontmatter } = require('../util/index') +const { mdExplodeIncludes } = require('../webpack/util') module.exports = async function resolveOptions (sourceDir) { const vuepressDir = path.resolve(sourceDir, '.vuepress') @@ -128,9 +129,15 @@ module.exports = async function resolveOptions (sourceDir) { data.lastUpdated = getGitLastUpdatedTimeStamp(filepath) } + // explode content in order to extract correct headers + const { explodedSrc: content } = await mdExplodeIncludes({ + dir: path.dirname(filepath), + src: await fs.readFile(filepath, 'utf-8') + }) + // extract yaml frontmatter - const content = await fs.readFile(filepath, 'utf-8') const frontmatter = parseFrontmatter(content) + // infer title const title = inferTitle(frontmatter) if (title) { diff --git a/lib/webpack/markdownLoader.js b/lib/webpack/markdownLoader.js index 3c49b747f6..7d563e6fe9 100644 --- a/lib/webpack/markdownLoader.js +++ b/lib/webpack/markdownLoader.js @@ -5,11 +5,22 @@ const { EventEmitter } = require('events') const { getOptions } = require('loader-utils') const { inferTitle, extractHeaders, parseFrontmatter } = require('../util') const LRU = require('lru-cache') +const { mdExplodeIncludes } = require('./util') const cache = LRU({ max: 1000 }) const devCache = LRU({ max: 1000 }) -module.exports = function (src) { +module.exports = async function (src) { + const cb = this.async() + try { +/* IMPORTANT: I didn't indent these lines to hopefully get a better looking diff */ + +// explode content of placeholders + const file = this.resourcePath + const dir = path.dirname(file) + const { explodedSrc, dependencies } = await mdExplodeIncludes({ dir, src }) + dependencies.forEach(d => this.addDependency(d)) + const isProd = process.env.NODE_ENV === 'production' const isServer = this.target === 'node' const { markdown, sourceDir } = getOptions(this) @@ -17,14 +28,13 @@ module.exports = function (src) { // 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 key = hash(file + explodedSrc) const cached = cache.get(key) if (cached && (isProd || /\?vue/.test(this.resourceQuery))) { - return cached + return cb(null, cached) } - const frontmatter = parseFrontmatter(src) + const frontmatter = parseFrontmatter(explodedSrc) const content = frontmatter.content if (!isProd && !isServer) { @@ -67,7 +77,6 @@ module.exports = function (src) { 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))) { @@ -87,7 +96,10 @@ module.exports = function (src) { (hoistedTags || []).join('\n') ) cache.set(key, res) - return res + return cb(null, res) + } catch (e) { + return cb(e) + } } function headersChanged (a, b) { diff --git a/lib/webpack/util.js b/lib/webpack/util.js new file mode 100644 index 0000000000..a1e66cfbd1 --- /dev/null +++ b/lib/webpack/util.js @@ -0,0 +1,47 @@ +const { readFile } = require('fs-extra') +const { resolve, dirname } = require('path') + +const asyncReplace = async (str, regex, aReplacer) => { + regex = new RegExp(regex, 'g') + const replacedParts = [] + let match + let i = 0 + while ((match = regex.exec(str)) !== null) { + // put non matching string + replacedParts.push(str.slice(i, match.index)) + // call the async replacer function with the matched array spreaded + replacedParts.push(aReplacer(...match)) + i = regex.lastIndex + } + + // put the rest of str + replacedParts.push(str.slice(i)) + + // wait for aReplacer calls to finish and join them back into string + return (await Promise.all(replacedParts)).join('') +} + +const mdExplodeIncludes = exports.mdExplodeIncludes = async ({ dir, src }) => { + const deps = [] + + return { + explodedSrc: await asyncReplace(src, //g, async (match, path) => { + try { + const absolutePath = resolve(dir, path) + const content = await readFile(absolutePath, 'utf8') + + // recursively explode the included file + const { explodedSrc, dependencies } = await mdExplodeIncludes({ + dir: dirname(absolutePath), + src: content + }) + + deps.push(absolutePath, ...dependencies) + return explodedSrc + } catch (e) { + return match + } + }), + dependencies: deps + } +}