diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 5b3f866095..4d366c9ee6 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -12,6 +12,14 @@ module.exports = { description: 'Vue 驱动的静态网站生成器' } }, + plugins: [ + [ + require('./vuepress-plugin-custom-domain'), + { + domain: 'www.vuejs.org' + } + ] + ], head: [ ['link', { rel: 'icon', href: `/logo.png` }], ['link', { rel: 'manifest', href: '/manifest.json' }], diff --git a/docs/.vuepress/vuepress-plugin-custom-domain.js b/docs/.vuepress/vuepress-plugin-custom-domain.js new file mode 100644 index 0000000000..a6c1413e45 --- /dev/null +++ b/docs/.vuepress/vuepress-plugin-custom-domain.js @@ -0,0 +1,23 @@ +const path = require('path') +const fs = require('fs-extra') + +module.exports = async function vuepressPluginCustomDomain({ domain }) { + this + + .ready(() => { + console.log('Used vuepress-plugin-custom-domain') + }) + + .compiled(async ({ outDir }) => { + if (process.env.NODE_ENV !== 'production') { + return + } + await fs.ensureDir(outDir) + await fs.writeFile(path.resolve(outDir, 'CHAME'), domain, 'utf-8') + console.log('Generated CHAME file.') + }) + + .updated(() => { + console.log('Updated') + }) +} diff --git a/lib/build.js b/lib/build.js index cd9b67ef7b..2d2007dacc 100644 --- a/lib/build.js +++ b/lib/build.js @@ -13,6 +13,7 @@ module.exports = async function build (sourceDir, cliOptions = {}) { const createServerConfig = require('./webpack/createServerConfig') const { createBundleRenderer } = require('vue-server-renderer') const { normalizeHeadTag, applyUserWebpackConfig } = require('./util') + const lifecycle = require('./lifecycle') process.stdout.write('Extracting site metadata...') const options = await prepare(sourceDir) @@ -20,6 +21,8 @@ module.exports = async function build (sourceDir, cliOptions = {}) { options.outDir = cliOptions.outDir } + lifecycle.notifyReady(Object.assign({}, options)) + const { outDir } = options await fs.remove(outDir) @@ -36,6 +39,8 @@ module.exports = async function build (sourceDir, cliOptions = {}) { // compile! const stats = await compile([clientConfig, serverConfig]) + await lifecycle.notifyCompiled(Object.assign({}, options)) + const serverBundle = require(path.resolve(outDir, 'manifest/server.json')) const clientManifest = require(path.resolve(outDir, 'manifest/client.json')) diff --git a/lib/dev.js b/lib/dev.js index 6205cae828..c4118f7035 100644 --- a/lib/dev.js +++ b/lib/dev.js @@ -16,10 +16,30 @@ module.exports = async function dev (sourceDir, cliOptions = {}) { const createClientConfig = require('./webpack/createClientConfig') const { applyUserWebpackConfig } = require('./util') const { frontmatterEmitter } = require('./webpack/markdownLoader') + const lifecycle = require('./lifecycle') - process.stdout.write('Extracting site metadata...') + process.stdout.write('Extracting site metadata...\n') const options = await prepare(sourceDir) + lifecycle.notifyReady(Object.assign({}, options)) + + if (lifecycle.isEmpty('compiled')) { + lifecycle.compiled(({ port, displayHost }) => { + console.log( + `\n VuePress dev server listening at ${ + chalk.cyan(`http://${displayHost}:${port}${options.publicPath}`) + }\n` + ) + }) + } + + if (lifecycle.isEmpty('updated')) { + lifecycle.updated(() => { + const time = new Date().toTimeString().match(/^[\d:]+/)[0] + console.log(` ${chalk.gray(`[${time}]`)} ${chalk.green('✔')} successfully compiled.`) + }) + } + // setup watchers to update options and dynamically generated files const update = () => { prepare(sourceDir).catch(err => { @@ -87,14 +107,9 @@ module.exports = async function dev (sourceDir, cliOptions = {}) { compiler.hooks.done.tap('vuepress', () => { if (isFirst) { isFirst = false - console.log( - `\n VuePress dev server listening at ${ - chalk.cyan(`http://${displayHost}:${port}${options.publicPath}`) - }\n` - ) + lifecycle.notifyCompiled(Object.assign({ port, displayHost }, options)) } else { - const time = new Date().toTimeString().match(/^[\d:]+/)[0] - console.log(` ${chalk.gray(`[${time}]`)} ${chalk.green('✔')} successfully compiled.`) + lifecycle.notifyUpdated(Object.assign({ port, displayHost }, options)) } }) diff --git a/lib/lifecycle.js b/lib/lifecycle.js new file mode 100644 index 0000000000..f4e0ff5d24 --- /dev/null +++ b/lib/lifecycle.js @@ -0,0 +1,34 @@ +const { EventEmitter } = require('events') + +const HOOK_NAMES = ['ready', 'compiled', 'updated'] + +class VuePressHook extends EventEmitter { + constructor () { + super() + HOOK_NAMES.forEach(hook => { + const handlerKey = `_${hook}Handlers` + const execKey = `notify${hook.charAt(0).toUpperCase() + hook.slice(1)}` + + this[handlerKey] = [] + + // public, Add hook handler + this[hook] = (handler) => { + this[handlerKey].push(handler.bind(this)) + return this + } + + // private, Execute hook handler + this[execKey] = async (...args) => { + for (const handler of this[handlerKey]) { + await handler(...args) + } + } + }) + } + + isEmpty (hook) { + return this[`_${hook}Handlers`].length === 0 + } +} + +module.exports = new VuePressHook() diff --git a/lib/prepare.js b/lib/prepare.js index 2d0fc56db2..567aaa5ffd 100644 --- a/lib/prepare.js +++ b/lib/prepare.js @@ -5,6 +5,7 @@ const yamlParser = require('js-yaml') const tomlParser = require('toml') const createMarkdown = require('./markdown') const tempPath = path.resolve(__dirname, 'app/.temp') +const lifecycle = require('./lifecycle') const { inferTitle, extractHeaders, parseFrontmatter } = require('./util') fs.ensureDirSync(tempPath) @@ -74,6 +75,14 @@ if (!Object.assign) Object.assign = require('object-assign')` fs.existsSync(options.themeEnhanceAppPath) ) + // 8. handle plugins + for (const [pluginConfig, pluginOptions] of options.plugins) { + const pluginFn = resolvePlugin(pluginConfig) + if (pluginFn) { + pluginFn.bind(lifecycle)(pluginOptions) + } + } + return options } @@ -111,6 +120,12 @@ async function resolveOptions (sourceDir) { }) } + // resolve plugins + const plugins = (Array.isArray(siteConfig.plugins) + ? siteConfig.plugins + : [] + ) + // resolve theme const useDefaultTheme = ( !siteConfig.theme && @@ -138,6 +153,7 @@ async function resolveOptions (sourceDir) { notFoundPath: null, useDefaultTheme, isAlgoliaSearch, + plugins, markdown: createMarkdown(siteConfig) } @@ -341,3 +357,20 @@ async function parseConfig (file) { return data || {} } + +function resolvePlugin (pluginConfig) { + if (typeof pluginConfig === 'function') { + return pluginConfig + } + if (typeof pluginConfig === 'string') { + try { + return require(pluginConfig.startsWith('vuepress-plugin-') + ? pluginConfig + : `vuepress-plugin-${pluginConfig}` + ) + } catch (err) { + throw new Error(`[vuepress] Cannot resolve plugin: ${pluginConfig}`) + } + } + throw new Error(`[vuepress] Cannot resolve plugin: ${pluginConfig}`) +}