Skip to content

Attempt to enable inclusion of partial markdown #227

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
6 changes: 5 additions & 1 deletion lib/prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const tomlParser = require('toml')
const createMarkdown = require('./markdown')
const tempPath = path.resolve(__dirname, 'app/.temp')
const { inferTitle, extractHeaders, parseFrontmatter } = require('./util')
const { mdExplodeIncludes } = require('./webpack/util')

fs.ensureDirSync(tempPath)

Expand Down Expand Up @@ -187,7 +188,10 @@ async function resolveOptions (sourceDir) {
}

// extract yaml frontmatter
const content = await fs.readFile(path.resolve(sourceDir, file), 'utf-8')
const { explodedSrc: content } = await mdExplodeIncludes({
cwd: path.dirname(path.resolve(sourceDir, file)),
src: await fs.readFile(path.resolve(sourceDir, file), 'utf-8')
})
const frontmatter = parseFrontmatter(content)
// infer title
const title = inferTitle(frontmatter)
Expand Down
25 changes: 18 additions & 7 deletions lib/webpack/markdownLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,35 @@ 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 */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have un-indented the lines in the try { ... } catch block to show a better looking diff. Please re-indent when PR is accepted. 😊

const file = this.resourcePath
const dir = path.dirname(file)

const { explodedSrc, dependencies } = await mdExplodeIncludes({ cwd: 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)

// 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) {
Expand Down Expand Up @@ -66,7 +75,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))) {
Expand All @@ -86,7 +94,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) {
Expand Down
47 changes: 47 additions & 0 deletions lib/webpack/util.js
Original file line number Diff line number Diff line change
@@ -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 ({ cwd, src }) => {
const deps = []

return {
explodedSrc: await asyncReplace(src, /<!--\s*include\s+([^\s]+)\s*-->/g, async (match, path) => {
try {
const absolutePath = resolve(cwd, path)
const content = await readFile(absolutePath, 'utf8')

// recursively explode the included file
const { explodedSrc, dependencies } = await mdExplodeIncludes({
cwd: dirname(absolutePath),
src: content
})

deps.push(absolutePath, ...dependencies)
return explodedSrc
} catch (e) {
return match
}
}),
dependencies: deps
}
}