Skip to content

Commit d16d3d5

Browse files
authored
feat: refine theme api (#1319)
1 parent 40b3da8 commit d16d3d5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1012
-397
lines changed

CHANGELOG.md

100644100755
-45
Original file line numberDiff line numberDiff line change
@@ -22,51 +22,6 @@
2222
### Features
2323

2424
* **$core:** support global layout (close: [#1226](https://github.com/vuejs/vuepress/issues/1226)) ([c91f55a](https://github.com/vuejs/vuepress/commit/c91f55a))
25-
26-
From now on, users have the ability to use a custom global layout component via [siteConfig](https://v1.vuepress.vuejs.org/miscellaneous/glossary.html#siteconfig) or [themeEntryFile](https://v1.vuepress.vuejs.org/miscellaneous/glossary.html#themeentryfile):
27-
28-
```js
29-
module.exports = {
30-
globalLayout: '/path/to/your/global/vue/sfc'
31-
}
32-
```
33-
34-
Here is the [content of default global layout component](https://github.com/vuejs/vuepress/blob/master/packages/%40vuepress/core/lib/app/components/GlobalLayout.vue), an example of setting global header and footer:
35-
36-
```vue
37-
<template>
38-
<div id="global-layout">
39-
<header><h1>Header</h1></header>
40-
<component :is="layout"/>
41-
<footer><h1>Footer</h1></footer>
42-
</div>
43-
</template>
44-
45-
<script>
46-
export default {
47-
computed: {
48-
layout () {
49-
if (this.$page.path) {
50-
if (this.$vuepress.isLayoutExists(this.$page.frontmatter.layout)) {
51-
return this.$page.frontmatter.layout
52-
}
53-
return 'Layout'
54-
}
55-
return 'NotFound'
56-
}
57-
}
58-
}
59-
</script>
60-
```
61-
62-
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:
63-
64-
- siteConfig
65-
- siteAgreement
66-
- themeEntryFile
67-
- themeAgreement
68-
- default
69-
7025
* **$theme-default:** disable search box via frontmatter (close: [#1287](https://github.com/vuejs/vuepress/issues/1287)) ([#1288](https://github.com/vuejs/vuepress/issues/1288)) ([54e9eb0](https://github.com/vuejs/vuepress/commit/54e9eb0))
7126

7227

__mocks__/vuepress-theme-child/Layout.vue

Whitespace-only changes.

__mocks__/vuepress-theme-child/components/Home.vue

Whitespace-only changes.
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
extend: 'vuepress-theme-parent'
3+
}

__mocks__/vuepress-theme-parent/Layout.vue

Whitespace-only changes.

__mocks__/vuepress-theme-parent/components/Home.vue

Whitespace-only changes.

__mocks__/vuepress-theme-parent/components/Sidebar.vue

Whitespace-only changes.
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = {}

packages/@vuepress/core/__tests__/theme-api/fixtures/theme/Layout.vue

Whitespace-only changes.

packages/@vuepress/core/__tests__/theme-api/fixtures/theme/components/Home.vue

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
jest.mock('vuepress-theme-parent')
2+
jest.mock('vuepress-theme-child')
3+
4+
import ThemeAPI from '../../lib/theme-api'
5+
import { resolve } from 'path'
6+
7+
const theme = {
8+
path: resolve(process.cwd(), '__mocks__/vuepress-theme-child'),
9+
name: 'vuepress-theme-child',
10+
shortcut: 'child',
11+
entryFile: require('vuepress-theme-child')
12+
}
13+
14+
const parent = {
15+
path: resolve(process.cwd(), '__mocks__/vuepress-theme-parent'),
16+
name: 'vuepress-theme-parent',
17+
shortcut: 'parent',
18+
entryFile: {}
19+
}
20+
21+
describe('ThemeAPI', () => {
22+
test('extend', async () => {
23+
const themeAPI = new ThemeAPI(theme, parent)
24+
console.log(themeAPI.theme.entry)
25+
})
26+
// loadTheme('vuepress-theme-child')
27+
})

packages/@vuepress/core/lib/internal-plugins/enhanceApp.js

100644100755
+8-6
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ module.exports = (options, context) => ({
44
name: '@vuepress/internal-enhance-app',
55

66
enhanceAppFiles () {
7-
const { sourceDir, themePath } = context
7+
const { sourceDir, themeAPI } = context
88
const enhanceAppPath = path.resolve(sourceDir, '.vuepress/enhanceApp.js')
9-
const themeEnhanceAppPath = path.resolve(themePath, 'enhanceApp.js')
10-
return [
11-
enhanceAppPath,
12-
themeEnhanceAppPath
13-
]
9+
const files = [enhanceAppPath]
10+
if (themeAPI.existsParentTheme) {
11+
files.push(path.resolve(themeAPI.parentTheme.path, 'enhanceApp.js'))
12+
}
13+
const themeEnhanceAppPath = path.resolve(themeAPI.theme.path, 'enhanceApp.js')
14+
files.push(themeEnhanceAppPath)
15+
return files
1416
}
1517
})
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
11
module.exports = (options, ctx) => {
2-
const { layoutComponentMap } = ctx
3-
const componentNames = Object.keys(layoutComponentMap)
4-
52
return {
63
name: '@vuepress/internal-layout-components',
74

85
async clientDynamicModules () {
6+
const componentNames = Object.keys(ctx.themeAPI.layoutComponentMap)
97
const code = `export default {\n${componentNames
10-
.map(name => ` ${JSON.stringify(name)}: () => import(${JSON.stringify(layoutComponentMap[name].path)})`)
8+
.map(name => ` ${JSON.stringify(name)}: () => import(${JSON.stringify(ctx.themeAPI.layoutComponentMap[name].path)})`)
119
.join(',\n')} \n}`
1210
return { name: 'layout-components.js', content: code, dirname: 'internal' }
13-
},
14-
15-
chainWebpack (config, isServer) {
16-
const setAlias = (alias, raw) => config.resolve.alias.set(alias, raw)
17-
componentNames.forEach(name => {
18-
setAlias(`@${name}`, layoutComponentMap[name].path)
19-
})
2011
}
2112
}
2213
}

packages/@vuepress/core/lib/internal-plugins/palette/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ module.exports = (options, ctx) => ({
2020
// 2. write palette.styl
2121
const { sourceDir, writeTemp } = ctx
2222

23-
const themePalette = path.resolve(ctx.themePath, 'styles/palette.styl')
23+
const themePalette = path.resolve(ctx.themeAPI.theme.path, 'styles/palette.styl')
2424
const userPalette = path.resolve(sourceDir, '.vuepress/styles/palette.styl')
2525

2626
const themePaletteContent = fs.existsSync(themePalette)
@@ -34,8 +34,8 @@ module.exports = (options, ctx) => ({
3434
// user's palette can override theme's palette.
3535
let paletteContent = themePaletteContent + userPaletteContent
3636

37-
if (ctx.parentThemePath) {
38-
const parentThemePalette = path.resolve(ctx.parentThemePath, 'styles/palette.styl')
37+
if (ctx.themeAPI.existsParentTheme) {
38+
const parentThemePalette = path.resolve(ctx.themeAPI.parentTheme.path, 'styles/palette.styl')
3939
const parentThemePaletteContent = fs.existsSync(parentThemePalette)
4040
? `@import(${JSON.stringify(parentThemePalette.replace(/[\\]+/g, '/'))})`
4141
: ''

packages/@vuepress/core/lib/internal-plugins/style/index.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
const { fs, path, logger, chalk } = require('@vuepress/shared-utils')
22

3+
/**
4+
* @param options
5+
* @param {AppContext} ctx
6+
*/
37
module.exports = (options, ctx) => ({
48
name: '@vuepress/internal-style',
59

610
enhanceAppFiles: [path.resolve(__dirname, 'client.js')],
711

812
async ready () {
9-
const { sourceDir, writeTemp } = ctx
13+
const { sourceDir, writeTemp, themeAPI } = ctx
1014

1115
const overridePath = path.resolve(sourceDir, '.vuepress/override.styl')
1216
const hasUserOverride = fs.existsSync(overridePath)
@@ -15,7 +19,7 @@ module.exports = (options, ctx) => ({
1519
logger.tip(`${chalk.magenta('override.styl')} has been deprecated from v1.0.0, using ${chalk.cyan('.vuepress/styles/palette.styl')} instead.\n`)
1620
}
1721

18-
const themeStyle = path.resolve(ctx.themePath, 'styles/index.styl')
22+
const themeStyle = path.resolve(themeAPI.theme.path, 'styles/index.styl')
1923
const userStyle = path.resolve(sourceDir, '.vuepress/styles/index.styl')
2024

2125
const themeStyleContent = fs.existsSync(themeStyle)
@@ -28,8 +32,8 @@ module.exports = (options, ctx) => ({
2832

2933
let styleContent = themeStyleContent + userStyleContent
3034

31-
if (ctx.parentThemePath) {
32-
const parentThemeStyle = path.resolve(ctx.parentThemePath, 'styles/index.styl')
35+
if (themeAPI.existsParentTheme) {
36+
const parentThemeStyle = path.resolve(themeAPI.parentTheme.path, 'styles/index.styl')
3337
const parentThemeStyleContent = fs.existsSync(parentThemeStyle)
3438
? `@import(${JSON.stringify(parentThemeStyle.replace(/[\\]+/g, '/'))})`
3539
: ''

packages/@vuepress/core/lib/plugin-api/index.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,11 @@ module.exports = class PluginAPI {
8484
if (isPlainObject(pluginRaw) && pluginRaw.$$normalized) {
8585
plugin = pluginRaw
8686
} else {
87-
plugin = this.normalizePlugin(pluginRaw, pluginOptions)
87+
try {
88+
plugin = this.normalizePlugin(pluginRaw, pluginOptions)
89+
} catch (e) {
90+
logger.warn(e.message)
91+
}
8892
}
8993

9094
if (plugin.multiple !== true) {
@@ -114,8 +118,7 @@ module.exports = class PluginAPI {
114118
normalizePlugin (pluginRaw, pluginOptions = {}) {
115119
let plugin = this._pluginResolver.resolve(pluginRaw)
116120
if (!plugin.entry) {
117-
console.warn(`[vuepress] cannot resolve plugin "${pluginRaw}"`)
118-
return this
121+
throw new Error(`[vuepress] cannot resolve plugin "${pluginRaw}"`)
119122
}
120123
plugin = flattenPlugin(plugin, pluginOptions, this._pluginContext, this)
121124
plugin.$$normalized = true

packages/@vuepress/core/lib/prepare/AppContext.js

100644100755
+34-60
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ module.exports = class AppContext {
9494
this.resolveConfigAndInitialize()
9595
this.resolveCacheLoaderOptions()
9696
this.normalizeHeadTagUrls()
97-
await this.resolveTheme()
97+
this.themeAPI = loadTheme(this)
9898
this.resolveTemplates()
9999
this.resolveGlobalLayout()
100100

@@ -137,7 +137,7 @@ module.exports = class AppContext {
137137
)
138138

139139
this.pluginAPI
140-
// internl core plugins
140+
// internl core plugins
141141
.use(require('../internal-plugins/siteData'))
142142
.use(require('../internal-plugins/routes'))
143143
.use(require('../internal-plugins/rootMixins'))
@@ -153,8 +153,8 @@ module.exports = class AppContext {
153153
.use('@vuepress/register-components', {
154154
componentsDir: [
155155
path.resolve(this.sourceDir, '.vuepress/components'),
156-
path.resolve(this.themePath, 'global-components'),
157-
this.parentThemePath && path.resolve(this.parentThemePath, 'global-components')
156+
path.resolve(this.themeAPI.theme.path, 'global-components'),
157+
this.themeAPI.existsParentTheme && path.resolve(this.themeAPI.parentTheme.path, 'global-components')
158158
]
159159
})
160160
}
@@ -167,11 +167,12 @@ module.exports = class AppContext {
167167

168168
applyUserPlugins () {
169169
this.pluginAPI.useByPluginsConfig(this.cliOptions.plugins)
170-
if (this.parentThemePath) {
171-
this.pluginAPI.use(this.parentThemeEntryFile)
170+
if (this.themeAPI.existsParentTheme) {
171+
this.pluginAPI.use(this.themeAPI.parentTheme.entry)
172172
}
173173
this.pluginAPI
174-
.use(this.themeEntryFile)
174+
.use(this.themeAPI.theme.entry)
175+
.use(this.themeAPI.vuepressPlugin)
175176
.use(Object.assign({}, this.siteConfig, { name: '@vuepress/internal-site-config' }))
176177
}
177178

@@ -221,41 +222,26 @@ module.exports = class AppContext {
221222
*/
222223

223224
resolveTemplates () {
224-
const { siteSsrTemplate, siteDevTemplate } = this.siteConfig
225-
226-
const templateDir = path.resolve(this.vuepressDir, 'templates')
227-
const siteSsrTemplate2 = path.resolve(templateDir, 'ssr.html')
228-
const siteDevTemplate2 = path.resolve(templateDir, 'dev.html')
229-
230-
const themeSsrTemplate = path.resolve(this.themePath, 'templates/ssr.html')
231-
const themeDevTemplate = path.resolve(this.themePath, 'templates/dev.html')
232-
233-
const parentThemeSsrTemplate = path.resolve(this.themePath, 'templates/ssr.html')
234-
const parentThemeDevTemplate = path.resolve(this.themePath, 'templates/dev.html')
235-
236-
const defaultSsrTemplate = path.resolve(__dirname, '../app/index.ssr.html')
237-
const defaultDevTemplate = path.resolve(__dirname, '../app/index.dev.html')
238-
239-
const ssrTemplate = fsExistsFallback([
240-
siteSsrTemplate,
241-
siteSsrTemplate2,
242-
themeSsrTemplate,
243-
parentThemeSsrTemplate,
244-
defaultSsrTemplate
245-
])
225+
this.devTemplate = this.resolveCommonAgreementFilePath(
226+
'devTemplate',
227+
{
228+
defaultValue: path.resolve(__dirname, '../app/index.dev.html'),
229+
siteAgreement: 'templates/dev.html',
230+
themeAgreement: 'templates/dev.html'
231+
}
232+
)
246233

247-
const devTemplate = fsExistsFallback([
248-
siteDevTemplate,
249-
siteDevTemplate2,
250-
themeDevTemplate,
251-
parentThemeDevTemplate,
252-
defaultDevTemplate
253-
])
234+
this.ssrTemplate = this.resolveCommonAgreementFilePath(
235+
'ssrTemplate',
236+
{
237+
defaultValue: path.resolve(__dirname, '../app/index.ssr.html'),
238+
siteAgreement: 'templates/ssr.html',
239+
themeAgreement: 'templates/ssr.html'
240+
}
241+
)
254242

255-
logger.debug('SSR Template File: ' + chalk.gray(ssrTemplate))
256-
logger.debug('DEV Template File: ' + chalk.gray(devTemplate))
257-
this.devTemplate = devTemplate
258-
this.ssrTemplate = ssrTemplate
243+
logger.debug('SSR Template File: ' + chalk.gray(this.ssrTemplate))
244+
logger.debug('DEV Template File: ' + chalk.gray(this.devTemplate))
259245
}
260246

261247
/**
@@ -266,14 +252,12 @@ module.exports = class AppContext {
266252
*/
267253

268254
resolveGlobalLayout () {
269-
const GLOBAL_LAYOUT_COMPONENT_NAME = `GlobalLayout`
270-
271255
this.globalLayout = this.resolveCommonAgreementFilePath(
272256
'globalLayout',
273257
{
274-
defaultValue: path.resolve(__dirname, `../app/components/${GLOBAL_LAYOUT_COMPONENT_NAME}.vue`),
275-
siteAgreement: `components/${GLOBAL_LAYOUT_COMPONENT_NAME}.vue`,
276-
themeAgreement: `layouts/${GLOBAL_LAYOUT_COMPONENT_NAME}.vue`
258+
defaultValue: path.resolve(__dirname, `../app/components/GlobalLayout.vue`),
259+
siteAgreement: `components/GlobalLayout.vue`,
260+
themeAgreement: `layouts/GlobalLayout.vue`
277261
}
278262
)
279263

@@ -354,17 +338,6 @@ module.exports = class AppContext {
354338
this.pages.push(page)
355339
}
356340

357-
/**
358-
* Resolve theme
359-
*
360-
* @returns {Promise<void>}
361-
* @api private
362-
*/
363-
364-
async resolveTheme () {
365-
Object.assign(this, (await loadTheme(this)))
366-
}
367-
368341
/**
369342
* Get config value of current active theme.
370343
*
@@ -374,7 +347,8 @@ module.exports = class AppContext {
374347
*/
375348

376349
getThemeConfigValue (key) {
377-
return this.themeEntryFile[key] || this.parentThemeEntryFile[key]
350+
return this.themeAPI.theme.entry[key]
351+
|| this.themeAPI.existsParentTheme && this.themeAPI.parentTheme.entry[key]
378352
}
379353

380354
/**
@@ -386,12 +360,12 @@ module.exports = class AppContext {
386360
*/
387361

388362
resolveThemeAgreementFile (filepath) {
389-
const current = path.resolve(this.themePath, filepath)
363+
const current = path.resolve(this.themeAPI.theme.path, filepath)
390364
if (fs.existsSync(current)) {
391365
return current
392366
}
393-
if (this.parentThemePath) {
394-
const parent = path.resolve(this.parentThemePath, filepath)
367+
if (this.themeAPI.existsParentTheme) {
368+
const parent = path.resolve(this.themeAPI.theme.path, filepath)
395369
if (fs.existsSync(parent)) {
396370
return parent
397371
}

0 commit comments

Comments
 (0)