From 0af8373a9b6dd03a6b5fe42e10b34fc253492e0f Mon Sep 17 00:00:00 2001 From: ULIVZ <472590061@qq.com> Date: Wed, 20 Feb 2019 02:13:55 +0800 Subject: [PATCH 01/10] feat: init --- __mocks__/vuepress-theme-child/Layout.vue | 0 .../vuepress-theme-child/components/Home.vue | 0 __mocks__/vuepress-theme-child/index.js | 3 + __mocks__/vuepress-theme-parent/Layout.vue | 0 .../vuepress-theme-parent/components/Home.vue | 0 .../components/Sidebar.vue | 0 __mocks__/vuepress-theme-parent/index.js | 1 + .../theme-api/fixtures/theme/Layout.vue | 0 .../fixtures/theme/components/Home.vue | 0 .../core/__tests__/theme-api/index.spec.js | 27 +++ .../core/lib/internal-plugins/enhanceApp.js | 10 +- .../core/lib/internal-plugins/style/index.js | 8 +- .../@vuepress/core/lib/prepare/AppContext.js | 42 ++-- .../@vuepress/core/lib/prepare/loadTheme.js | 198 +++++------------- .../core/lib/theme-api/Layout.fallback.vue | 3 + .../@vuepress/core/lib/theme-api/index.js | 141 +++++++++++++ .../core/lib/webpack/createBaseConfig.js | 2 - .../theme-default/components/DropdownLink.vue | 2 +- .../@vuepress/theme-default/layouts/Home.vue | 162 ++++++++++++++ 19 files changed, 423 insertions(+), 176 deletions(-) create mode 100644 __mocks__/vuepress-theme-child/Layout.vue create mode 100644 __mocks__/vuepress-theme-child/components/Home.vue create mode 100644 __mocks__/vuepress-theme-child/index.js create mode 100644 __mocks__/vuepress-theme-parent/Layout.vue create mode 100644 __mocks__/vuepress-theme-parent/components/Home.vue create mode 100644 __mocks__/vuepress-theme-parent/components/Sidebar.vue create mode 100644 __mocks__/vuepress-theme-parent/index.js create mode 100644 packages/@vuepress/core/__tests__/theme-api/fixtures/theme/Layout.vue create mode 100644 packages/@vuepress/core/__tests__/theme-api/fixtures/theme/components/Home.vue create mode 100644 packages/@vuepress/core/__tests__/theme-api/index.spec.js create mode 100644 packages/@vuepress/core/lib/theme-api/Layout.fallback.vue create mode 100644 packages/@vuepress/core/lib/theme-api/index.js create mode 100644 packages/@vuepress/theme-default/layouts/Home.vue diff --git a/__mocks__/vuepress-theme-child/Layout.vue b/__mocks__/vuepress-theme-child/Layout.vue new file mode 100644 index 0000000000..e69de29bb2 diff --git a/__mocks__/vuepress-theme-child/components/Home.vue b/__mocks__/vuepress-theme-child/components/Home.vue new file mode 100644 index 0000000000..e69de29bb2 diff --git a/__mocks__/vuepress-theme-child/index.js b/__mocks__/vuepress-theme-child/index.js new file mode 100644 index 0000000000..8cf79b81cc --- /dev/null +++ b/__mocks__/vuepress-theme-child/index.js @@ -0,0 +1,3 @@ +module.exports = { + extend: 'vuepress-theme-parent' +} diff --git a/__mocks__/vuepress-theme-parent/Layout.vue b/__mocks__/vuepress-theme-parent/Layout.vue new file mode 100644 index 0000000000..e69de29bb2 diff --git a/__mocks__/vuepress-theme-parent/components/Home.vue b/__mocks__/vuepress-theme-parent/components/Home.vue new file mode 100644 index 0000000000..e69de29bb2 diff --git a/__mocks__/vuepress-theme-parent/components/Sidebar.vue b/__mocks__/vuepress-theme-parent/components/Sidebar.vue new file mode 100644 index 0000000000..e69de29bb2 diff --git a/__mocks__/vuepress-theme-parent/index.js b/__mocks__/vuepress-theme-parent/index.js new file mode 100644 index 0000000000..4ba52ba2c8 --- /dev/null +++ b/__mocks__/vuepress-theme-parent/index.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/packages/@vuepress/core/__tests__/theme-api/fixtures/theme/Layout.vue b/packages/@vuepress/core/__tests__/theme-api/fixtures/theme/Layout.vue new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/@vuepress/core/__tests__/theme-api/fixtures/theme/components/Home.vue b/packages/@vuepress/core/__tests__/theme-api/fixtures/theme/components/Home.vue new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/@vuepress/core/__tests__/theme-api/index.spec.js b/packages/@vuepress/core/__tests__/theme-api/index.spec.js new file mode 100644 index 0000000000..331889692b --- /dev/null +++ b/packages/@vuepress/core/__tests__/theme-api/index.spec.js @@ -0,0 +1,27 @@ +jest.mock('vuepress-theme-parent') +jest.mock('vuepress-theme-child') + +import ThemeAPI from '../../lib/theme-api' +import { resolve } from 'path' + +const theme = { + path: resolve(process.cwd(), '__mocks__/vuepress-theme-child'), + name: 'vuepress-theme-child', + shortcut: 'child', + entryFile: require('vuepress-theme-child') +} + +const parent = { + path: resolve(process.cwd(), '__mocks__/vuepress-theme-parent'), + name: 'vuepress-theme-parent', + shortcut: 'parent', + entryFile: {} +} + +describe('ThemeAPI', () => { + test('extend', async () => { + const themeAPI = new ThemeAPI(theme, parent) + console.log(themeAPI.theme.entryFile) + }) + // loadTheme('vuepress-theme-child') +}) diff --git a/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js b/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js index 5aa987c38a..4fb1393fb8 100644 --- a/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js +++ b/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js @@ -4,12 +4,16 @@ module.exports = (options, context) => ({ name: '@vuepress/internal-enhance-app', enhanceAppFiles () { - const { sourceDir, themePath } = context + const { sourceDir, themeAPI } = context const enhanceAppPath = path.resolve(sourceDir, '.vuepress/enhanceApp.js') - const themeEnhanceAppPath = path.resolve(themePath, 'enhanceApp.js') - return [ + const themeEnhanceAppPath = path.resolve(themeAPI.theme.path, 'enhanceApp.js') + const files = [ enhanceAppPath, themeEnhanceAppPath ] + if (themeAPI.existsParentTheme) { + files.push(path.resolve(themeAPI.parentTheme.path, 'enhanceApp.js')) + } + return files } }) diff --git a/packages/@vuepress/core/lib/internal-plugins/style/index.js b/packages/@vuepress/core/lib/internal-plugins/style/index.js index a0646d7d9c..38d864b9ca 100644 --- a/packages/@vuepress/core/lib/internal-plugins/style/index.js +++ b/packages/@vuepress/core/lib/internal-plugins/style/index.js @@ -6,7 +6,7 @@ module.exports = (options, ctx) => ({ enhanceAppFiles: [path.resolve(__dirname, 'client.js')], async ready () { - const { sourceDir, writeTemp } = ctx + const { sourceDir, writeTemp, themeAPI } = ctx const overridePath = path.resolve(sourceDir, '.vuepress/override.styl') const hasUserOverride = fs.existsSync(overridePath) @@ -15,7 +15,7 @@ module.exports = (options, ctx) => ({ logger.tip(`${chalk.magenta('override.styl')} has been deprecated from v1.0.0, using ${chalk.cyan('.vuepress/styles/palette.styl')} instead.\n`) } - const themeStyle = path.resolve(ctx.themePath, 'styles/index.styl') + const themeStyle = path.resolve(themeAPI.themePath, 'styles/index.styl') const userStyle = path.resolve(sourceDir, '.vuepress/styles/index.styl') const themeStyleContent = fs.existsSync(themeStyle) @@ -28,8 +28,8 @@ module.exports = (options, ctx) => ({ let styleContent = themeStyleContent + userStyleContent - if (ctx.parentThemePath) { - const parentThemeStyle = path.resolve(ctx.parentThemePath, 'styles/index.styl') + if (themeAPI.existsParentTheme) { + const parentThemeStyle = path.resolve(themeAPI.parentThemePath, 'styles/index.styl') const parentThemeStyleContent = fs.existsSync(parentThemeStyle) ? `@import(${JSON.stringify(parentThemeStyle.replace(/[\\]+/g, '/'))})` : '' diff --git a/packages/@vuepress/core/lib/prepare/AppContext.js b/packages/@vuepress/core/lib/prepare/AppContext.js index e92f2bc312..00b9614651 100644 --- a/packages/@vuepress/core/lib/prepare/AppContext.js +++ b/packages/@vuepress/core/lib/prepare/AppContext.js @@ -94,7 +94,10 @@ module.exports = class AppContext { this.resolveConfigAndInitialize() this.resolveCacheLoaderOptions() this.normalizeHeadTagUrls() - await this.resolveTheme() + /** + * @type {ThemeAPI} + */ + this.themeAPI = loadTheme(this) this.resolveTemplates() this.resolveGlobalLayout() @@ -137,7 +140,7 @@ module.exports = class AppContext { ) this.pluginAPI - // internl core plugins + // internl core plugins .use(require('../internal-plugins/siteData')) .use(require('../internal-plugins/routes')) .use(require('../internal-plugins/rootMixins')) @@ -153,8 +156,8 @@ module.exports = class AppContext { .use('@vuepress/register-components', { componentsDir: [ path.resolve(this.sourceDir, '.vuepress/components'), - path.resolve(this.themePath, 'global-components'), - this.parentThemePath && path.resolve(this.parentThemePath, 'global-components') + path.resolve(this.themeAPI.themePath, 'global-components'), + this.themeAPI.existsParentTheme && path.resolve(this.themeAPI.parentThemePath, 'global-components') ] }) } @@ -167,11 +170,11 @@ module.exports = class AppContext { applyUserPlugins () { this.pluginAPI.useByPluginsConfig(this.cliOptions.plugins) - if (this.parentThemePath) { - this.pluginAPI.use(this.parentThemeEntryFile) + if (this.themeAPI.existsParentTheme) { + this.pluginAPI.use(this.themeAPI.parentThemePath) } this.pluginAPI - .use(this.themeEntryFile) + .use(this.themeAPI.themePath) .use(Object.assign({}, this.siteConfig, { name: '@vuepress/internal-site-config' })) } @@ -227,11 +230,11 @@ module.exports = class AppContext { const siteSsrTemplate2 = path.resolve(templateDir, 'ssr.html') const siteDevTemplate2 = path.resolve(templateDir, 'dev.html') - const themeSsrTemplate = path.resolve(this.themePath, 'templates/ssr.html') - const themeDevTemplate = path.resolve(this.themePath, 'templates/dev.html') + const themeSsrTemplate = path.resolve(this.themeAPI.themePath, 'templates/ssr.html') + const themeDevTemplate = path.resolve(this.themeAPI.themePath, 'templates/dev.html') - const parentThemeSsrTemplate = path.resolve(this.themePath, 'templates/ssr.html') - const parentThemeDevTemplate = path.resolve(this.themePath, 'templates/dev.html') + const parentThemeSsrTemplate = path.resolve(this.themeAPI.parentThemePath, 'templates/ssr.html') + const parentThemeDevTemplate = path.resolve(this.themeAPI.parentThemePath, 'templates/dev.html') const defaultSsrTemplate = path.resolve(__dirname, '../app/index.ssr.html') const defaultDevTemplate = path.resolve(__dirname, '../app/index.dev.html') @@ -354,17 +357,6 @@ module.exports = class AppContext { this.pages.push(page) } - /** - * Resolve theme - * - * @returns {Promise} - * @api private - */ - - async resolveTheme () { - Object.assign(this, (await loadTheme(this))) - } - /** * Get config value of current active theme. * @@ -386,12 +378,12 @@ module.exports = class AppContext { */ resolveThemeAgreementFile (filepath) { - const current = path.resolve(this.themePath, filepath) + const current = path.resolve(this.themeAPI.themePath, filepath) if (fs.existsSync(current)) { return current } - if (this.parentThemePath) { - const parent = path.resolve(this.parentThemePath, filepath) + if (this.themeAPI.existsParentTheme) { + const parent = path.resolve(this.themeAPI.parentThemePath, filepath) if (fs.existsSync(parent)) { return parent } diff --git a/packages/@vuepress/core/lib/prepare/loadTheme.js b/packages/@vuepress/core/lib/prepare/loadTheme.js index f07804cddb..a14b1f74d8 100644 --- a/packages/@vuepress/core/lib/prepare/loadTheme.js +++ b/packages/@vuepress/core/lib/prepare/loadTheme.js @@ -5,11 +5,13 @@ */ const { - fs, path, + fs, + path: { resolve, parse }, moduleResolver: { getThemeResolver }, datatypes: { isString }, logger, chalk } = require('@vuepress/shared-utils') +const ThemeAPI = require('../theme-api') /** * Resolve theme. @@ -25,164 +27,78 @@ const { * @param {string} theme * @param {string} sourceDir * @param {string} vuepressDir - * @returns {Promise} + * @returns {ThemeAPI} */ -module.exports = async function loadTheme (ctx) { - const { siteConfig, cliOptions, sourceDir, vuepressDir, pluginAPI } = ctx - const theme = siteConfig.theme || cliOptions.theme +module.exports = function loadTheme (ctx) { const themeResolver = getThemeResolver() - const localThemePath = path.resolve(vuepressDir, 'theme') - const useLocalTheme - = !fs.existsSync(theme) - && fs.existsSync(localThemePath) - && (fs.readdirSync(localThemePath)).length > 0 - - let themePath = null // Mandatory - let themeEntryFile = {} // Optional - let themeName - let themeShortcut - let parentThemePath = null // Optional - let parentThemeEntryFile = {} // Optional - - if (useLocalTheme) { - themePath = localThemePath - logger.tip(`Apply theme located at ${chalk.gray(themePath)}...`) - } else if (isString(theme)) { - const resolved = themeResolver.resolve(theme, sourceDir) - const { entry, name, shortcut } = resolved - - if (entry === null) { - throw new Error(`Cannot resolve theme ${theme}.`) - } - - themePath = normalizeThemePath(resolved) - themeName = name - themeShortcut = shortcut - logger.tip(`Apply theme ${chalk.gray(themeName)}`) - } else { + const theme = resolveTheme(themeResolver, ctx) + if (!theme.path) { throw new Error(`[vuepress] You must specify a theme, or create a local custom theme. \n For more details, refer to https://vuepress.vuejs.org/guide/custom-themes.html#custom-themes. \n`) } + theme.entryFile.name = '@vuepress/internal-theme-entry-file' - try { - themeEntryFile = pluginAPI.normalizePlugin(themePath, ctx.themeConfig) - } catch (error) { - themeEntryFile = {} + let parentTheme = {} + if (theme.entryFile.extend) { + parentTheme = resolveTheme(themeResolver, ctx, true, theme.entryFile.extend) + parentTheme.entryFile.name = '@vuepress/internal-parent-theme-entry-file' } - themeEntryFile.name = '@vuepress/internal-theme-entry-file' - themeEntryFile.shortcut = null - - // handle theme api - const layoutDirs = [ - path.resolve(themePath, '.'), - path.resolve(themePath, 'layouts') - ] - - if (themeEntryFile.extend) { - const resolved = themeResolver.resolve(themeEntryFile.extend, sourceDir) - if (resolved.entry === null) { - throw new Error(`Cannot resolve parent theme ${themeEntryFile.extend}.`) - } - parentThemePath = normalizeThemePath(resolved) - - try { - parentThemeEntryFile = pluginAPI.normalizePlugin(parentThemePath, ctx.themeConfig) - } catch (error) { - parentThemeEntryFile = {} - } - - parentThemeEntryFile.name = '@vuepress/internal-parent-theme-entry-file' - parentThemeEntryFile.shortcut = null - - layoutDirs.unshift( - path.resolve(parentThemePath, '.'), - path.resolve(parentThemePath, 'layouts'), - ) - - themeEntryFile.alias = Object.assign( - themeEntryFile.alias || {}, - { '@parent-theme': parentThemePath } - ) - } + return new ThemeAPI(theme, parentTheme, ctx) +} - // normalize component name - const getComponentName = filename => { - filename = filename.slice(0, -4) - if (filename === '404') { - filename = 'NotFound' - } - return filename +function normalizeThemePath (resolved) { + const { entry, name, fromDep } = resolved + if (fromDep) { + const pkgPath = require.resolve(name) + return parse(pkgPath).dir + } else if (entry.endsWith('.js') || entry.endsWith('.vue')) { + return parse(entry).dir + } else { + return entry } +} - const readdirSync = dir => fs.existsSync(dir) && fs.readdirSync(dir) || [] - - // built-in named layout or not. - const isInternal = componentName => componentName === 'Layout' - || componentName === 'NotFound' - - const layoutComponentMap = layoutDirs - .map( - layoutDir => readdirSync(layoutDir) - .filter(filename => filename.endsWith('.vue')) - .map(filename => { - const componentName = getComponentName(filename) - return { - filename, - componentName, - isInternal: isInternal(componentName), - path: path.resolve(layoutDir, filename) - } - }) - ) - - .reduce((arr, next) => { - arr.push(...next) - return arr - }, []) - - .reduce((map, component) => { - map[component.componentName] = component - return map - }, {}) +function resolveTheme (ctx, resolver, ignoreLocal, theme) { + const { siteConfig, cliOptions, sourceDir, vuepressDir, pluginAPI } = ctx + const localThemePath = resolve(vuepressDir, 'theme') + theme = siteConfig.theme || cliOptions.theme - const { Layout = {}, NotFound = {}} = layoutComponentMap + let path + let name + let shortcut + let entryFile = {} - // layout component does not exist. - if (!Layout || !fs.existsSync(Layout.path)) { - throw new Error(`[vuepress] Cannot resolve Layout.vue file in \n ${Layout.path}`) - } + // 1. From local + if (!ignoreLocal + && !fs.existsSync(theme) + && fs.existsSync(localThemePath) + && (fs.readdirSync(localThemePath)).length > 0 + ) { + path = localThemePath + name = shortcut = 'local' + logger.tip(`Apply local theme at ${chalk.gray(path)}...`) - // use default 404 component. - if (!NotFound || !fs.existsSync(NotFound.path)) { - layoutComponentMap.NotFound = { - filename: 'NotFound.vue', - componentName: 'NotFound', - path: path.resolve(__dirname, '../app/components/NotFound.vue'), - isInternal: true + // 2. From dep + } else if (isString(theme)) { + const resolved = resolver.resolve(theme, sourceDir) + if (resolved.entry === null) { + throw new Error(`Cannot resolve theme: ${theme}.`) } + path = normalizeThemePath(resolved) + name = resolved.name + shortcut = resolved.shortcut + logger.tip(`Apply theme ${chalk.gray(name)}`) + } else { + return {} } - return { - themePath, - layoutComponentMap, - themeEntryFile, - themeName, - themeShortcut, - parentThemePath, - parentThemeEntryFile + try { + entryFile = pluginAPI.normalizePlugin(path, ctx.themeConfig) + } catch (error) { + entryFile = {} } -} -function normalizeThemePath (resolved) { - const { entry, name, fromDep } = resolved - if (fromDep) { - const pkgPath = require.resolve(name) - return path.parse(pkgPath).dir - } else if (entry.endsWith('.js') || entry.endsWith('.vue')) { - return path.parse(entry).dir - } else { - return entry - } + return { path, name, shortcut, entryFile } } diff --git a/packages/@vuepress/core/lib/theme-api/Layout.fallback.vue b/packages/@vuepress/core/lib/theme-api/Layout.fallback.vue new file mode 100644 index 0000000000..34d84c1b5f --- /dev/null +++ b/packages/@vuepress/core/lib/theme-api/Layout.fallback.vue @@ -0,0 +1,3 @@ + diff --git a/packages/@vuepress/core/lib/theme-api/index.js b/packages/@vuepress/core/lib/theme-api/index.js new file mode 100644 index 0000000000..a3e8c409f5 --- /dev/null +++ b/packages/@vuepress/core/lib/theme-api/index.js @@ -0,0 +1,141 @@ +const { logger, fs, path: { resolve }} = require('@vuepress/shared-utils') +const readdirSync = dir => fs.existsSync(dir) && fs.readdirSync(dir) || [] + +module.exports = class ThemeAPI { + constructor (theme, parentTheme, ctx) { + this.theme = theme + this.parentTheme = parentTheme || {} + this.ctx = ctx + this.existsParentTheme = !!this.parentTheme.path + this.init() + } + + setAlias (alias) { + this.theme.entryFile.alias = { + ...(this.theme.entryFile.alias || {}), + alias + } + } + + get themePath () { + return this.theme.path + } + + get parentThemePath () { + return this.parentTheme.path + } + + init () { + const alias = { + '@current-theme': this.theme.path + } + this.setAlias() + if (this.existsParentTheme) { + alias['@parent-theme'] = this.parentTheme.path + } + this.componentMap = this.getComponents() + this.layoutComponentMap = this.getLayoutComponentMap() + + Object.keys(this.componentMap).forEach((name) => { + const { filename, path } = this.componentMap[name] + alias[`@theme/components/${filename}`] = path + }) + + Object.keys(this.layoutComponentMap).forEach((name) => { + const { filename, path } = this.layoutComponentMap[name] + alias[`@theme/layouts/${filename}`] = path + }) + this.setAlias(alias) + } + + getComponents () { + const componentDirs = [ + resolve(this.theme.path, 'components') + ] + if (this.existsParentTheme) { + componentDirs.unshift( + resolve(this.parentTheme.path, 'components'), + ) + } + return resolveSFCs(componentDirs) + } + + getLayoutComponentMap () { + const layoutDirs = [ + resolve(this.theme.path, '.'), + resolve(this.theme.path, 'layouts') + ] + if (this.existsParentTheme) { + layoutDirs.unshift( + resolve(this.parentTheme.path, '.'), + resolve(this.parentTheme.path, 'layouts'), + ) + } + // built-in named layout or not. + const layoutComponentMap = resolveSFCs(layoutDirs) + + const { Layout = {}, NotFound = {}} = layoutComponentMap + // layout component does not exist. + if (!Layout || !fs.existsSync(Layout.path)) { + const fallbackLayoutPath = resolve(__dirname, 'Layout.fallback.vue') + layoutComponentMap.Layout = { + filename: 'Layout.vue', + componentName: 'Layout', + path: fallbackLayoutPath, + isInternal: true + } + logger.warn( + `[vuepress] Cannot resolve Layout.vue file in \n ${Layout.path},` + + `fallback to default layout: ${fallbackLayoutPath}` + ) + } + if (!NotFound || !fs.existsSync(NotFound.path)) { + layoutComponentMap.NotFound = { + filename: 'NotFound.vue', + componentName: 'NotFound', + path: resolve(__dirname, '../app/components/NotFound.vue'), + isInternal: true + } + } + return layoutComponentMap + } +} + +function resolveSFCs (dirs) { + return dirs.map( + layoutDir => readdirSync(layoutDir) + .filter(filename => filename.endsWith('.vue')) + .map(filename => { + const componentName = getComponentName(filename) + return { + filename, + componentName, + isInternal: isInternal(componentName), + path: resolve(layoutDir, filename) + } + }) + ).reduce((arr, next) => { + arr.push(...next) + return arr + }, []).reduce((map, component) => { + map[component.componentName] = component + return map + }, {}) +} + +/** + * normalize component name + * @param {strin} filename + * @returns {string} + */ +function getComponentName (filename) { + filename = filename.slice(0, -4) + if (filename === '404') { + filename = 'NotFound' + } + return filename +} + +function isInternal (name) { + return name === 'Layout' || name === 'NotFound' +} diff --git a/packages/@vuepress/core/lib/webpack/createBaseConfig.js b/packages/@vuepress/core/lib/webpack/createBaseConfig.js index c6b5a90cc1..bf79b2072f 100644 --- a/packages/@vuepress/core/lib/webpack/createBaseConfig.js +++ b/packages/@vuepress/core/lib/webpack/createBaseConfig.js @@ -15,7 +15,6 @@ module.exports = function createBaseConfig ({ sourceDir, outDir, base: publicPath, - themePath, markdown, tempPath, cacheDirectory, @@ -52,7 +51,6 @@ module.exports = function createBaseConfig ({ config.resolve .set('symlinks', true) .alias - .set('@theme', themePath) .set('@source', sourceDir) .set('@app', path.resolve(__dirname, '../app')) .set('@temp', tempPath) diff --git a/packages/@vuepress/theme-default/components/DropdownLink.vue b/packages/@vuepress/theme-default/components/DropdownLink.vue index ec45fae957..1d51d80bfc 100644 --- a/packages/@vuepress/theme-default/components/DropdownLink.vue +++ b/packages/@vuepress/theme-default/components/DropdownLink.vue @@ -50,7 +50,7 @@ + + From 814a9ca50809562c869ba2780b82d1394f7943ef Mon Sep 17 00:00:00 2001 From: ULIVZ <472590061@qq.com> Date: Wed, 20 Feb 2019 02:23:48 +0800 Subject: [PATCH 02/10] chore: tweaks --- packages/@vuepress/core/lib/internal-plugins/enhanceApp.js | 4 ++-- packages/docs/docs/miscellaneous/glossary.md | 4 ++-- packages/docs/docs/plugin/context-api.md | 6 ------ packages/docs/docs/plugin/option-api.md | 4 ++-- packages/docs/docs/zh/miscellaneous/glossary.md | 4 ++-- packages/docs/docs/zh/plugin/context-api.md | 6 ------ packages/docs/docs/zh/plugin/option-api.md | 4 ++-- 7 files changed, 10 insertions(+), 22 deletions(-) diff --git a/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js b/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js index 4fb1393fb8..d10d65b5cb 100644 --- a/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js +++ b/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js @@ -6,13 +6,13 @@ module.exports = (options, context) => ({ enhanceAppFiles () { const { sourceDir, themeAPI } = context const enhanceAppPath = path.resolve(sourceDir, '.vuepress/enhanceApp.js') - const themeEnhanceAppPath = path.resolve(themeAPI.theme.path, 'enhanceApp.js') + const themeEnhanceAppPath = path.resolve(themeAPI.themePath, 'enhanceApp.js') const files = [ enhanceAppPath, themeEnhanceAppPath ] if (themeAPI.existsParentTheme) { - files.push(path.resolve(themeAPI.parentTheme.path, 'enhanceApp.js')) + files.push(path.resolve(themeAPI.parentThemePath, 'enhanceApp.js')) } return files } diff --git a/packages/docs/docs/miscellaneous/glossary.md b/packages/docs/docs/miscellaneous/glossary.md index b30c09e9e0..f6d36bd83c 100644 --- a/packages/docs/docs/miscellaneous/glossary.md +++ b/packages/docs/docs/miscellaneous/glossary.md @@ -52,13 +52,13 @@ Value of `themeConfig` in `.vuepress/config.js`, i.e., `user's theme configurati ## themePath -> Access: `Context.themePath` +> Access: `Context.themeAPI.theme.path` Root path (absolute path) of the currently used theme. ## themeEntryFile -> Access: `Context.themeEntryFile` +> Access: `Context.themeAPI.theme.entry` Theme's configuration file (`themePath/index.js`). diff --git a/packages/docs/docs/plugin/context-api.md b/packages/docs/docs/plugin/context-api.md index 6c725660d1..a3c1dae32b 100644 --- a/packages/docs/docs/plugin/context-api.md +++ b/packages/docs/docs/plugin/context-api.md @@ -42,12 +42,6 @@ Root directory where the temporary files are located. Output path. -## ctx.themePath - -- Type: `string` - -The path of the currently active theme. - ## ctx.base - Type: `string` diff --git a/packages/docs/docs/plugin/option-api.md b/packages/docs/docs/plugin/option-api.md index 1745ada016..f216161025 100644 --- a/packages/docs/docs/plugin/option-api.md +++ b/packages/docs/docs/plugin/option-api.md @@ -117,7 +117,7 @@ We can set aliases via [chainWebpack](#chainwebpack): ```js module.exports = (options, context) => ({ chainWebpack (config) { - config.resolve.alias.set('@theme', context.themePath) + config.resolve.alias.set('@pwd', process.cwd()) } }) ``` @@ -127,7 +127,7 @@ But `alias` option makes this process more like configuration: ```js module.exports = (options, context) => ({ alias: { - '@theme': context.themePath + '@pwd': process.cwd() } }) ``` diff --git a/packages/docs/docs/zh/miscellaneous/glossary.md b/packages/docs/docs/zh/miscellaneous/glossary.md index 95419a4aa2..4f31bdca25 100644 --- a/packages/docs/docs/zh/miscellaneous/glossary.md +++ b/packages/docs/docs/zh/miscellaneous/glossary.md @@ -52,13 +52,13 @@ VuePress 的动态布局系统等特性是基于 `frontmatter` 实现的,你 ## themePath -> Access: `Context.themePath` +> Access: `Context.themeAPI.theme.path` 当前使用的主题的根路径(绝对路径)。 ## themeEntryFile -> Access: `Context.themeEntryFile` +> Access: `Context.themeAPI.theme.entry` 主题的配置文件 (`themePath/index.js`)。 diff --git a/packages/docs/docs/zh/plugin/context-api.md b/packages/docs/docs/zh/plugin/context-api.md index dfee98d9df..af72f8d015 100644 --- a/packages/docs/docs/zh/plugin/context-api.md +++ b/packages/docs/docs/zh/plugin/context-api.md @@ -42,12 +42,6 @@ VuePress 是否运行在生产环境模式下。 输出目录。 -## ctx.themePath - -- 类型: `string` - -当前应用的主题的根路径。 - ## ctx.base - 类型: `string` diff --git a/packages/docs/docs/zh/plugin/option-api.md b/packages/docs/docs/zh/plugin/option-api.md index e92d839814..2ea8e80c0d 100644 --- a/packages/docs/docs/zh/plugin/option-api.md +++ b/packages/docs/docs/zh/plugin/option-api.md @@ -121,7 +121,7 @@ module.exports = (options, context) => ({ ```js module.exports = (options, context) => ({ chainWebpack (config) { - config.resolve.alias.set('@theme', context.themePath) + config.resolve.alias.set('@pwd', process.cwd()) } }) ``` @@ -131,7 +131,7 @@ module.exports = (options, context) => ({ ```js module.exports = (options, context) => ({ alias: { - '@theme': context.themePath + '@theme': context.themeAPI.themePath } }) ``` From fe266e2b907b2a7d9eca1556483e968532ef138f Mon Sep 17 00:00:00 2001 From: ULIVZ <472590061@qq.com> Date: Thu, 21 Feb 2019 01:20:30 +0800 Subject: [PATCH 03/10] feat: enhancement and make it work --- .../core/__tests__/theme-api/index.spec.js | 2 +- .../core/lib/internal-plugins/enhanceApp.js | 4 +- .../lib/internal-plugins/layoutComponents.js | 13 +--- .../lib/internal-plugins/palette/index.js | 6 +- .../core/lib/internal-plugins/style/index.js | 8 ++- .../@vuepress/core/lib/prepare/AppContext.js | 70 +++++++------------ .../@vuepress/core/lib/prepare/loadTheme.js | 20 +++--- .../@vuepress/core/lib/theme-api/index.js | 38 ++++++---- .../theme-default/components/DropdownLink.vue | 2 +- .../theme-default/components/Home.vue | 2 +- .../theme-default/components/NavLinks.vue | 4 +- .../theme-default/components/Navbar.vue | 4 +- .../theme-default/components/Sidebar.vue | 4 +- .../theme-default/components/SidebarGroup.vue | 2 +- .../theme-default/components/SidebarLinks.vue | 4 +- .../theme-default/layouts/Layout.vue | 8 +-- .../layouts => theme-vue/components}/Home.vue | 2 +- .../@vuepress/theme-vue/layouts/Layout.vue | 4 +- 18 files changed, 93 insertions(+), 104 deletions(-) rename packages/@vuepress/{theme-default/layouts => theme-vue/components}/Home.vue (98%) diff --git a/packages/@vuepress/core/__tests__/theme-api/index.spec.js b/packages/@vuepress/core/__tests__/theme-api/index.spec.js index 331889692b..a8e6c7bdd4 100644 --- a/packages/@vuepress/core/__tests__/theme-api/index.spec.js +++ b/packages/@vuepress/core/__tests__/theme-api/index.spec.js @@ -21,7 +21,7 @@ const parent = { describe('ThemeAPI', () => { test('extend', async () => { const themeAPI = new ThemeAPI(theme, parent) - console.log(themeAPI.theme.entryFile) + console.log(themeAPI.theme.entry) }) // loadTheme('vuepress-theme-child') }) diff --git a/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js b/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js index d10d65b5cb..4fb1393fb8 100644 --- a/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js +++ b/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js @@ -6,13 +6,13 @@ module.exports = (options, context) => ({ enhanceAppFiles () { const { sourceDir, themeAPI } = context const enhanceAppPath = path.resolve(sourceDir, '.vuepress/enhanceApp.js') - const themeEnhanceAppPath = path.resolve(themeAPI.themePath, 'enhanceApp.js') + const themeEnhanceAppPath = path.resolve(themeAPI.theme.path, 'enhanceApp.js') const files = [ enhanceAppPath, themeEnhanceAppPath ] if (themeAPI.existsParentTheme) { - files.push(path.resolve(themeAPI.parentThemePath, 'enhanceApp.js')) + files.push(path.resolve(themeAPI.parentTheme.path, 'enhanceApp.js')) } return files } diff --git a/packages/@vuepress/core/lib/internal-plugins/layoutComponents.js b/packages/@vuepress/core/lib/internal-plugins/layoutComponents.js index 97f781b933..d8b71a2417 100644 --- a/packages/@vuepress/core/lib/internal-plugins/layoutComponents.js +++ b/packages/@vuepress/core/lib/internal-plugins/layoutComponents.js @@ -1,22 +1,13 @@ module.exports = (options, ctx) => { - const { layoutComponentMap } = ctx - const componentNames = Object.keys(layoutComponentMap) - return { name: '@vuepress/internal-layout-components', async clientDynamicModules () { + const componentNames = Object.keys(ctx.themeAPI.layoutComponentMap) const code = `export default {\n${componentNames - .map(name => ` ${JSON.stringify(name)}: () => import(${JSON.stringify(layoutComponentMap[name].path)})`) + .map(name => ` ${JSON.stringify(name)}: () => import(${JSON.stringify(ctx.themeAPI.layoutComponentMap[name].path)})`) .join(',\n')} \n}` return { name: 'layout-components.js', content: code, dirname: 'internal' } - }, - - chainWebpack (config, isServer) { - const setAlias = (alias, raw) => config.resolve.alias.set(alias, raw) - componentNames.forEach(name => { - setAlias(`@${name}`, layoutComponentMap[name].path) - }) } } } diff --git a/packages/@vuepress/core/lib/internal-plugins/palette/index.js b/packages/@vuepress/core/lib/internal-plugins/palette/index.js index fc0c2684b7..bd43d5e4fa 100644 --- a/packages/@vuepress/core/lib/internal-plugins/palette/index.js +++ b/packages/@vuepress/core/lib/internal-plugins/palette/index.js @@ -20,7 +20,7 @@ module.exports = (options, ctx) => ({ // 2. write palette.styl const { sourceDir, writeTemp } = ctx - const themePalette = path.resolve(ctx.themePath, 'styles/palette.styl') + const themePalette = path.resolve(ctx.themeAPI.theme.path, 'styles/palette.styl') const userPalette = path.resolve(sourceDir, '.vuepress/styles/palette.styl') const themePaletteContent = fs.existsSync(themePalette) @@ -34,8 +34,8 @@ module.exports = (options, ctx) => ({ // user's palette can override theme's palette. let paletteContent = themePaletteContent + userPaletteContent - if (ctx.parentThemePath) { - const parentThemePalette = path.resolve(ctx.parentThemePath, 'styles/palette.styl') + if (ctx.themeAPI.existsParentTheme) { + const parentThemePalette = path.resolve(ctx.themeAPI.parentTheme.path, 'styles/palette.styl') const parentThemePaletteContent = fs.existsSync(parentThemePalette) ? `@import(${JSON.stringify(parentThemePalette.replace(/[\\]+/g, '/'))})` : '' diff --git a/packages/@vuepress/core/lib/internal-plugins/style/index.js b/packages/@vuepress/core/lib/internal-plugins/style/index.js index 38d864b9ca..2055be6978 100644 --- a/packages/@vuepress/core/lib/internal-plugins/style/index.js +++ b/packages/@vuepress/core/lib/internal-plugins/style/index.js @@ -1,5 +1,9 @@ const { fs, path, logger, chalk } = require('@vuepress/shared-utils') +/** + * @param options + * @param {AppContext} ctx + */ module.exports = (options, ctx) => ({ name: '@vuepress/internal-style', @@ -15,7 +19,7 @@ module.exports = (options, ctx) => ({ logger.tip(`${chalk.magenta('override.styl')} has been deprecated from v1.0.0, using ${chalk.cyan('.vuepress/styles/palette.styl')} instead.\n`) } - const themeStyle = path.resolve(themeAPI.themePath, 'styles/index.styl') + const themeStyle = path.resolve(themeAPI.theme.path, 'styles/index.styl') const userStyle = path.resolve(sourceDir, '.vuepress/styles/index.styl') const themeStyleContent = fs.existsSync(themeStyle) @@ -29,7 +33,7 @@ module.exports = (options, ctx) => ({ let styleContent = themeStyleContent + userStyleContent if (themeAPI.existsParentTheme) { - const parentThemeStyle = path.resolve(themeAPI.parentThemePath, 'styles/index.styl') + const parentThemeStyle = path.resolve(themeAPI.parentTheme.path, 'styles/index.styl') const parentThemeStyleContent = fs.existsSync(parentThemeStyle) ? `@import(${JSON.stringify(parentThemeStyle.replace(/[\\]+/g, '/'))})` : '' diff --git a/packages/@vuepress/core/lib/prepare/AppContext.js b/packages/@vuepress/core/lib/prepare/AppContext.js index 00b9614651..9214c909c5 100644 --- a/packages/@vuepress/core/lib/prepare/AppContext.js +++ b/packages/@vuepress/core/lib/prepare/AppContext.js @@ -94,9 +94,6 @@ module.exports = class AppContext { this.resolveConfigAndInitialize() this.resolveCacheLoaderOptions() this.normalizeHeadTagUrls() - /** - * @type {ThemeAPI} - */ this.themeAPI = loadTheme(this) this.resolveTemplates() this.resolveGlobalLayout() @@ -156,8 +153,8 @@ module.exports = class AppContext { .use('@vuepress/register-components', { componentsDir: [ path.resolve(this.sourceDir, '.vuepress/components'), - path.resolve(this.themeAPI.themePath, 'global-components'), - this.themeAPI.existsParentTheme && path.resolve(this.themeAPI.parentThemePath, 'global-components') + path.resolve(this.themeAPI.theme.path, 'global-components'), + this.themeAPI.existsParentTheme && path.resolve(this.themeAPI.parentTheme.path, 'global-components') ] }) } @@ -171,10 +168,11 @@ module.exports = class AppContext { applyUserPlugins () { this.pluginAPI.useByPluginsConfig(this.cliOptions.plugins) if (this.themeAPI.existsParentTheme) { - this.pluginAPI.use(this.themeAPI.parentThemePath) + this.pluginAPI.use(this.themeAPI.parentTheme.entry) } this.pluginAPI - .use(this.themeAPI.themePath) + .use(this.themeAPI.theme.entry) + .use(this.themeAPI.vuepressPlugin) .use(Object.assign({}, this.siteConfig, { name: '@vuepress/internal-site-config' })) } @@ -224,41 +222,26 @@ module.exports = class AppContext { */ resolveTemplates () { - const { siteSsrTemplate, siteDevTemplate } = this.siteConfig - - const templateDir = path.resolve(this.vuepressDir, 'templates') - const siteSsrTemplate2 = path.resolve(templateDir, 'ssr.html') - const siteDevTemplate2 = path.resolve(templateDir, 'dev.html') - - const themeSsrTemplate = path.resolve(this.themeAPI.themePath, 'templates/ssr.html') - const themeDevTemplate = path.resolve(this.themeAPI.themePath, 'templates/dev.html') - - const parentThemeSsrTemplate = path.resolve(this.themeAPI.parentThemePath, 'templates/ssr.html') - const parentThemeDevTemplate = path.resolve(this.themeAPI.parentThemePath, 'templates/dev.html') - - const defaultSsrTemplate = path.resolve(__dirname, '../app/index.ssr.html') - const defaultDevTemplate = path.resolve(__dirname, '../app/index.dev.html') - - const ssrTemplate = fsExistsFallback([ - siteSsrTemplate, - siteSsrTemplate2, - themeSsrTemplate, - parentThemeSsrTemplate, - defaultSsrTemplate - ]) + this.devTemplate = this.resolveCommonAgreementFilePath( + 'devTemplate', + { + defaultValue: path.resolve(__dirname, '../app/index.dev.html'), + siteAgreement: 'templates/dev.html', + themeAgreement: 'templates/dev.html' + } + ) - const devTemplate = fsExistsFallback([ - siteDevTemplate, - siteDevTemplate2, - themeDevTemplate, - parentThemeDevTemplate, - defaultDevTemplate - ]) + this.ssrTemplate = this.resolveCommonAgreementFilePath( + 'ssrTemplate', + { + defaultValue: path.resolve(__dirname, '../app/index.ssr.html'), + siteAgreement: 'templates/ssr.html', + themeAgreement: 'templates/ssr.html' + } + ) - logger.debug('SSR Template File: ' + chalk.gray(ssrTemplate)) - logger.debug('DEV Template File: ' + chalk.gray(devTemplate)) - this.devTemplate = devTemplate - this.ssrTemplate = ssrTemplate + logger.debug('SSR Template File: ' + chalk.gray(this.ssrTemplate)) + logger.debug('DEV Template File: ' + chalk.gray(this.devTemplate)) } /** @@ -366,7 +349,8 @@ module.exports = class AppContext { */ getThemeConfigValue (key) { - return this.themeEntryFile[key] || this.parentThemeEntryFile[key] + return this.themeAPI.theme.entry[key] + || this.themeAPI.existsParentTheme && this.themeAPI.parentTheme.entry[key] } /** @@ -378,12 +362,12 @@ module.exports = class AppContext { */ resolveThemeAgreementFile (filepath) { - const current = path.resolve(this.themeAPI.themePath, filepath) + const current = path.resolve(this.themeAPI.theme.path, filepath) if (fs.existsSync(current)) { return current } if (this.themeAPI.existsParentTheme) { - const parent = path.resolve(this.themeAPI.parentThemePath, filepath) + const parent = path.resolve(this.themeAPI.theme.path, filepath) if (fs.existsSync(parent)) { return parent } diff --git a/packages/@vuepress/core/lib/prepare/loadTheme.js b/packages/@vuepress/core/lib/prepare/loadTheme.js index a14b1f74d8..7c90e452f8 100644 --- a/packages/@vuepress/core/lib/prepare/loadTheme.js +++ b/packages/@vuepress/core/lib/prepare/loadTheme.js @@ -33,16 +33,16 @@ const ThemeAPI = require('../theme-api') module.exports = function loadTheme (ctx) { const themeResolver = getThemeResolver() - const theme = resolveTheme(themeResolver, ctx) + const theme = resolveTheme(ctx, themeResolver) if (!theme.path) { throw new Error(`[vuepress] You must specify a theme, or create a local custom theme. \n For more details, refer to https://vuepress.vuejs.org/guide/custom-themes.html#custom-themes. \n`) } - theme.entryFile.name = '@vuepress/internal-theme-entry-file' + theme.entry.name = '@vuepress/internal-theme-entry-file' let parentTheme = {} - if (theme.entryFile.extend) { - parentTheme = resolveTheme(themeResolver, ctx, true, theme.entryFile.extend) - parentTheme.entryFile.name = '@vuepress/internal-parent-theme-entry-file' + if (theme.entry.extend) { + parentTheme = resolveTheme(ctx, themeResolver, true, theme.entry.extend) + parentTheme.entry.name = '@vuepress/internal-parent-theme-entry-file' } return new ThemeAPI(theme, parentTheme, ctx) @@ -63,12 +63,12 @@ function normalizeThemePath (resolved) { function resolveTheme (ctx, resolver, ignoreLocal, theme) { const { siteConfig, cliOptions, sourceDir, vuepressDir, pluginAPI } = ctx const localThemePath = resolve(vuepressDir, 'theme') - theme = siteConfig.theme || cliOptions.theme + theme = theme || siteConfig.theme || cliOptions.theme let path let name let shortcut - let entryFile = {} + let entry = {} // 1. From local if (!ignoreLocal @@ -95,10 +95,10 @@ function resolveTheme (ctx, resolver, ignoreLocal, theme) { } try { - entryFile = pluginAPI.normalizePlugin(path, ctx.themeConfig) + entry = pluginAPI.normalizePlugin(path, ctx.themeConfig) } catch (error) { - entryFile = {} + entry = {} } - return { path, name, shortcut, entryFile } + return { path, name, shortcut, entry } } diff --git a/packages/@vuepress/core/lib/theme-api/index.js b/packages/@vuepress/core/lib/theme-api/index.js index a3e8c409f5..76c760b083 100644 --- a/packages/@vuepress/core/lib/theme-api/index.js +++ b/packages/@vuepress/core/lib/theme-api/index.js @@ -2,34 +2,28 @@ const { logger, fs, path: { resolve }} = require('@vuepress/shared-utils') const readdirSync = dir => fs.existsSync(dir) && fs.readdirSync(dir) || [] module.exports = class ThemeAPI { - constructor (theme, parentTheme, ctx) { + constructor (theme, parentTheme) { this.theme = theme this.parentTheme = parentTheme || {} - this.ctx = ctx this.existsParentTheme = !!this.parentTheme.path + this.vuepressPlugin = { + name: '@vuepress/internal-theme-api', + alias: {} + } this.init() } setAlias (alias) { - this.theme.entryFile.alias = { - ...(this.theme.entryFile.alias || {}), - alias + this.vuepressPlugin.alias = { + ...this.vuepressPlugin.alias, + ...alias } } - get themePath () { - return this.theme.path - } - - get parentThemePath () { - return this.parentTheme.path - } - init () { const alias = { '@current-theme': this.theme.path } - this.setAlias() if (this.existsParentTheme) { alias['@parent-theme'] = this.parentTheme.path } @@ -101,6 +95,13 @@ module.exports = class ThemeAPI { } } +/** + * Resolve Vue SFCs, return a Map + * + * @param dirs + * @returns {*} + */ + function resolveSFCs (dirs) { return dirs.map( layoutDir => readdirSync(layoutDir) @@ -125,9 +126,11 @@ function resolveSFCs (dirs) { /** * normalize component name + * * @param {strin} filename * @returns {string} */ + function getComponentName (filename) { filename = filename.slice(0, -4) if (filename === '404') { @@ -136,6 +139,13 @@ function getComponentName (filename) { return filename } +/** + * Whether it's agreed layout component + * + * @param name + * @returns {boolean} + */ + function isInternal (name) { return name === 'Layout' || name === 'NotFound' } diff --git a/packages/@vuepress/theme-default/components/DropdownLink.vue b/packages/@vuepress/theme-default/components/DropdownLink.vue index 1d51d80bfc..0d360830ee 100644 --- a/packages/@vuepress/theme-default/components/DropdownLink.vue +++ b/packages/@vuepress/theme-default/components/DropdownLink.vue @@ -51,7 +51,7 @@ - ``` - + Here is the [content of default global layout component](https://github.com/vuejs/vuepress/blob/master/packages/%40vuepress/core/lib/app/components/GlobalLayout.vue), Also, you can follow the convention, directly create a component `.vuepress/components/GlobalLayout.vue` or `themePath/layouts/GlobalLayout.vue` without any config. the loading priority is as follows: - siteConfig - siteAgreement - - themeEntryFile + - themeEntry - themeAgreement - default diff --git a/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js b/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js old mode 100644 new mode 100755 index 4fb1393fb8..7e90d4d751 --- a/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js +++ b/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js @@ -6,14 +6,12 @@ module.exports = (options, context) => ({ enhanceAppFiles () { const { sourceDir, themeAPI } = context const enhanceAppPath = path.resolve(sourceDir, '.vuepress/enhanceApp.js') - const themeEnhanceAppPath = path.resolve(themeAPI.theme.path, 'enhanceApp.js') - const files = [ - enhanceAppPath, - themeEnhanceAppPath - ] + const files = [enhanceAppPath] if (themeAPI.existsParentTheme) { files.push(path.resolve(themeAPI.parentTheme.path, 'enhanceApp.js')) } + const themeEnhanceAppPath = path.resolve(themeAPI.theme.path, 'enhanceApp.js') + files.push(themeEnhanceAppPath) return files } }) diff --git a/packages/@vuepress/core/lib/plugin-api/index.js b/packages/@vuepress/core/lib/plugin-api/index.js index 83639fb542..a887fe7dd9 100644 --- a/packages/@vuepress/core/lib/plugin-api/index.js +++ b/packages/@vuepress/core/lib/plugin-api/index.js @@ -84,7 +84,11 @@ module.exports = class PluginAPI { if (isPlainObject(pluginRaw) && pluginRaw.$$normalized) { plugin = pluginRaw } else { - plugin = this.normalizePlugin(pluginRaw, pluginOptions) + try { + plugin = this.normalizePlugin(pluginRaw, pluginOptions) + } catch (e) { + logger.warn(e.message) + } } if (plugin.multiple !== true) { @@ -114,8 +118,7 @@ module.exports = class PluginAPI { normalizePlugin (pluginRaw, pluginOptions = {}) { let plugin = this._pluginResolver.resolve(pluginRaw) if (!plugin.entry) { - console.warn(`[vuepress] cannot resolve plugin "${pluginRaw}"`) - return this + throw new Error(`[vuepress] cannot resolve plugin "${pluginRaw}"`) } plugin = flattenPlugin(plugin, pluginOptions, this._pluginContext, this) plugin.$$normalized = true diff --git a/packages/@vuepress/core/lib/prepare/AppContext.js b/packages/@vuepress/core/lib/prepare/AppContext.js old mode 100644 new mode 100755 index 9214c909c5..f769842f87 --- a/packages/@vuepress/core/lib/prepare/AppContext.js +++ b/packages/@vuepress/core/lib/prepare/AppContext.js @@ -252,14 +252,12 @@ module.exports = class AppContext { */ resolveGlobalLayout () { - const GLOBAL_LAYOUT_COMPONENT_NAME = `GlobalLayout` - this.globalLayout = this.resolveCommonAgreementFilePath( 'globalLayout', { - defaultValue: path.resolve(__dirname, `../app/components/${GLOBAL_LAYOUT_COMPONENT_NAME}.vue`), - siteAgreement: `components/${GLOBAL_LAYOUT_COMPONENT_NAME}.vue`, - themeAgreement: `layouts/${GLOBAL_LAYOUT_COMPONENT_NAME}.vue` + defaultValue: path.resolve(__dirname, `../app/components/GlobalLayout.vue`), + siteAgreement: `components/GlobalLayout.vue`, + themeAgreement: `layouts/GlobalLayout.vue` } ) diff --git a/packages/@vuepress/core/lib/prepare/loadTheme.js b/packages/@vuepress/core/lib/prepare/loadTheme.js old mode 100644 new mode 100755 index 7c90e452f8..9a4bbca3d9 --- a/packages/@vuepress/core/lib/prepare/loadTheme.js +++ b/packages/@vuepress/core/lib/prepare/loadTheme.js @@ -37,6 +37,7 @@ module.exports = function loadTheme (ctx) { if (!theme.path) { throw new Error(`[vuepress] You must specify a theme, or create a local custom theme. \n For more details, refer to https://vuepress.vuejs.org/guide/custom-themes.html#custom-themes. \n`) } + logger.tip(`Apply theme ${chalk.gray(theme.name)}`) theme.entry.name = '@vuepress/internal-theme-entry-file' let parentTheme = {} @@ -45,6 +46,8 @@ module.exports = function loadTheme (ctx) { parentTheme.entry.name = '@vuepress/internal-parent-theme-entry-file' } + logger.debug('theme', theme.name, theme.path) + logger.debug('parentTheme', parentTheme.name, parentTheme.path) return new ThemeAPI(theme, parentTheme, ctx) } @@ -52,7 +55,13 @@ function normalizeThemePath (resolved) { const { entry, name, fromDep } = resolved if (fromDep) { const pkgPath = require.resolve(name) - return parse(pkgPath).dir + let packageRootDir = parse(pkgPath).dir + // For those cases that "main" field was set to non-index file + // e.g. `layouts/Layout.vue` + while (!fs.existsSync(`${packageRootDir}/package.json`)) { + packageRootDir = resolve(packageRootDir, '..') + } + return packageRootDir } else if (entry.endsWith('.js') || entry.endsWith('.vue')) { return parse(entry).dir } else { @@ -89,7 +98,6 @@ function resolveTheme (ctx, resolver, ignoreLocal, theme) { path = normalizeThemePath(resolved) name = resolved.name shortcut = resolved.shortcut - logger.tip(`Apply theme ${chalk.gray(name)}`) } else { return {} } diff --git a/packages/@vuepress/theme-blog/components/Home.vue b/packages/@vuepress/theme-blog/components/Home.vue new file mode 100755 index 0000000000..15cfbf39d6 --- /dev/null +++ b/packages/@vuepress/theme-blog/components/Home.vue @@ -0,0 +1,11 @@ + + + diff --git a/packages/@vuepress/theme-blog/layouts/Layout.vue b/packages/@vuepress/theme-blog/layouts/Layout2.vue old mode 100644 new mode 100755 similarity index 100% rename from packages/@vuepress/theme-blog/layouts/Layout.vue rename to packages/@vuepress/theme-blog/layouts/Layout2.vue diff --git a/packages/@vuepress/theme-vue/components/Home.vue b/packages/@vuepress/theme-vue/components/Home.vue deleted file mode 100644 index 43f5812818..0000000000 --- a/packages/@vuepress/theme-vue/components/Home.vue +++ /dev/null @@ -1,162 +0,0 @@ - - - - - diff --git a/packages/docs/docs/.vuepress/config.js b/packages/docs/docs/.vuepress/config.js old mode 100644 new mode 100755 index f12074bde8..b0eb2681f2 --- a/packages/docs/docs/.vuepress/config.js +++ b/packages/docs/docs/.vuepress/config.js @@ -76,7 +76,9 @@ module.exports = ctx => ({ ga: 'UA-128189152-1' }], ], - clientRootMixin: path.resolve(__dirname, 'mixin.js'), + clientRootMixin: ctx.isProd + ? path.resolve(__dirname, 'mixin.js') + : [], extendMarkdown (md) { md.use(container, 'upgrade', { render: (tokens, idx) => tokens[idx].nesting === 1 @@ -161,7 +163,8 @@ function getThemeSidebar (groupA, introductionA) { 'using-a-theme', 'writing-a-theme', 'option-api', - 'default-theme-config' + 'default-theme-config', + 'inheritance' ] }, ] diff --git a/packages/docs/docs/miscellaneous/glossary.md b/packages/docs/docs/miscellaneous/glossary.md old mode 100644 new mode 100755 index f6d36bd83c..030ca15ca8 --- a/packages/docs/docs/miscellaneous/glossary.md +++ b/packages/docs/docs/miscellaneous/glossary.md @@ -56,7 +56,7 @@ Value of `themeConfig` in `.vuepress/config.js`, i.e., `user's theme configurati Root path (absolute path) of the currently used theme. -## themeEntryFile +## themeEntry > Access: `Context.themeAPI.theme.entry` diff --git a/packages/docs/docs/theme/inheritance.md b/packages/docs/docs/theme/inheritance.md new file mode 100755 index 0000000000..9465728da0 --- /dev/null +++ b/packages/docs/docs/theme/inheritance.md @@ -0,0 +1,9 @@ +# Theme Inheritance + +## Motivation + + + +## What is Subject Inheritance? + + diff --git a/packages/docs/docs/theme/option-api.md b/packages/docs/docs/theme/option-api.md old mode 100644 new mode 100755 index 0fbb7aa90c..c6acbd51a3 --- a/packages/docs/docs/theme/option-api.md +++ b/packages/docs/docs/theme/option-api.md @@ -32,3 +32,4 @@ Note that in the child theme, VuePress will apply a `@parent-theme` [alias](../p - [Example: `@vuepress/theme-vue`](https://github.com/vuejs/vuepress/tree/master/packages/@vuepress/theme-vue) - [Design Concepts of VuePress 1.x](../miscellaneous/design-concepts.md) + diff --git a/packages/docs/docs/zh/miscellaneous/glossary.md b/packages/docs/docs/zh/miscellaneous/glossary.md old mode 100644 new mode 100755 index 4f31bdca25..435a691c82 --- a/packages/docs/docs/zh/miscellaneous/glossary.md +++ b/packages/docs/docs/zh/miscellaneous/glossary.md @@ -6,64 +6,75 @@ sidebar: auto 你可能会在文档中碰到一些陌生的概念,本节列出了文档中常见的术语,方便查阅、学习、插件/主题开发之用。 -## frontmatter +## layout -> Access: `$page.frontmatter` +- Access: `$page.frontmatter.layout` -当前页面的 `markdown` 文件中包裹在 `---` 中的配置,一般用于做一些页面级别的配置。 +当前页面所使用的布局组件名。 -::: tip -VuePress 的动态布局系统等特性是基于 `frontmatter` 实现的,你可以使用插件 API [extendPageData](../plugin/option-api.md#extendpagedata) 在构建期间动态修改 frontmatter 的值。 -::: +## frontmatter + +- Access: `$page.frontmatter` + +当前页面的 `markdown` 文件中包裹在 `---` 中的配置,一般用于做一些页面级别的配置,参考 [Front Matter](../guide/frontmatter.md) 一节了解更多。 ## permalink -> Access: `$page.frontmatter.permalink` +- Access: `$page.frontmatter.permalink` -永久链接,参考 [permalinks](../guide/permalinks.md) 了解更多。 +永久链接,参考 [Permalinks](../guide/permalinks.md) 一节了解更多。 ## regularPath -> Access: `$page.regularPath` +- Access: `$page.regularPath` 当前页面基于目录结构生成的 URL。 -::: tip -在构建期动态生成路由时,一个页面的 URL (`$page.path`) 将优先使用 `$page.frontmatter.permalink`,若不存在则降级到 `$page.regularPath`。 -::: +## path + +- Access: `$page.path` + +当前页面的实际 URL。在构建期生成路由时,一个页面的 URL 将优先使用 `permalink`,若不存在则降级到 `regularPath`。 ## headers -> Access: `$page.headers` +- Access: `$page.headers` 即 `markdown` 中那些以一个或多个 `#` 定义的标题。 ## siteConfig -> Access: `$site | Context.siteConfig` +- Access: `$site | Context.siteConfig` -即 `.vuepress/config.js`,译为`站点配置`。 +即 `.vuepress/config.js`,译为 `站点配置`。 ## themeConfig -> Access: `$site | Context.themeConfig` +- Access: `$site | Context.themeConfig` -即 `.vuepress/config.js` 中 `themeConfig` 的值,译为`用户的主题配置`。 +即 `.vuepress/config.js` 中 `themeConfig` 的值,是用户对当前所使用的主题的配置。 ## themePath -> Access: `Context.themeAPI.theme.path` +- Access: `Context.themeAPI.theme.path` -当前使用的主题的根路径(绝对路径)。 +当前使用的主题的所在路径(绝对路径)。 -## themeEntryFile +## themeEntry -> Access: `Context.themeAPI.theme.entry` +- Access: `Context.themeAPI.theme.entry` -主题的配置文件 (`themePath/index.js`)。 +主题的配置文件 `themePath/index.js`。 -## layout +## parentThemePath -> Access: `$page.frontmatter.layout` +- Access: `Context.themeAPI.parentTheme.path` + +如果当前使用的主题是一个派生主题,那么 `parentThemePath` 就是指父主题的所在路径(绝对路径)。 + +## parentThemeNetry + +- Access: `Context.themeAPI.parentTheme.entry` + +如果当前使用的主题是一个派生主题,那么 `parentThemePath` 就是指父主题的主题的配置文件 `parentThemePath/index.js`。 -当前页面所使用的布局组件名。 diff --git a/packages/docs/docs/zh/theme/README.md b/packages/docs/docs/zh/theme/README.md index 88856355e2..83447d4210 100644 --- a/packages/docs/docs/zh/theme/README.md +++ b/packages/docs/docs/zh/theme/README.md @@ -3,3 +3,11 @@ ::: tip 主题组件受到相同的 [浏览器的 API 访问限制](../guide/using-vue.md#浏览器的API访问限制). ::: + +本栏的主要内容如下: + +- [使用主题](./using-a-theme.md) +- [开发主题](./writing-a-theme.md) +- [主题的配置](./option-api.md) +- [主题的继承](./inheritance.md) +- [默认主题配置](./default-theme-config.md) diff --git a/packages/docs/docs/zh/theme/inheritance.md b/packages/docs/docs/zh/theme/inheritance.md new file mode 100755 index 0000000000..0c9bf28584 --- /dev/null +++ b/packages/docs/docs/zh/theme/inheritance.md @@ -0,0 +1,181 @@ +# 主题的继承 + +## 动机 + +我们有两个主要的理由来支持这个特性: + +1. VuePress 为开发者提供了一个[默认主题](./default-theme-config.md),它能在大多数场景下满足了文档编写者的需求。即便如此,仍然还是会有不少用户选择将其 eject 出来进行修改。实际上,我们可能只需要小改其中的某个组件。 +2. 在 [0.x](https://vuepress.vuejs.org/guide/custom-themes.html#site-and-page-metadata) 中,主题的入口只需要一个 `Layout.vue`,所以我们可以通过把另一个主题的 `Layout.vue` 直接引入来实现复用,这时,如果父 Layout 中提供了一些 slots,那么我们就可以在自己的 Layout 组件中传入这些 slot 了——这实现了简单的主题继承。 + + 到了 1.x 中,一个主题的配置开始变得复杂,我们开始有了[主题级别的配置](./option-api.md),它支持为主题添加插件、自定义 GlobalLayout 等。除此之外,我们还有一些引入了主题开发的 [目录结构的约定](./writing-a-theme.md#目录结构),如 `styles/index.styl`,在这样的背景下,我们无法使用 0.x 的方式来实现继承了。 + +因此,我们需要提供一种合理、可靠的主题继承方案。 + +## 概念 + +为了介绍本节,我们先几个基本概念: + +- **原子主题**:即父主题,类似默认主题这种完全从头实现的主题。 +- **派生主题**:即子主题,基于父主题创建的主题; + +::: tip 提示 +主题支持暂时不支持高阶继承,也就是说,一个派生主题无法被继承。 +::: + +## 使用 + +假设你想创建一个继承自 VuePress 默认主题的派生主题,你只需要在你的主题配置中配置 [extend](./option-api.md#extend) 选项: + +```js +module.exports = { + extend: '@vuepress/theme-default' +} +``` + +## 继承策略 + +**父主题的所有能力都会"传递"给子主题,对于文件级别的约定,子主题可以通过在同样的位置创建同名文件来覆盖它,对于某些主题配置选项,如 [globalLayout](./option-api.md#globallayout),子主题也可以通过同名配置来覆盖它。** + +文件级别的约定如下: + +- **全局组件**,即放置在 `theme/global-components` 中的 Vue 组件。 +- **组件**,即放置在 `theme/components` 中的 Vue 组件。 +- **全局的样式和调色板**,即放置在 `theme/styles` 中的 `index.styl` 和 `palette.styl`。 +- **HTML 模板**,即放置在 `theme/templates` 中的 `dev.html` 和 `ssr.html`。 +- **主题水平的客户端增强文件**,即 `theme/enhanceApp.js` + +能被子主题覆盖的主题配置选项如下: + +- [devTemplate](./option-api.md#devtemplate) +- [ssrTemplate](./option-api.md#ssrtemplate) +- [globalLayout](./option-api.md#globallayout) + +无法被子主题覆盖的主题配置选项如下: + +- [extend](./option-api.md#extend):父主题不会存在 extend。 + +需要特殊处理的主题选项: + +- [plugins](./option-api.md#plugins):见下文。 + +## 插件的覆盖 + +对于父主题中的 [plugins](./option-api.md#plugins), 子主题不会覆盖它,但可以通过同名的插件配置来 override 插件的默认选项,如: + +举例来说,如果父主题具有如下配置: + +```js +// parentThemePath/index.js +module.exports = { + plugins: [ + ['@vuepress/search', { + searchMaxSuggestions: 5 + }] + ] +} +``` + +那么子主题可以通过如下方式来修改该插件的默认值: + +```js +// themePath/index.js +module.exports = { + plugins: [ + ['@vuepress/search', { + searchMaxSuggestions: 10 + }] + ] +} +``` + +也可以选择禁用它: + +```js +// themePath/index.js +module.exports = { + plugins: [ + ['@vuepress/search', false] + ] +} +``` + +::: warning +一般情况下,你都不需要这样做,除非你明确知道禁用父主题中的插件不会带来问题。 +::: + +## 组件的覆盖 + +你可能想要在子主题中覆盖父主题中的同名组件,默认情况下,当父主题中的组件都使用相对路径引用其他组件时,你将不可能做到这一点,因为你无法在运行时修改父主题的代码。 + +VuePress 则通过一种巧妙的方式实现了这种需求,但这对父主题有一定的要求——**所有的组件都必须使用 `@theme` 别名来引用其他组件**。 + +举例来说,如果你想开发一个父主题,它的结构如下: + +::: vue +theme +├── components +│   ├── `Home.vue` +│   ├── `Navbar.vue` +│   └── `Sidebar.vue` +├── layouts +│   ├── `404.vue` +│   └── `Layout.vue` +├── package.json +└── index.js +::: + +那么,你在主题中的任意 Vue 组件中,**都应该通过 `@theme` 来访问主题根目录**: + +```vue + +``` + +在这样的前提下,当你在子主题中同样的位置创建一个 `Navbar` 组件时: + +::: vue +theme +└── components +    └── `Navbar.vue` +::: + +`@theme/components/Navbar.vue` 会自动地映射到子主题中的 Navbar 组件,当你移除这个组件时,`@theme/components/Navbar.vue` 又会自动恢复为父主题中的 Navbar 组件。 + +如此一来,就可以实现轻松地“篡改”一个原子主题的某个部分。 + +::: tip +1. 组件的覆盖,最好直接基于父主题中对应组件的代码来修改; +2. 目前,在本地开发子主题,每次创建或移除组件时,你需要手动重启 Dev Server。 +::: + +## 访问父主题 + +你可以在子主题的组件中,使用 `@parent-theme` 来访问父主题,下述示例展示了在子主题中创建一个同名的布局组件,并简单复用父主题中的 slot,[@vuepress/theme-vue](https://github.com/vuejs/vuepress/tree/master/packages/%40vuepress/theme-vue) 便是通过这种方式来基于默认主题创造的。 + +```vue + + + + +``` + + + + + diff --git a/packages/docs/docs/zh/theme/option-api.md b/packages/docs/docs/zh/theme/option-api.md old mode 100644 new mode 100755 index f304f7bd12..51beaf77d1 --- a/packages/docs/docs/zh/theme/option-api.md +++ b/packages/docs/docs/zh/theme/option-api.md @@ -2,7 +2,30 @@ metaTitle: Option API | Theme --- -# Option API +# 主题的配置 + +和插件几乎一样,主题的配置文件 `themeEntry` 应该导出一个普通的 JavaScript 对象(`#1`),它也可以是一个返回对象的函数(`#2`),这个函数接受用户在 `siteConfig`(即 `.vuepress/config.js`) 中的 `themeConfig` 为第一个参数、包含编译期上下文的 [ctx](./context-api.md) 对象作为第二个参数。 + +``` js +// #1 +module.exports = { + // ... +} +``` + +``` js +// #2 +module.exports = (themeConfig, ctx) => { + return { + // ... + } +} +``` + +::: tip +1. 你应该能看到 `themeEntry` 和 `themeConfig` 的区别,前者是一个主题本身的配置,这些配置由 VuePress 本身提供;而后者则是用户对主题的配置,这些配置选项则由当前使用的主题来实现,如 [默认主题配置](./default-theme-config.md)。 +2. 除了本节列出的选项,`themeEntry` 也支持插件支持的所有 [配置选项](../plugin/option-api.md) 和 [生命周期](../plugin/life-cycle.md)。 +::: ## plugins @@ -13,11 +36,87 @@ metaTitle: Option API | Theme - [插件 > 使用插件](../plugin/using-a-plugin.md). -VuePress 支持一个主题继承于另一个主题。VuePress 将遵循 `override` 的方式自动帮你解决各种主题属性(如样式、布局组件)的优先级。 +--- + +::: warning 注意 +You probably don't need to use following options tagged with unless you know what you are doing! +你可能不需要使用下面这些带有 的选项,除非你知道你在做什么! +::: + +## devTemplate + +- 类型: `String` +- 默认值: undefined + +dev 模式下使用的 HTML 模板路径,默认模板见 [这里](https://github.com/vuejs/vuepress/blob/master/packages/%40vuepress/core/lib/app/index.dev.html)。 + +## ssrTemplate + +- 类型: `String` +- 默认值: undefined + +build 模式下使用的 HTML 模板路径,默认模板见 [这里](https://github.com/vuejs/vuepress/blob/master/packages/%40vuepress/core/lib/app/index.dev.html)。 -值得注意的是,在子主题中,VuePress 将注入一个指向父主题包目录根路径的 [alias](../plugin/option-api.md#alias) `@parent-theme`。 + +## extend + +- 类型: `String` +- 默认值: undefined + +```js +module.exports = { + extend: '@vuepress/theme-default' +} +``` + +VuePress 支持一个主题继承于另一个主题。VuePress 将遵循 `override` 的理念自动帮你解决各种主题属性(如样式、布局组件)的优先级。 **参考:** +- [主题继承](./inheritance.md) - [例子: `@vuepress/theme-vue`](https://github.com/vuejs/vuepress/tree/master/packages/@vuepress/theme-vue) -- [Design Concepts of VuePress 1.x](../miscellaneous/design-concepts.md) +- [VuePress 1.x 的设计理念](../miscellaneous/design-concepts.md) + +## globalLayout + +- 类型: `String` +- 默认值: undefined + +```js +// themePath/index.js +module.exports = { + globalLayout: '/path/to/your/global/vue/sfc' +} +``` + +全局布局组件是负责管理全局布局方案的一个组件,VuePress [默认的 globalLayout](https://github.com/vuejs/vuepress/blob/master/packages/%40vuepress/core/lib/app/components/GlobalLayout.vue)会帮你根据 [$frontmatter.layout](../guide/frontmatter.md#layout) 来渲染不同的布局,所以大部分情况下你不要配置此选项。 + +举例来说,当你想为当前主题设置全局的 header 和 footer 时,你可以这样做: + + +```vue + + + + +``` diff --git a/packages/docs/docs/zh/theme/using-a-theme.md b/packages/docs/docs/zh/theme/using-a-theme.md index 3212fd82ea..2554e7930e 100644 --- a/packages/docs/docs/zh/theme/using-a-theme.md +++ b/packages/docs/docs/zh/theme/using-a-theme.md @@ -1,10 +1,10 @@ # 使用主题 -使用一个主题和使用一个插件几乎一致。 +使用一个主题和使用一个插件的方式几乎一致。 -## 使用 dependency 中的主题 +## 使用来自依赖的插件 -一个插件可以在以 `vuepress-theme-xxx` 的形式发布到 npm,你可以这样使用它: +一个主题可以在以 `vuepress-theme-xxx` 的形式发布到 npm,你可以这样使用它: ``` js module.exports = { @@ -14,7 +14,7 @@ module.exports = { ## 主题的缩写 -如果你的插件名以 `vuepress-theme-` 开头,你可以使用缩写来省略这个前缀: +如果你的主题名以 `vuepress-theme-` 开头,你可以使用缩写来省略这个前缀: ``` js module.exports = { @@ -47,5 +47,5 @@ module.exports = { ``` ::: warning 注意 -以 `@vuepress/plugin-` 开头的插件是官方维护的插件。 +以 `@vuepress/theme-` 开头的主题是官方维护的主题。 ::: diff --git a/packages/docs/docs/zh/theme/writing-a-theme.md b/packages/docs/docs/zh/theme/writing-a-theme.md old mode 100644 new mode 100755 index 0c586cb115..a40b42d907 --- a/packages/docs/docs/zh/theme/writing-a-theme.md +++ b/packages/docs/docs/zh/theme/writing-a-theme.md @@ -33,27 +33,27 @@ ## 目录结构 -随着需求的变化,只有一个布局组件 `Layout.vue` 可能还不够,你可能想要定义更多的布局组件用于不同的页面,你可能还想要自定义一个[调色板](../config/README.md#palette-styl), 甚至应用一些插件。 +随着需求的变化,只有一个布局组件 `Layout.vue` 可能还不够,你可能想要定义更多的布局组件用于不同的页面,你可能还想要自定义一个[调色板](../config/README.md#palette-styl),甚至应用一些插件。 那么是时候重新组织你的主题了!一个约定的主题的目录结构如下: ::: vue -themePath -├── `global-components` _(**可选的**)_ +theme +├── `global-components` │ └── xxx.vue -├── `components` _(**可选的**)_ +├── `components` │ └── xxx.vue ├── `layouts` │   ├── Layout.vue _(**必要的**)_ -│   └── 404.vue _(**可选的**)_ -├── `styles` _(**必要的**)_ +│   └── 404.vue +├── `styles` │   ├── index.styl │   └── palette.styl -├── `templates` _(**必要的**)_ +├── `templates` │   ├── dev.html │   └── ssr.html -├── `index.js` _(**当你将主题发布为一个 npm 包时,这是必须的!**)_ -├── `enhanceApp.js` _(**必要的**)_ +├── `index.js` +├── `enhanceApp.js` └── package.json ::: @@ -66,12 +66,12 @@ themePath - `theme/enhanceApp.js`: 主题水平的客户端增强文件。 ::: warning 注意 -当你将你的主题以一个 npm 包的形式发布时,请确保这个包包含 `index.js`,同时将 `package.json` 中的 `"main"` 字段设置为 `index.js`,如此一来 VuePress 才能获取到正确的 [themePath](../miscellaneous/glossary.md#theme-side). +当你将你的主题以一个 npm 包的形式发布时,如果你没有任何主题配置,即没有 `theme/index.js`,那么你需要将 `package.json` 中的 `"main"` 字段设置为 `layouts/Layout.vue`,只有这样 VuePress 才能正确地解析主题。 ```json { ... - "main": "index.js" + "main": "layouts/Layout.vue", ... } ``` @@ -100,6 +100,10 @@ layout: AnotherLayout --- ```` +::: tip +每个 layout 组件都可能会渲染出截然不同的页面,如果你想设置一些全局的 UI(如全局的 `
`),可以考虑使用 [globalLayout](./option-api.md#globallayout)。 +::: + ## 使用插件 你可以通过主题的配置文件 `themePath/index.js` 来给主题应用一些插件: From f420359aadd8bb7650cfe4c0ae4456f9608a8ddb Mon Sep 17 00:00:00 2001 From: ULIVZ <472590061@qq.com> Date: Sun, 24 Feb 2019 20:00:48 +0800 Subject: [PATCH 05/10] docs: update --- .../@vuepress/theme-blog/components/Home.vue | 11 -- packages/docs/docs/theme/inheritance.md | 177 +++++++++++++++++- packages/docs/docs/theme/option-api.md | 107 ++++++++++- packages/docs/docs/theme/using-a-theme.md | 4 +- packages/docs/docs/theme/writing-a-theme.md | 27 +-- packages/docs/docs/zh/theme/inheritance.md | 28 +-- packages/docs/docs/zh/theme/option-api.md | 12 +- 7 files changed, 311 insertions(+), 55 deletions(-) delete mode 100755 packages/@vuepress/theme-blog/components/Home.vue diff --git a/packages/@vuepress/theme-blog/components/Home.vue b/packages/@vuepress/theme-blog/components/Home.vue deleted file mode 100755 index 15cfbf39d6..0000000000 --- a/packages/@vuepress/theme-blog/components/Home.vue +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/packages/docs/docs/theme/inheritance.md b/packages/docs/docs/theme/inheritance.md index 9465728da0..6300046df2 100755 --- a/packages/docs/docs/theme/inheritance.md +++ b/packages/docs/docs/theme/inheritance.md @@ -1,9 +1,182 @@ -# Theme Inheritance +# Theme Inheritance ## Motivation +We have two main reasons to support this feature: + +1. VuePress provides users with a [default theme](./default-theme-config.md), which meets the needs of document writers in most scenarios, even so, there are still many users who choose to `eject` and modify, even if they may only need to make minor changes to one of the components. + +2. In [0.x](https://vuepress.vuejs.org/guide/custom-themes.html#site-and-page-metadata), only one `Layout.vue` is needed for a theme, so we can achieve simple expansion by directly wrapping `Layout.vue` of another theme. + + By 1.x, the elements of a theme has become more complex, we have started to have [theme level configuration](./option-api.md), which supports plugins, custom global layout, etc. In addition, we have also introduced the [directory structure conventions](./writing-a-theme.md#directory-structure) on theme development, such as `styles/index.styl`, under this background, we can not achieve inheritance as 0.x did. + +Therefore, we need to provide a reasonable and reliable theme inheritance strategy. + +## Concepts + +To introduce this section, let's start with a few basic concepts: + +- **Atomic theme**:i.e. the parent theme, which is implemented entirely from scratch, like the default theme. +- **Derived theme**:i.e. the child theme, created based on parent theme; + +::: tip +For now theme inheritance doesn't support high-level inheritance, that means, a derived topic cannot be inherited. +::: + +## Usage + +Suppose you want to create a theme inherited from the default theme of VuePress, you just need to configure the [extend](./option-api.md#extend) option in your theme configuration: + +```js +module.exports = { + extend: '@vuepress/theme-default' +} +``` + +## Inheritance Strategy + +**All the capabilities of the parent theme will be `"passed"` to the child theme. For file-level conventions, child theme can override it by creating a file with the same name in the same location. For some theme configuration options, such as [globalLayout](./option-api.md/globallayout), child theme can override it by the same name configuration.** + +The [file-level agreement](./writing-a-theme.md#directory-structure) at are as follows: + +- **Global Components**,i.e. the Vue components under `theme/global-components`. +- **Components**,i.e. the Vue components under `theme/components`. +- **Global Style and Palette**,i.e. `index.styl` and `palette.styl` under `theme/styles`. +- **HTML Template**,i.e. `dev.html` and `ssr.html` under `theme/templates`. +- **Theme-Level App Enhancement File**,i.e. `theme/enhanceApp.js` + +For theme configuration, the configuration options that can be overrode by child theme are as follows: + +- [devTemplate](./option-api.md#devtemplate) +- [ssrTemplate](./option-api.md#ssrtemplate) +- [globalLayout](./option-api.md#globallayout) + +Theme configuration options that cannot be overrode by child theme: + +- [extend](./option-api.md#extend) + +Theme configuration options requiring special treatment: + +- [plugins](./option-api.md#plugins):See [Override Plugins](#override-plugins)。 + +## Override Plugins + +For [plugins](./option-api.md#plugins) in the parent theme, the child theme cannot override it intuitively, but the options of plugin can be overrode by creating plugin configuration with the same name. + +For example, if the parent theme has the following configuration: + +```js +// parentThemePath/index.js +module.exports = { + plugins: [ + ['@vuepress/search', { + searchMaxSuggestions: 5 + }] + ] +} +``` + +The child theme can modify the options of plugin in the following ways: + +```js +// themePath/index.js +module.exports = { + plugins: [ + ['@vuepress/search', { + searchMaxSuggestions: 10 + }] + ] +} +``` + +Child theme can even disable it: + +```js +// themePath/index.js +module.exports = { + plugins: [ + ['@vuepress/search', false] + ] +} +``` + +::: warning +Normally, you don't need to do this unless you know clearly that disabling plugins in parent themes won't cause problems. +::: + +## Override Components + +You may want to override the same-name components in the parent theme. By default, when the components in the parent theme use relative paths to reference other components, you will not be able to do this because you cannot modify the code of the parent theme at run time. + +VuePress achieves this requirement in a clever way, but there is a requirement for the parent theme - **All components must use the `@theme` alias to refer to other components**. + +For example, if you are developing an atomic theme with the following structure: + +::: vue +theme +├── components +│   ├── `Home.vue` +│   ├── `Navbar.vue` +│   └── `Sidebar.vue` +├── layouts +│   ├── `404.vue` +│   └── `Layout.vue` +├── package.json +└── index.js +::: + +Then, in any Vue components on the theme, **you should access the theme root directory through `@theme`**: + +```vue + +``` + +On this premise, when you create a `Navbar` component in the same place in the child theme + +::: vue +theme +└── components +    └── `Navbar.vue` +::: + +`@theme/components/Navbar.vue` will automatically map to the Navbar component in the child theme. and when you remove the component, `@theme/components/Navbar.vue` will automatically restore to the Navbar component in the parent theme. + +In this way, you can easily "tamper" with some part of an atomic theme. + +::: tip +1. You'd better override the component based on the code of the corresponding component in the parent theme. +2. Currently, when developing theme locally, you need to manually restart dev serverwhen a component is created or removed. +::: + +## Access Parent Theme + +You can use `@parent-theme` to access the root path of the parent theme. The following example shows creating a layout component with the same name in a child theme and simply using slots in the parent theme. [@vuepress/theme-vue](https://github.com/vuejs/vuepress/tree/master/packages/%40vuepress/theme-vue) is created in this way based on the default theme. + +```vue + + + + +``` + -## What is Subject Inheritance? diff --git a/packages/docs/docs/theme/option-api.md b/packages/docs/docs/theme/option-api.md index c6acbd51a3..b31c4b94cb 100755 --- a/packages/docs/docs/theme/option-api.md +++ b/packages/docs/docs/theme/option-api.md @@ -1,8 +1,33 @@ --- -metaTitle: Option API | Theme +metaTitle: Configuration | Theme --- -# Option API +# Theme Configuration + +As with plugins, the theme configuration file `themeEntry` should export a `plain JavaScript object`(`#1`). If the plugin needs to take options, it can be a function that exports a plain object(`#2`). The function will be called with the `siteConfig.themeConfig` as the first argument, along with [ctx](./context-api.md) which provides some compile-time metadata. + +``` js +// #1 +module.exports = { + // ... +} +``` + +``` js +// #2 +module.exports = (themeConfig, ctx) => { + return { + // ... + } +} +``` + + +::: tip +1. You should see the difference between `themeEntry` and `themeConfig`, the former is a configuration for ths theme itself, which is provided by VuePress. the latter is the user's configuration for the theme, which is implemented by the currently used theme, e.g. [Default Theme Config](./default-theme-config.md). + +2. In addition to the options listed in this section, `themeEntry` also supports all [Option API](../plugin/option-api.md) and [Life Cycle](../plugin/life-cycle.md) supported by plugins. +::: ## plugins @@ -11,9 +36,33 @@ metaTitle: Option API | Theme **Also see:** -- [Plugin > Using a plugin](../plugin/using-a-plugin.md). +- [Plugin > Using a Plugin](../plugin/using-a-plugin.md). -## extend +--- + +::: warning +You probably don't need to use following options tagged with unless you know what you are doing! +::: + +## devTemplate + +- Type: `String` +- Default: undefined + +HTML template path used in `dev` mode, default template see [here](https://github.com/vuejs/vuepress/blob/master/packages/%40vuepress/core/lib/app/index.dev.html) + +## ssrTemplate + +- Type: `String` +- Default: undefined + +HTML template path used in `build` mode, default template see [here](https://github.com/vuejs/vuepress/blob/master/packages/%40vuepress/core/lib/app/index.ssr.html) + +**Also see:** + +- [Vue SSR Guide > template](https://ssr.vuejs.org/api/#template). + +## extend - Type: `String` - Default: undefined @@ -24,12 +73,52 @@ module.exports = { } ``` -VuePress supports a theme to be inherited from another theme. VuePress will follow the principle of `override` to automatically help you resolve the priorities of various theme attributes, such as styles, layout components. - -Note that in the child theme, VuePress will apply a `@parent-theme` [alias](../plugin/option-api.md#alias) pointing to the package directory of parent theme. +VuePress provides the ability to inherit one theme from another. VuePress will follow the concept of `override` and automatically help you prioritize various thematic attributes, e.g. styles and layout components. **Also see:** -- [Example: `@vuepress/theme-vue`](https://github.com/vuejs/vuepress/tree/master/packages/@vuepress/theme-vue) -- [Design Concepts of VuePress 1.x](../miscellaneous/design-concepts.md) +- [Theme Inheritance](./inheritance.md) +- [VuePress 1.x 的设计理念](../miscellaneous/design-concepts.md) + +## globalLayout + +- Type: `String` +- Default: undefined + +```js +// themePath/index.js +module.exports = { + globalLayout: '/path/to/your/global/vue/sfc' +} +``` + +Global layout component is a component responsible for the global layout strategy. The [default global layout](https://github.com/vuejs/vuepress/blob/master/packages/%40vuepress/core/lib/app/components/GlobalLayout.vue) will help you render different layouts according to [$frontmatter.layout](../guide/frontmatter.md#layout), so in most cases you do not need to configure this option. + +For example, when you want to set a global header and footer for your theme, you can do this: + +```vue + + + +``` diff --git a/packages/docs/docs/theme/using-a-theme.md b/packages/docs/docs/theme/using-a-theme.md index 1eac4ceff8..f441a9fc5c 100644 --- a/packages/docs/docs/theme/using-a-theme.md +++ b/packages/docs/docs/theme/using-a-theme.md @@ -14,7 +14,7 @@ module.exports = { ## Theme Shorthand -If you prefix the plugin with `vuepress-theme-`, you can use a shorthand to leave out that prefix: +If you prefix the theme with `vuepress-theme-`, you can use a shorthand to leave out that prefix: ``` js module.exports = { @@ -47,5 +47,5 @@ module.exports = { ``` ::: warning Note -The plugin whose name starts with `@vuepress/theme-` is an officially maintained theme. +The theme whose name starts with `@vuepress/theme-` is an officially maintained theme. ::: diff --git a/packages/docs/docs/theme/writing-a-theme.md b/packages/docs/docs/theme/writing-a-theme.md index cce052cc3f..9ddad11902 100644 --- a/packages/docs/docs/theme/writing-a-theme.md +++ b/packages/docs/docs/theme/writing-a-theme.md @@ -34,22 +34,22 @@ Just one `Layout.vue` might not be enough, and you might also want to define mor So it's time to reorganize your theme, an agreed theme directory structure is as follows: ::: vue -themePath -├── `global-components` _(**Optional**)_ +theme +├── `global-components` │ └── xxx.vue -├── `components` _(**Optional**)_ +├── `components` │ └── xxx.vue ├── `layouts` -│   ├── Layout.vue _(**Required**)_ -│   └── 404.vue _(**Optional**)_ -├── `styles` _(**Optional**)_ +│   ├── Layout.vue _(**必要的**)_ +│   └── 404.vue +├── `styles` │   ├── index.styl │   └── palette.styl -├── `templates` _(**Optional**)_ +├── `templates` │   ├── dev.html │   └── ssr.html -├── `index.js` _(**Only required when you publish it as an npm package**)_ -├── `enhanceApp.js` _(**Optional**)_ +├── `index.js` +├── `enhanceApp.js` └── package.json ::: @@ -62,12 +62,11 @@ themePath - `theme/enhanceApp.js`: Theme level enhancements. ::: warning Note -When you want to publish your theme as an npm package, make sure the package has `index.js`, and set `"main"` field at `package.json` to `index.js` so that VuePress can resolve and get the correct [themePath](../miscellaneous/glossary.md#theme-side). - +When you publish your theme as an NPM package, if you don't have any theme configuration, that means you don't have `theme/index.js`, you'll need to set the `"main"` field to `layouts/Layout.vue` in `package.json`, only in this way VuePress can correctly resolve the theme. ```json { ... - "main": "index.js" + "main": "layouts/Layout.vue", ... } ``` @@ -96,6 +95,10 @@ layout: AnotherLayout --- ```` +::: tip +Each layout component may render distinct pages. If you want to apply some global UI (e.g. global header), consider using [globalLayout](./option-api.md#globallayout)。 +::: + ## Apply plugins You can apply some plugins to the theme via `theme/index.js`. diff --git a/packages/docs/docs/zh/theme/inheritance.md b/packages/docs/docs/zh/theme/inheritance.md index 0c9bf28584..4863882019 100755 --- a/packages/docs/docs/zh/theme/inheritance.md +++ b/packages/docs/docs/zh/theme/inheritance.md @@ -1,13 +1,13 @@ -# 主题的继承 +# 主题的继承 ## 动机 我们有两个主要的理由来支持这个特性: -1. VuePress 为开发者提供了一个[默认主题](./default-theme-config.md),它能在大多数场景下满足了文档编写者的需求。即便如此,仍然还是会有不少用户选择将其 eject 出来进行修改。实际上,我们可能只需要小改其中的某个组件。 -2. 在 [0.x](https://vuepress.vuejs.org/guide/custom-themes.html#site-and-page-metadata) 中,主题的入口只需要一个 `Layout.vue`,所以我们可以通过把另一个主题的 `Layout.vue` 直接引入来实现复用,这时,如果父 Layout 中提供了一些 slots,那么我们就可以在自己的 Layout 组件中传入这些 slot 了——这实现了简单的主题继承。 +1. VuePress 为开发者提供了一个[默认主题](./default-theme-config.md),它能在大多数场景下满足了文档编写者的需求。即便如此,仍然还是会有不少用户选择将其 eject 出来进行修改,即使他们可能只是想要修改其中的某个组件。 +2. 在 [0.x](https://vuepress.vuejs.org/guide/custom-themes.html#site-and-page-metadata) 中,主题的入口只需要一个 `Layout.vue`,所以我们可以通过包装另一个主题的 `Layout.vue` 来实现简单的拓展。 - 到了 1.x 中,一个主题的配置开始变得复杂,我们开始有了[主题级别的配置](./option-api.md),它支持为主题添加插件、自定义 GlobalLayout 等。除此之外,我们还有一些引入了主题开发的 [目录结构的约定](./writing-a-theme.md#目录结构),如 `styles/index.styl`,在这样的背景下,我们无法使用 0.x 的方式来实现继承了。 + 到了 1.x 中,一个主题的元素开始变得复杂,我们开始有了[主题级别的配置](./option-api.md),它支持为主题添加插件、自定义 GlobalLayout 等。除此之外,我们还有一些引入了主题开发的 [目录结构的约定](./writing-a-theme.md#目录结构),如 `styles/index.styl`,在这样的背景下,我们无法使用 0.x 的方式来实现继承了。 因此,我们需要提供一种合理、可靠的主题继承方案。 @@ -19,7 +19,7 @@ - **派生主题**:即子主题,基于父主题创建的主题; ::: tip 提示 -主题支持暂时不支持高阶继承,也就是说,一个派生主题无法被继承。 +主题继承暂时不支持高阶继承,也就是说,一个派生主题无法被继承。 ::: ## 使用 @@ -34,9 +34,9 @@ module.exports = { ## 继承策略 -**父主题的所有能力都会"传递"给子主题,对于文件级别的约定,子主题可以通过在同样的位置创建同名文件来覆盖它,对于某些主题配置选项,如 [globalLayout](./option-api.md#globallayout),子主题也可以通过同名配置来覆盖它。** +**父主题的所有能力都会"传递"给子主题,对于文件级别的约定,子主题可以通过在同样的位置创建同名文件来覆盖它;对于某些主题配置选项,如 [globalLayout](./option-api.md#globallayout),子主题也可以通过同名配置来覆盖它。** -文件级别的约定如下: +[文件级别的约定](./writing-a-theme.md#目录结构)如下: - **全局组件**,即放置在 `theme/global-components` 中的 Vue 组件。 - **组件**,即放置在 `theme/components` 中的 Vue 组件。 @@ -44,7 +44,7 @@ module.exports = { - **HTML 模板**,即放置在 `theme/templates` 中的 `dev.html` 和 `ssr.html`。 - **主题水平的客户端增强文件**,即 `theme/enhanceApp.js` -能被子主题覆盖的主题配置选项如下: +对于主题配置,能被子主题覆盖的配置选项如下: - [devTemplate](./option-api.md#devtemplate) - [ssrTemplate](./option-api.md#ssrtemplate) @@ -52,15 +52,15 @@ module.exports = { 无法被子主题覆盖的主题配置选项如下: -- [extend](./option-api.md#extend):父主题不会存在 extend。 +- [extend](./option-api.md#extend) 需要特殊处理的主题选项: -- [plugins](./option-api.md#plugins):见下文。 +- [plugins](./option-api.md#plugins):参见 [插件的覆盖](#插件的覆盖)。 ## 插件的覆盖 -对于父主题中的 [plugins](./option-api.md#plugins), 子主题不会覆盖它,但可以通过同名的插件配置来 override 插件的默认选项,如: +对于父主题中的 [plugins](./option-api.md#plugins), 子主题不会直接覆盖它,但是插件的选项可以通过创建同名的插件配置来覆盖。 举例来说,如果父主题具有如下配置: @@ -109,7 +109,7 @@ module.exports = { VuePress 则通过一种巧妙的方式实现了这种需求,但这对父主题有一定的要求——**所有的组件都必须使用 `@theme` 别名来引用其他组件**。 -举例来说,如果你想开发一个父主题,它的结构如下: +举例来说,如果你正在开发的一个原子主题的结构如下: ::: vue theme @@ -124,7 +124,7 @@ theme └── index.js ::: -那么,你在主题中的任意 Vue 组件中,**都应该通过 `@theme` 来访问主题根目录**: +那么,在该主题中的任意 Vue 组件中,**你都应该通过 `@theme` 来访问主题根目录**: ```vue