mirror of
https://github.com/wahyd4/vuepress.git
synced 2026-08-09 05:16:23 +10:00
feat: refine plugin API
This commit is contained in:
@@ -1 +1 @@
|
||||
module.exports = '@org/vuepress-plugin-a'
|
||||
module.exports = {}
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = '@org/vuepress-plugin-b'
|
||||
module.exports = {}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = {}
|
||||
@@ -1 +1 @@
|
||||
module.exports = 'vuepress-plugin-a'
|
||||
module.exports = {}
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = 'vuepress-plugin-b'
|
||||
module.exports = {}
|
||||
|
||||
+23
-23
@@ -1,46 +1,46 @@
|
||||
// TODO change to ES6 import
|
||||
// https://github.com/facebook/jest/issues/6835
|
||||
|
||||
const Tapable = require('../../lib/plugin-api/core/Tapable')
|
||||
const Option = require('../../lib/plugin-api/Option')
|
||||
|
||||
describe('Tapable', () => {
|
||||
test('shoould tapable record the key', () => {
|
||||
const tapable = new Tapable('option')
|
||||
expect(tapable.key).toBe('option')
|
||||
describe('Option', () => {
|
||||
test('shoould option record the key', () => {
|
||||
const option = new Option('option')
|
||||
expect(option.key).toBe('option')
|
||||
})
|
||||
|
||||
test('should \'tap\' work', () => {
|
||||
const tapable = new Tapable('option')
|
||||
tapable.tap('plugin-a', 'a')
|
||||
tapable.tap('plugin-b', 'b')
|
||||
expect(tapable.items).toEqual([
|
||||
const option = new Option('option')
|
||||
option.tap('plugin-a', 'a')
|
||||
option.tap('plugin-b', 'b')
|
||||
expect(option.items).toEqual([
|
||||
{ value: 'a', name: 'plugin-a' },
|
||||
{ value: 'b', name: 'plugin-b' }
|
||||
])
|
||||
expect(tapable.values).toEqual(['a', 'b'])
|
||||
expect(option.values).toEqual(['a', 'b'])
|
||||
})
|
||||
|
||||
test('should \'tap\' resolve array value', () => {
|
||||
const tapable = new Tapable('option')
|
||||
tapable.tap('plugin-a', ['a-1', 'a-2'])
|
||||
tapable.tap('plugin-b', 'b')
|
||||
expect(tapable.items).toEqual([
|
||||
const option = new Option('option')
|
||||
option.tap('plugin-a', ['a-1', 'a-2'])
|
||||
option.tap('plugin-b', 'b')
|
||||
expect(option.items).toEqual([
|
||||
{ value: 'a-1', name: 'plugin-a' },
|
||||
{ value: 'a-2', name: 'plugin-a' },
|
||||
{ value: 'b', name: 'plugin-b' }
|
||||
])
|
||||
expect(tapable.values).toEqual(['a-1', 'a-2', 'b'])
|
||||
expect(option.values).toEqual(['a-1', 'a-2', 'b'])
|
||||
})
|
||||
|
||||
test('should \'run\' work', async () => {
|
||||
const tapable = new Tapable('option')
|
||||
const option = new Option('option')
|
||||
const handler1 = jest.fn()
|
||||
const handler2 = jest.fn()
|
||||
|
||||
tapable.tap('plugin-a', handler1)
|
||||
tapable.tap('plugin-b', handler2)
|
||||
option.tap('plugin-a', handler1)
|
||||
option.tap('plugin-b', handler2)
|
||||
|
||||
await tapable.run(1, 2)
|
||||
await option.run(1, 2)
|
||||
expect(handler1.mock.calls).toHaveLength(1)
|
||||
expect(handler2.mock.calls).toHaveLength(1)
|
||||
expect(handler1.mock.calls[0][0]).toBe(1)
|
||||
@@ -50,14 +50,14 @@ describe('Tapable', () => {
|
||||
})
|
||||
|
||||
test('should \'parallelRun\' work', async () => {
|
||||
const tapable = new Tapable('option')
|
||||
const option = new Option('option')
|
||||
const handler1 = jest.fn()
|
||||
const handler2 = jest.fn()
|
||||
|
||||
tapable.tap('plugin-a', handler1)
|
||||
tapable.tap('plugin-b', handler2)
|
||||
option.tap('plugin-a', handler1)
|
||||
option.tap('plugin-b', handler2)
|
||||
|
||||
await tapable.parallelRun(1, 2)
|
||||
await option.parallelRun(1, 2)
|
||||
expect(handler1.mock.calls).toHaveLength(1)
|
||||
expect(handler2.mock.calls).toHaveLength(1)
|
||||
expect(handler1.mock.calls[0][0]).toBe(1)
|
||||
@@ -1,42 +1,15 @@
|
||||
jest.mock('vuepress-plugin-a')
|
||||
jest.mock('@org/vuepress-plugin-a')
|
||||
|
||||
const {
|
||||
resolvePlugin,
|
||||
resolveScopePackage
|
||||
} = require('../../lib/plugin-api/util')
|
||||
const Plugin = require('../../lib/plugin-api/index')
|
||||
const { PLUGIN_OPTION_MAP } = require('../../lib/plugin-api/constants')
|
||||
|
||||
// const Plugin = require('../../lib/plugin-api/index')
|
||||
|
||||
describe('resolvePlugin', () => {
|
||||
test('should resolve scope packages correctly', () => {
|
||||
const pkg1 = resolveScopePackage('@vuepress/plugin-a')
|
||||
expect(pkg1.org).toBe('vuepress')
|
||||
expect(pkg1.name).toBe('plugin-a')
|
||||
const pkg2 = resolveScopePackage('vuepress/plugin-a')
|
||||
expect(pkg2).toBe(null)
|
||||
const pkg3 = resolveScopePackage('vuepress-plugin-a')
|
||||
expect(pkg3).toBe(null)
|
||||
})
|
||||
|
||||
test('shoould return raw when function or object is given', () => {
|
||||
const plugin1 = () => {}
|
||||
const plugin2 = {}
|
||||
expect(resolvePlugin(plugin1)).toBe(plugin1)
|
||||
expect(resolvePlugin(plugin2)).toBe(plugin2)
|
||||
})
|
||||
|
||||
// https://jestjs.io/docs/en/manual-mocks#mocking-node-modules
|
||||
test('shoould resolve fullname correctly', () => {
|
||||
expect(resolvePlugin('vuepress-plugin-a')).toBe('vuepress-plugin-a')
|
||||
expect(resolvePlugin('@org/vuepress-plugin-a')).toBe('@org/vuepress-plugin-a')
|
||||
})
|
||||
|
||||
// https://jestjs.io/docs/en/manual-mocks#mocking-node-modules
|
||||
test('shoould resolve shortcut correctly', () => {
|
||||
expect(resolvePlugin('a')).toBe('vuepress-plugin-a')
|
||||
expect(resolvePlugin('@org/a')).toBe('@org/vuepress-plugin-a')
|
||||
// special shortcut for vuepress
|
||||
expect(resolvePlugin('@vuepress/a')).toBe('@vuepress/plugin-a')
|
||||
describe('Plugin', () => {
|
||||
test('should resolve scope packages correctly.', () => {
|
||||
const plugin = new Plugin()
|
||||
const readyHandler = () => {}
|
||||
plugin.registerOption(PLUGIN_OPTION_MAP.READY.key, readyHandler)
|
||||
expect(plugin.options.ready.values).toHaveLength(1)
|
||||
expect(plugin.options.ready.values[0]).toBe(readyHandler)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
jest.mock('vuepress-plugin-a')
|
||||
jest.mock('@org/vuepress-plugin-a')
|
||||
|
||||
const {
|
||||
resolvePlugin,
|
||||
hydratePlugin,
|
||||
resolveScopePackage
|
||||
} = require('../../lib/plugin-api/util')
|
||||
|
||||
function resolveMockModule (name) {
|
||||
return require(`../../../../../__mocks__/${name}`)
|
||||
}
|
||||
|
||||
// const Plugin = require('../../lib/plugin-api/index')
|
||||
|
||||
describe('resolvePlugin', () => {
|
||||
test('should resolve scope packages correctly.', () => {
|
||||
const pkg1 = resolveScopePackage('@vuepress/plugin-a')
|
||||
expect(pkg1.org).toBe('vuepress')
|
||||
expect(pkg1.name).toBe('plugin-a')
|
||||
|
||||
const pkg2 = resolveScopePackage('vuepress/plugin-a')
|
||||
expect(pkg2).toBe(null)
|
||||
|
||||
const pkg3 = resolveScopePackage('vuepress-plugin-a')
|
||||
expect(pkg3).toBe(null)
|
||||
})
|
||||
|
||||
test('shoould resolve local plugin as expected.', () => {
|
||||
const plugin1 = () => {}
|
||||
const plugin2 = {}
|
||||
expect(resolvePlugin(plugin1)).toEqual({ name: 'plugin1', shortcut: 'plugin1', config: plugin1 })
|
||||
expect(resolvePlugin(plugin2)).toEqual({ name: 'anonymous-1', shortcut: 'anonymous-1', config: plugin2 })
|
||||
})
|
||||
|
||||
test('shoould resolve fullname usage correctly.', () => {
|
||||
let plugin = resolvePlugin('vuepress-plugin-a')
|
||||
expect(plugin.name).toBe('vuepress-plugin-a')
|
||||
expect(plugin.shortcut).toBe('a')
|
||||
expect(plugin.config).toBe(resolveMockModule('vuepress-plugin-a'))
|
||||
|
||||
plugin = resolvePlugin('@org/vuepress-plugin-a')
|
||||
expect(plugin.name).toBe('@org/vuepress-plugin-a')
|
||||
expect(plugin.shortcut).toBe('@org/a')
|
||||
expect(plugin.config).toBe(resolveMockModule('@org/vuepress-plugin-a'))
|
||||
})
|
||||
|
||||
test('shoould resolve shortcut usage correctly.', () => {
|
||||
// normal package
|
||||
let plugin = resolvePlugin('a')
|
||||
expect(plugin.name).toBe('vuepress-plugin-a')
|
||||
expect(plugin.shortcut).toBe('a')
|
||||
expect(plugin.config).toBe(resolveMockModule('vuepress-plugin-a'))
|
||||
|
||||
// scope packages
|
||||
plugin = resolvePlugin('@org/a')
|
||||
expect(plugin.name).toBe('@org/vuepress-plugin-a')
|
||||
expect(plugin.shortcut).toBe('@org/a')
|
||||
expect(plugin.config).toBe(resolveMockModule('@org/vuepress-plugin-a'))
|
||||
|
||||
// special case for @vuepress package
|
||||
plugin = resolvePlugin('@vuepress/a')
|
||||
expect(plugin.name).toBe('@vuepress/plugin-a')
|
||||
expect(plugin.shortcut).toBe('@vuepress/a')
|
||||
expect(plugin.config).toBe(resolveMockModule('@vuepress/plugin-a'))
|
||||
})
|
||||
|
||||
test('shoould return null when plugin cannot be resolved.', () => {
|
||||
expect(resolvePlugin('c')).toEqual({ name: 'c', shortcut: 'c', config: null })
|
||||
})
|
||||
})
|
||||
|
||||
describe('hydratePlugin', () => {
|
||||
test('shoould hydrate plugin correctly', () => {
|
||||
const plugin = { name: 'a', shortcut: 'a', config: { enhanceAppFiles: 'file' }}
|
||||
const hydratedPlugin = hydratePlugin(plugin, {}, {})
|
||||
expect(hydratedPlugin.name).toBe('a')
|
||||
expect(hydratedPlugin.shortcut).toBe('a')
|
||||
expect(hydratedPlugin.enabled).toBe(true)
|
||||
expect(hydratedPlugin.enhanceAppFiles).toBe('file')
|
||||
})
|
||||
|
||||
test('shoould set \'enabled\' to false when \'pluginOptions\' is set to false.', () => {
|
||||
const plugin = { name: 'a', shortcut: 'a', config: {}}
|
||||
const hydratedPlugin = hydratePlugin(plugin, false, {})
|
||||
expect(hydratedPlugin.name).toBe('a')
|
||||
expect(hydratedPlugin.shortcut).toBe('a')
|
||||
expect(hydratedPlugin.enabled).toBe(false)
|
||||
})
|
||||
|
||||
test('shoould hydrate functional plugin correctly.', () => {
|
||||
const config = jest.fn(() => ({ enhanceAppFiles: 'file' }))
|
||||
const plugin = { name: 'a', shortcut: 'a', config }
|
||||
const pluginOptions = {}
|
||||
const pluginContext = {}
|
||||
const hydratedPlugin = hydratePlugin(plugin, pluginOptions, pluginContext)
|
||||
expect(hydratedPlugin.name).toBe('a')
|
||||
expect(hydratedPlugin.shortcut).toBe('a')
|
||||
expect(hydratedPlugin.enabled).toBe(true)
|
||||
expect(hydratedPlugin.enhanceAppFiles).toBe('file')
|
||||
expect(config.mock.calls).toHaveLength(1)
|
||||
expect(config.mock.calls[0][0]).toBe(pluginOptions)
|
||||
expect(Object.getPrototypeOf(config.mock.calls[0][1])).toBe(pluginContext)
|
||||
})
|
||||
})
|
||||
@@ -94,7 +94,7 @@ module.exports = async function build (sourceDir, cliOptions = {}) {
|
||||
)
|
||||
}
|
||||
|
||||
await options.plugin.hooks.generated.run()
|
||||
await options.plugin.options.generated.run()
|
||||
|
||||
// DONE.
|
||||
const relativeDir = path.relative(process.cwd(), outDir)
|
||||
|
||||
@@ -24,7 +24,7 @@ module.exports = async function dev (sourceDir, cliOptions = {}) {
|
||||
|
||||
// setup watchers to update options and dynamically generated files
|
||||
const update = () => {
|
||||
options.plugin.hooks.updated.run()
|
||||
options.plugin.options.updated.run()
|
||||
prepare(sourceDir).catch(err => {
|
||||
console.error(logger.error(chalk.red(err.stack), false))
|
||||
})
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
module.exports = class Tapable {
|
||||
module.exports = class Option {
|
||||
constructor (key) {
|
||||
this.key = key
|
||||
this.items = [] // Array<{ value: T, name: string }>
|
||||
@@ -1,38 +1,28 @@
|
||||
const READY = 'ready'
|
||||
const COMPILED = 'compiled'
|
||||
const UPDATED = 'updated'
|
||||
const GENERATED = 'generated'
|
||||
|
||||
const CHAIN_WEBPACK = 'chainWebpack'
|
||||
const ENHANCE_DEV_SERVER = 'enhanceDevServer'
|
||||
const ENHANCE_APP_FILES = 'enhanceAppFiles'
|
||||
const OUT_FILES = 'outFiles'
|
||||
const EXTEND_PAGE_DATA = 'extendPageData'
|
||||
const EXTEND_MARKDOWN = 'extendMarkdown'
|
||||
const CLIENT_DYNAMIC_MODULES = 'clientDynamicModules'
|
||||
const CLIENT_ROOT_MIXIN = 'clientRootMixin'
|
||||
const ADDITIONAL_PAGES = 'additionalPages'
|
||||
const GLOBAL_UI_COMPONENTS = 'globalUIComponents'
|
||||
|
||||
const HOOK = {
|
||||
READY,
|
||||
COMPILED,
|
||||
UPDATED,
|
||||
GENERATED
|
||||
const PLUGIN_OPTION_META_MAP = {
|
||||
// hooks
|
||||
READY: { name: 'ready', types: [Function] },
|
||||
COMPILED: { name: 'compiled', types: [Function] },
|
||||
UPDATED: { name: 'updated', types: [Function] },
|
||||
GENERATED: { name: 'generated', types: [Function] },
|
||||
// options
|
||||
CHAIN_WEBPACK: { name: 'chainWebpack', types: [Function] },
|
||||
ENHANCE_DEV_SERVER: { name: 'enhanceDevServer', types: [Function] },
|
||||
ENHANCE_APP_FILES: { name: 'enhanceAppFiles', types: [Array, Function] },
|
||||
OUT_FILES: { name: 'outFiles', types: [Object] },
|
||||
EXTEND_PAGE_DATA: { name: 'extendPageData', types: [Function] },
|
||||
EXTEND_MARKDOWN: { name: 'extendMarkdown', types: [Function] },
|
||||
CLIENT_DYNAMIC_MODULES: { name: 'clientDynamicModules', types: [Function] },
|
||||
CLIENT_ROOT_MIXIN: { name: 'clientRootMixin', types: [String] },
|
||||
ADDITIONAL_PAGES: { name: 'additionalPages', types: [Function, Array] },
|
||||
GLOBAL_UI_COMPONENTS: { name: 'globalUIComponents', types: [String, Array] }
|
||||
}
|
||||
|
||||
const OPTION = {
|
||||
CHAIN_WEBPACK,
|
||||
ENHANCE_DEV_SERVER,
|
||||
ENHANCE_APP_FILES,
|
||||
OUT_FILES,
|
||||
EXTEND_PAGE_DATA,
|
||||
EXTEND_MARKDOWN,
|
||||
CLIENT_DYNAMIC_MODULES,
|
||||
CLIENT_ROOT_MIXIN,
|
||||
ADDITIONAL_PAGES,
|
||||
GLOBAL_UI_COMPONENTS
|
||||
}
|
||||
const PLUGIN_OPTION_MAP = {}
|
||||
Object.keys(PLUGIN_OPTION_META_MAP).forEach(key => {
|
||||
PLUGIN_OPTION_MAP[key] = Object.assign({ key }, PLUGIN_OPTION_META_MAP[key])
|
||||
})
|
||||
|
||||
exports.HOOK = HOOK
|
||||
exports.OPTION = OPTION
|
||||
const OPTION_NAMES = Object.keys(PLUGIN_OPTION_META_MAP).map(key => PLUGIN_OPTION_META_MAP[key].name)
|
||||
|
||||
exports.PLUGIN_OPTION_MAP = PLUGIN_OPTION_MAP
|
||||
exports.OPTION_NAMES = OPTION_NAMES
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
const Tapable = require('./Tapable')
|
||||
|
||||
module.exports = class Hook extends Tapable {}
|
||||
@@ -1,33 +1,24 @@
|
||||
const chalk = require('chalk')
|
||||
const Hook = require('./core/Hook')
|
||||
const instantiateAPI = require('./option/instantiateOption')
|
||||
const logger = require('../util/logger')
|
||||
const { resolvePlugin, inferPluginName } = require('./util')
|
||||
const instantiateOption = require('./option/instantiateOption')
|
||||
const { resolvePlugin, hydratePlugin, normalizePluginsConfig } = require('./util')
|
||||
const { assertTypes } = require('../util/shared')
|
||||
const { HOOK, OPTION } = require('./constants')
|
||||
const { PLUGIN_OPTION_MAP } = require('./constants')
|
||||
|
||||
module.exports = class Plugin {
|
||||
constructor (context) {
|
||||
this.hooks = {}
|
||||
this.options = {}
|
||||
this._pluginContext = context
|
||||
this.extendHooks(Object.values(HOOK))
|
||||
this.extendOptions(Object.values(OPTION))
|
||||
this.initializeOptions(PLUGIN_OPTION_MAP)
|
||||
}
|
||||
|
||||
use (pluginRaw, pluginOptions) {
|
||||
use (pluginRaw, pluginOptions = {}) {
|
||||
let plugin = resolvePlugin(pluginRaw)
|
||||
if (typeof plugin === 'function') {
|
||||
// 'Object.create' here is to give each plugin a separate context,
|
||||
// but also own the inheritance context.
|
||||
plugin = plugin(pluginOptions, Object.create(this._pluginContext))
|
||||
if (!plugin.config) {
|
||||
console.warn(`[vuepress] cannot resolve plugin "${pluginRaw}"`)
|
||||
return
|
||||
}
|
||||
|
||||
plugin = Object.assign({
|
||||
enabled: true,
|
||||
name: inferPluginName(pluginRaw, plugin)
|
||||
}, plugin)
|
||||
|
||||
plugin = hydratePlugin(plugin, pluginOptions, this._pluginContext)
|
||||
if (plugin.enabled) {
|
||||
this.applyPlugin(plugin)
|
||||
} else {
|
||||
@@ -36,60 +27,39 @@ module.exports = class Plugin {
|
||||
return this
|
||||
}
|
||||
|
||||
useByConfigs (pluginConfigs) {
|
||||
if (!Array.isArray(pluginConfigs)) {
|
||||
pluginConfigs = []
|
||||
}
|
||||
pluginConfigs.forEach(pluginConfigs => {
|
||||
pluginConfigs = Array.isArray(pluginConfigs)
|
||||
? pluginConfigs
|
||||
: [pluginConfigs]
|
||||
const [pluginRaw, pluginOptions] = pluginConfigs
|
||||
useByPluginsConfig (pluginsConfig) {
|
||||
pluginsConfig = normalizePluginsConfig(pluginsConfig)
|
||||
pluginsConfig.forEach(([pluginRaw, pluginOptions]) => {
|
||||
this.use(pluginRaw, pluginOptions)
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
extendHooks (hooks) {
|
||||
hooks.forEach(hook => {
|
||||
this.hooks[hook] = new Hook(hook)
|
||||
initializeOptions () {
|
||||
Object.keys(PLUGIN_OPTION_MAP).forEach(key => {
|
||||
const option = PLUGIN_OPTION_MAP[key]
|
||||
this.options[option.name] = instantiateOption(option.name)
|
||||
})
|
||||
}
|
||||
|
||||
extendOptions (options) {
|
||||
options.forEach(api => {
|
||||
this.options[api] = instantiateAPI(api)
|
||||
})
|
||||
}
|
||||
|
||||
registerHook (name, hook, pluginName, types) {
|
||||
const { valid, warnMsg } = assertTypes(hook, types)
|
||||
registerOption (key, value, pluginName) {
|
||||
const option = PLUGIN_OPTION_MAP[key]
|
||||
const types = option.types
|
||||
const { valid, warnMsg } = assertTypes(value, types)
|
||||
if (valid) {
|
||||
this.hooks[name].tap(pluginName, hook)
|
||||
} else if (hook !== undefined) {
|
||||
this.options[option.name].tap(pluginName, value)
|
||||
} else if (value !== undefined) {
|
||||
logger.warn(
|
||||
`${chalk.gray(`[vuepress-plugin-${pluginName}]`)} ` +
|
||||
`Invalid value for "hook" ${chalk.cyan(name)}: ${warnMsg}`
|
||||
)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
registerOption (name, api, pluginName, types) {
|
||||
const { valid, warnMsg } = assertTypes(api, types)
|
||||
if (valid) {
|
||||
this.options[name].tap(pluginName, api)
|
||||
} else if (api !== undefined) {
|
||||
logger.warn(
|
||||
`${chalk.gray(`[vuepress-plugin-${pluginName}]`)} ` +
|
||||
`Invalid value for "option" ${chalk.cyan(name)}: ${warnMsg}`
|
||||
`Invalid value for "option" ${chalk.cyan(option.name)}: ${warnMsg}`
|
||||
)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
applyPlugin ({
|
||||
name,
|
||||
name: pluginName,
|
||||
shortcut,
|
||||
chainWebpack,
|
||||
enhanceDevServer,
|
||||
extendMarkdown,
|
||||
@@ -105,24 +75,22 @@ module.exports = class Plugin {
|
||||
additionalPages,
|
||||
globalUIComponents
|
||||
}) {
|
||||
logger.tip(`\nApply plugin ${chalk.gray(name)}...`)
|
||||
logger.tip(`\nApply plugin ${chalk.gray(pluginName)}...`)
|
||||
|
||||
this
|
||||
.registerHook(HOOK.READY, ready, name, [Function])
|
||||
.registerHook(HOOK.COMPILED, compiled, name, [Function])
|
||||
.registerHook(HOOK.UPDATED, updated, name, [Function])
|
||||
.registerHook(HOOK.GENERATED, generated, name, [Function])
|
||||
|
||||
this
|
||||
.registerOption(OPTION.CHAIN_WEBPACK, chainWebpack, name, [Function])
|
||||
.registerOption(OPTION.ENHANCE_DEV_SERVER, enhanceDevServer, name, [Function])
|
||||
.registerOption(OPTION.EXTEND_MARKDOWN, extendMarkdown, name, [Function])
|
||||
.registerOption(OPTION.EXTEND_PAGE_DATA, extendPageData, name, [Function])
|
||||
.registerOption(OPTION.ENHANCE_APP_FILES, enhanceAppFiles, name, [Array, Function])
|
||||
.registerOption(OPTION.OUT_FILES, outFiles, name, [Object])
|
||||
.registerOption(OPTION.CLIENT_DYNAMIC_MODULES, clientDynamicModules, name, [Function])
|
||||
.registerOption(OPTION.CLIENT_ROOT_MIXIN, clientRootMixin, name, [String])
|
||||
.registerOption(OPTION.ADDITIONAL_PAGES, additionalPages, name, [Function, Array])
|
||||
.registerOption(OPTION.GLOBAL_UI_COMPONENTS, globalUIComponents, name, [String, Array])
|
||||
.registerOption(PLUGIN_OPTION_MAP.READY.key, ready, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.COMPILED.key, compiled, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.UPDATED.key, updated, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.GENERATED.key, generated, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.CHAIN_WEBPACK.key, chainWebpack, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.ENHANCE_DEV_SERVER.key, enhanceDevServer, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.EXTEND_MARKDOWN.key, extendMarkdown, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.EXTEND_PAGE_DATA.key, extendPageData, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.ENHANCE_APP_FILES.key, enhanceAppFiles, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.OUT_FILES.key, outFiles, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.CLIENT_DYNAMIC_MODULES.key, clientDynamicModules, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.CLIENT_ROOT_MIXIN.key, clientRootMixin, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.ADDITIONAL_PAGES.key, additionalPages, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.GLOBAL_UI_COMPONENTS.key, globalUIComponents, pluginName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const Tapable = require('../core/Tapable')
|
||||
const Option = require('../Option')
|
||||
|
||||
module.exports = class AdditionalPagesOption extends Tapable {
|
||||
module.exports = class AdditionalPagesOption extends Option {
|
||||
tap (pluginName, value) {
|
||||
if (typeof value === 'function') {
|
||||
value = value()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const Tapable = require('../core/Tapable')
|
||||
const Option = require('../Option')
|
||||
const { writeTemp } = require('../../prepare/util')
|
||||
|
||||
module.exports = class ClientDynamicModulesOption extends Tapable {
|
||||
module.exports = class ClientDynamicModulesOption extends Option {
|
||||
async run () {
|
||||
for (const item of this.items) {
|
||||
const { value: fn, name: pluginName } = item
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
const fs = require('fs-extra')
|
||||
const chalk = require('chalk')
|
||||
const Tapable = require('../core/Tapable')
|
||||
const Option = require('../Option')
|
||||
const { writeTemp } = require('../../prepare/util')
|
||||
const { pathsToModuleCode } = require('../../prepare/codegen')
|
||||
const logger = require('../../util/logger')
|
||||
|
||||
module.exports = class EnhanceAppFilesOption extends Tapable {
|
||||
module.exports = class EnhanceAppFilesOption extends Option {
|
||||
/**
|
||||
* In fact, we can quickly implement support for function parameters
|
||||
* by overriding 'tap', but 'tap' will be executed immediately
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const Tapable = require('../core/Tapable')
|
||||
const Option = require('../Option')
|
||||
|
||||
module.exports = class ExtendPageDataOption extends Tapable {
|
||||
module.exports = class ExtendPageDataOption extends Option {
|
||||
async run (args) {
|
||||
const { data } = args
|
||||
for (const fn of this.values) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const Tapable = require('../core/Tapable')
|
||||
const Option = require('../Option')
|
||||
const { writeTemp } = require('../../prepare/util')
|
||||
|
||||
module.exports = class GlobalUIComponentsOption extends Tapable {
|
||||
module.exports = class GlobalUIComponentsOption extends Option {
|
||||
async run () {
|
||||
writeTemp(
|
||||
`dynamic-modules/global-ui.js`,
|
||||
|
||||
@@ -3,26 +3,26 @@ const ExtendPageDataOption = require('./ExtendPageDataOption')
|
||||
const ClientDynamicModulesOption = require('./ClientDynamicModulesOption')
|
||||
const AdditionalPagesOption = require('./AdditionalPagesOption')
|
||||
const GlobalUIComponentsOption = require('./GlobalUIComponentsOption')
|
||||
const Tapable = require('../core/Tapable')
|
||||
const { OPTION } = require('../constants')
|
||||
const Option = require('../Option')
|
||||
const { PLUGIN_OPTION_MAP } = require('../constants')
|
||||
|
||||
module.exports = function instantiateOption (name) {
|
||||
switch (name) {
|
||||
case OPTION.ENHANCE_APP_FILES:
|
||||
case PLUGIN_OPTION_MAP.ENHANCE_APP_FILES.name:
|
||||
return new EnhanceAppFilesOption(name)
|
||||
|
||||
case OPTION.EXTEND_PAGE_DATA:
|
||||
case PLUGIN_OPTION_MAP.EXTEND_PAGE_DATA.name:
|
||||
return new ExtendPageDataOption(name)
|
||||
|
||||
case OPTION.CLIENT_DYNAMIC_MODULES:
|
||||
case PLUGIN_OPTION_MAP.CLIENT_DYNAMIC_MODULES.name:
|
||||
return new ClientDynamicModulesOption(name)
|
||||
|
||||
case OPTION.ADDITIONAL_PAGES:
|
||||
case PLUGIN_OPTION_MAP.ADDITIONAL_PAGES.name:
|
||||
return new AdditionalPagesOption(name)
|
||||
|
||||
case OPTION.GLOBAL_UI_COMPONENTS:
|
||||
case PLUGIN_OPTION_MAP.GLOBAL_UI_COMPONENTS.name:
|
||||
return new GlobalUIComponentsOption(name)
|
||||
|
||||
default: return new Tapable(name)
|
||||
default: return new Option(name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
const logger = require('../util/logger')
|
||||
const chalk = require('chalk')
|
||||
const { isDebug } = require('../util/logger')
|
||||
const logger = require('../util/logger')
|
||||
const { assertTypes } = require('../util/shared')
|
||||
|
||||
const SCOPE_PACKAGE_RE = /^@(.*)\/(.*)/
|
||||
let anonymousPluginIdx = 0
|
||||
|
||||
exports.resolveScopePackage = function (name) {
|
||||
if (SCOPE_PACKAGE_RE.test(name)) {
|
||||
@@ -13,44 +16,102 @@ exports.resolveScopePackage = function (name) {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve plugin config, name, and shortcut.
|
||||
* @param pluginRaw
|
||||
* @returns {{config: Function|Object, name: string, shortcut: string}}
|
||||
*/
|
||||
exports.resolvePlugin = function (pluginRaw) {
|
||||
let name
|
||||
let config
|
||||
let shortcut
|
||||
if (typeof pluginRaw === 'function' || typeof pluginRaw === 'object') {
|
||||
return pluginRaw
|
||||
}
|
||||
if (typeof pluginRaw === 'string') {
|
||||
config = pluginRaw
|
||||
name = shortcut = pluginRaw.name || `anonymous-${++anonymousPluginIdx}`
|
||||
} else if (typeof pluginRaw === 'string') {
|
||||
try {
|
||||
return require(pluginRaw.startsWith('vuepress-plugin-') ? pluginRaw : `vuepress-plugin-${pluginRaw}`)
|
||||
shortcut = pluginRaw.startsWith('vuepress-plugin-') ? pluginRaw.slice(16) : pluginRaw
|
||||
name = `vuepress-plugin-${shortcut}`
|
||||
config = require(name)
|
||||
} catch (err) {
|
||||
const pkg = exports.resolveScopePackage(pluginRaw)
|
||||
try {
|
||||
if (pkg) {
|
||||
if (pkg.org === 'vuepress') {
|
||||
return require(pkg.name.startsWith('plugin-') ? pluginRaw : `@vuepress/plugin-${pkg.name}`)
|
||||
shortcut = pkg.name.startsWith('plugin-') ? pkg.name.slice(7) : pkg.name
|
||||
name = `@vuepress/plugin-${shortcut}`
|
||||
} else {
|
||||
return require(pkg.name.startsWith('vuepress-plugin-') ? pluginRaw : `@${pkg.org}/vuepress-plugin-${pkg.name}`)
|
||||
shortcut = pkg.name.startsWith('vuepress-plugin-') ? pkg.name.slice(16) : pkg.name
|
||||
name = `@${pkg.org}/vuepress-plugin-${shortcut}`
|
||||
}
|
||||
shortcut = `@${pkg.org}/${shortcut}`
|
||||
config = require(name)
|
||||
} else {
|
||||
throw new Error(`[vuepress] Cannot resolve ${pluginRaw}.`)
|
||||
throw new Error(`[vuepress] Invalid plugin usage ${pluginRaw}.`)
|
||||
}
|
||||
} catch (err2) {
|
||||
console.error(chalk.red(logger.error(`\n[vuepress] Cannot resolve plugin: ${pluginRaw}\n`, false)))
|
||||
throw new Error(err2)
|
||||
if (isDebug) {
|
||||
console.error(err2)
|
||||
}
|
||||
name = shortcut = pluginRaw
|
||||
config = null
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.warn(`\n[vuepress] Invalid plugin usage: ${chalk.yellow(pluginRaw)}\n`)
|
||||
return { config, name, shortcut }
|
||||
}
|
||||
|
||||
exports.inferPluginName = function (pluginRaw, pluginConfig) {
|
||||
if (pluginConfig.name) {
|
||||
return pluginConfig.name
|
||||
/**
|
||||
* Hydrates your plugin config, options and context.
|
||||
* @param {Function | Object} config
|
||||
* @param {String} name
|
||||
* @param {String} hortcut
|
||||
* @param {Object} pluginOptions
|
||||
* @param {Object} pluginContext
|
||||
*/
|
||||
exports.hydratePlugin = function ({ config, name, shortcut }, pluginOptions, pluginContext) {
|
||||
const { valid, warnMsg } = assertTypes(pluginOptions, [Object, Boolean])
|
||||
if (!valid) {
|
||||
logger.warn(
|
||||
`[${chalk.gray(shortcut)}] ` +
|
||||
`Invalid value for "pluginOptions" ${chalk.cyan(name)}: ${warnMsg}`
|
||||
)
|
||||
pluginOptions = {}
|
||||
}
|
||||
if (typeof pluginRaw === 'string') {
|
||||
if (pluginRaw.startsWith('vuepress-plugin-')) {
|
||||
return pluginRaw.slice(16)
|
||||
}
|
||||
return pluginRaw
|
||||
let enabled = true
|
||||
if (typeof pluginOptions === 'boolean') {
|
||||
enabled = pluginOptions
|
||||
pluginOptions = {}
|
||||
}
|
||||
// ensure each plugin have a unique name.
|
||||
return Date.now().toString(16)
|
||||
if (typeof config === 'function') {
|
||||
// 'Object.create' here is to give each plugin a separate context,
|
||||
// but also own the inheritance context.
|
||||
config = config(pluginOptions, Object.create(pluginContext))
|
||||
}
|
||||
return Object.assign(config, { name, shortcut, enabled })
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize plugins config in `.vuepress/config.js`
|
||||
* @param pluginsConfig
|
||||
*/
|
||||
exports.normalizePluginsConfig = function (pluginsConfig) {
|
||||
const { valid, warnMsg } = assertTypes(pluginsConfig, [Object, Array])
|
||||
if (!valid) {
|
||||
logger.warn(
|
||||
`[${chalk.gray('config')}] ` +
|
||||
`Invalid value for "plugin" value ${chalk.cyan(name)}: ${warnMsg}`
|
||||
)
|
||||
pluginsConfig = []
|
||||
}
|
||||
if (Array.isArray(pluginsConfig)) {
|
||||
pluginsConfig = pluginsConfig.map(item => {
|
||||
return Array.isArray(item) ? item : [item]
|
||||
})
|
||||
} else if (typeof pluginsConfig === 'object') {
|
||||
pluginsConfig = Object.keys(pluginsConfig).map(item => {
|
||||
return [item, pluginsConfig[item]]
|
||||
})
|
||||
}
|
||||
return pluginsConfig
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ module.exports = async function prepare ({
|
||||
// 2. resolve plugin
|
||||
const plugin = resolvePlugin(options)
|
||||
options.plugin = plugin
|
||||
const pluginOptions = plugin.options
|
||||
|
||||
// 3. resolve siteData
|
||||
// SiteData must be resolved after the plugin initialization
|
||||
@@ -28,12 +29,12 @@ module.exports = async function prepare ({
|
||||
options.siteData = await resolveSiteData(options)
|
||||
Object.freeze(options)
|
||||
|
||||
await plugin.hooks.ready.run()
|
||||
await pluginOptions.ready.run()
|
||||
|
||||
// 4. apply plugin options to extend markdown.
|
||||
plugin.options.extendMarkdown.run(markdown)
|
||||
plugin.options.clientDynamicModules.run()
|
||||
plugin.options.globalUIComponents.run()
|
||||
pluginOptions.extendMarkdown.run(markdown)
|
||||
pluginOptions.clientDynamicModules.run()
|
||||
pluginOptions.globalUIComponents.run()
|
||||
|
||||
// 5. generate routes code
|
||||
const routesCode = await genRoutesFile(options)
|
||||
@@ -60,7 +61,7 @@ module.exports = async function prepare ({
|
||||
)
|
||||
}
|
||||
|
||||
await plugin.options.enhanceAppFiles.run()
|
||||
await pluginOptions.enhanceAppFiles.run()
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ module.exports = function (options) {
|
||||
|
||||
plugin
|
||||
// user plugin
|
||||
.useByConfigs(cliPlugins)
|
||||
.useByConfigs(siteConfig.plugins)
|
||||
.useByConfigs(themePlugins)
|
||||
.useByPluginsConfig(cliPlugins)
|
||||
.useByPluginsConfig(siteConfig.plugins)
|
||||
.useByPluginsConfig(themePlugins)
|
||||
// built-in plugins
|
||||
.use(enhanceAppPlugin)
|
||||
.use(registerGlobalComponentsPlugin, {
|
||||
|
||||
@@ -55,3 +55,4 @@ logger.debug = function (msg) {
|
||||
|
||||
module.exports = logger
|
||||
module.exports.getLoggerFn = getLoggerFn
|
||||
module.exports.isDebug = isDebug
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = (options, context) => ({
|
||||
// globalComponents: ['BackTIoTop'],
|
||||
|
||||
// Usually used to construct a global UI, e.g. back to top.
|
||||
globalUIComponents: ['BackToTop'],
|
||||
// globalUIComponents: ['BackToTop'],
|
||||
|
||||
clientRootMixin: path.resolve(__dirname, 'mixin.js'),
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
const path = require('path')
|
||||
const themePlugin = require('./plugin')
|
||||
const activeHeaderLinksPlugin = require('@vuepress/plugin-active-header-links')
|
||||
const googleAnalyticsPlugin = require('@vuepress/plugin-google-analytics')
|
||||
|
||||
// Theme API.
|
||||
module.exports = {
|
||||
@@ -9,7 +7,7 @@ module.exports = {
|
||||
notFound: path.resolve(__dirname, 'src/NotFound.vue'),
|
||||
plugins: [
|
||||
themePlugin,
|
||||
activeHeaderLinksPlugin,
|
||||
googleAnalyticsPlugin
|
||||
'@vuepress/active-header-links',
|
||||
'@vuepress/google-analytics'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ module.exports = {
|
||||
['meta', { name: 'msapplication-TileColor', content: '#000000' }]
|
||||
],
|
||||
serviceWorker: true,
|
||||
plugins: ['back-to-top'],
|
||||
plugins: ['@vuepress/back-to-top'],
|
||||
themeConfig: {
|
||||
repo: 'vuejs/vuepress',
|
||||
editLinks: true,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
require('@vuepress/cli').bootstrap({
|
||||
theme: 'default',
|
||||
plugins: [
|
||||
'test',
|
||||
'i18n-ui'
|
||||
'@vuepress/test',
|
||||
'@vuepress/i18n-ui'
|
||||
]
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user