diff --git a/packages/@vuepress/core/lib/internal-plugins/frontmatterBlock/index.js b/packages/@vuepress/core/lib/internal-plugins/frontmatterBlock/index.js new file mode 100644 index 0000000000..b49f7e07e7 --- /dev/null +++ b/packages/@vuepress/core/lib/internal-plugins/frontmatterBlock/index.js @@ -0,0 +1,12 @@ +module.exports = () => ({ + name: '@vuepress/internal-frontmatter-block', + + chainWebpack (config) { + config + .module + .rule('frontmatter-block') + .resourceQuery(/blockType=frontmatter/) + .use('frontmatter-block-loader') + .loader(require.resolve('./loader.js')) + } +}) diff --git a/packages/@vuepress/core/lib/internal-plugins/frontmatterBlock/loader.js b/packages/@vuepress/core/lib/internal-plugins/frontmatterBlock/loader.js new file mode 100644 index 0000000000..f57e19c4c0 --- /dev/null +++ b/packages/@vuepress/core/lib/internal-plugins/frontmatterBlock/loader.js @@ -0,0 +1,29 @@ +const { parseVueFrontmatter: { parseStrippedFrontmatter }} = require('@vuepress/shared-utils') +const { frontmatterEmitter } = require('@vuepress/markdown-loader') +const LRU = require('lru-cache') +const cache = LRU({ max: 1000 }) + +module.exports = function (source, map) { + const isProd = process.env.NODE_ENV === 'production' + + if (!isProd) { + const file = this.resourcePath + // frontmatter changed... need to do a full reload + const cached = cache.get(file) + const parsed = parseStrippedFrontmatter(source) + + if (cached && + cached.data && + parsed && + parsed.data && + JSON.stringify(cached.data) !== JSON.stringify(parsed.data) + ) { + // TODO Replace temporary files in bulk to avoid repeated refreshes. + frontmatterEmitter.emit('update') + } + + cache.set(file, parsed) + } + + this.callback(null, '', map) +} diff --git a/packages/@vuepress/core/lib/prepare/AppContext.js b/packages/@vuepress/core/lib/prepare/AppContext.js index 71961ea12d..f4fe95bbd7 100644 --- a/packages/@vuepress/core/lib/prepare/AppContext.js +++ b/packages/@vuepress/core/lib/prepare/AppContext.js @@ -126,6 +126,7 @@ module.exports = class AppContext { .use(require('../internal-plugins/pageComponents')) .use(require('../internal-plugins/transformModule')) .use(require('../internal-plugins/dataBlock')) + .use(require('../internal-plugins/frontmatterBlock')) .use('@vuepress/last-updated', !!shouldUseLastUpdated) .use('@vuepress/register-components', { componentsDir: [ @@ -244,7 +245,7 @@ module.exports = class AppContext { async resolvePages () { // resolve pageFiles - const patterns = ['**/*.md', '!.vuepress', '!node_modules'] + const patterns = ['**/*.md', '**/*.vue', '!.vuepress', '!node_modules'] if (this.siteConfig.dest) { // #654 exclude dest folder when dest dir was set in // sourceDir but not in '.vuepress' diff --git a/packages/@vuepress/core/lib/prepare/Page.js b/packages/@vuepress/core/lib/prepare/Page.js index a3a26a9b55..962bd1ba27 100644 --- a/packages/@vuepress/core/lib/prepare/Page.js +++ b/packages/@vuepress/core/lib/prepare/Page.js @@ -16,7 +16,8 @@ const { fileToPath, getPermalink, extractHeaders, - parseFrontmatter + parseFrontmatter, + parseVueFrontmatter } = require('@vuepress/shared-utils') /** @@ -95,29 +96,34 @@ module.exports = class Page { } if (this._content) { - const { excerpt, data, content } = parseFrontmatter(this._content) - this._strippedContent = content - this.frontmatter = data - - // infer title - const title = inferTitle(this.frontmatter, this._strippedContent) - if (title) { - this.title = title - } - - // headers - const headers = extractHeaders( - this._strippedContent, - ['h2', 'h3'], - markdown - ) - if (headers.length) { - this.headers = headers - } - - if (excerpt) { - const { html } = markdown.render(excerpt) - this.excerpt = html + if (this._filePath.endsWith('.md')) { + const { excerpt, data, content } = parseFrontmatter(this._content) + this._strippedContent = content + this.frontmatter = data + + // infer title + const title = inferTitle(this.frontmatter, this._strippedContent) + if (title) { + this.title = title + } + + // headers + const headers = extractHeaders( + this._strippedContent, + ['h2', 'h3'], + markdown + ) + if (headers.length) { + this.headers = headers + } + + if (excerpt) { + const { html } = markdown.render(excerpt) + this.excerpt = html + } + } else if (this._filePath.endsWith('.vue')) { + const { data = {}} = parseVueFrontmatter(this._content) + this.frontmatter = data } } diff --git a/packages/@vuepress/shared-utils/__tests__/isIndexFile.spec.js b/packages/@vuepress/shared-utils/__tests__/isIndexFile.spec.js index c7914d62cd..1c5cb05059 100644 --- a/packages/@vuepress/shared-utils/__tests__/isIndexFile.spec.js +++ b/packages/@vuepress/shared-utils/__tests__/isIndexFile.spec.js @@ -7,7 +7,13 @@ test('isIndexFile', () => { 'INDEX.md', 'index.md', 'foo/README.md', - 'foo/index.md' + 'foo/index.md', + 'README.vue', + 'readme.vue', + 'INDEX.vue', + 'index.vue', + 'foo/README.vue', + 'foo/index.vue' ].forEach(file => { expect(isIndexFile(file)).toBe(true) }); @@ -15,7 +21,11 @@ test('isIndexFile', () => { 'foo/one.md', 'one.md', 'one-index.md', - 'foo/one-index.md' + 'foo/one-index.md', + 'foo/one.vue', + 'one.vue', + 'one-index.vue', + 'foo/one-index.vue' ].forEach(file => { expect(isIndexFile(file)).toBe(false) }) diff --git a/packages/@vuepress/shared-utils/index.js b/packages/@vuepress/shared-utils/index.js index 49c2b134bc..7c047ca273 100644 --- a/packages/@vuepress/shared-utils/index.js +++ b/packages/@vuepress/shared-utils/index.js @@ -4,6 +4,7 @@ exports.codegen = require('./lib/codegen') exports.compose = require('./lib/compose') exports.datatypes = require('./lib/datatypes') exports.parseFrontmatter = require('./lib/parseFrontmatter') +exports.parseVueFrontmatter = require('./lib/parseVueFrontmatter') exports.unescapeHtml = require('./lib/unescapeHtml') exports.escapeHtml = require('escape-html') diff --git a/packages/@vuepress/shared-utils/lib/fileToPath.js b/packages/@vuepress/shared-utils/lib/fileToPath.js index 57bdf599ec..c5a0ad98d1 100644 --- a/packages/@vuepress/shared-utils/lib/fileToPath.js +++ b/packages/@vuepress/shared-utils/lib/fileToPath.js @@ -6,11 +6,15 @@ const extRE = /\.(vue|md)$/ module.exports = function fileToPath (file) { if (isIndexFile(file)) { // README.md -> / + // README.vue -> / // foo/README.md -> /foo/ + // foo/README.vue -> /foo/ return file.replace(indexRE, '/$1') } else { // foo.md -> /foo.html + // foo.vue -> /foo.html // foo/bar.md -> /foo/bar.html + // foo/bar.vue -> /foo/bar.html return `/${file.replace(extRE, '').replace(/\\/g, '/')}.html` } } diff --git a/packages/@vuepress/shared-utils/lib/isIndexFile.js b/packages/@vuepress/shared-utils/lib/isIndexFile.js index 53e7df9141..cfe80ef3b8 100644 --- a/packages/@vuepress/shared-utils/lib/isIndexFile.js +++ b/packages/@vuepress/shared-utils/lib/isIndexFile.js @@ -1,4 +1,4 @@ -const indexRE = /(^|.*\/)(index|readme)\.md$/i +const indexRE = /(^|.*\/)(index|readme)\.(md|vue)$/i function isIndexFile (file) { return indexRE.test(file) diff --git a/packages/@vuepress/shared-utils/lib/parseVueFrontmatter.js b/packages/@vuepress/shared-utils/lib/parseVueFrontmatter.js new file mode 100644 index 0000000000..91f1c7c6c5 --- /dev/null +++ b/packages/@vuepress/shared-utils/lib/parseVueFrontmatter.js @@ -0,0 +1,24 @@ +const compiler = require('vue-template-compiler') +const { parse } = require('@vue/component-compiler-utils') +const parseFrontmatter = require('./parseFrontmatter') + +function parseStrippedFrontmatter (src) { + src = `---\n${src}\n---` + return parseFrontmatter(src) +} + +module.exports = src => { + const output = parse({ + source: src, + compiler, + needMap: false + }) + const find = output.customBlocks.find(block => block.type === 'frontmatter') + const frontmatterRaw = find && find.content + if (frontmatterRaw) { + return parseStrippedFrontmatter(frontmatterRaw) + } + return {} +} + +module.exports.parseStrippedFrontmatter = parseStrippedFrontmatter