Vue Global Config 
Configure your Vue 2.6 / 2.7 / 3 components globally!
app.use(YourComponent, {
'message': 'Hello World',
'placeholder': 'Please enter',
'@blur': function (e) {
console.log(e)
console.log(this)
},
'@vnodeMounted': function () {
console.log(this)
},
})
Vue.use(YourComponent, {
'message': 'Hello World',
'placeholder': 'Please enter',
'@blur': function (e) {
console.log(e)
console.log(this)
},
'@hook:mounted': function () {
console.log(this)
},
})
Why?
Vue provides support for globally registering components, but no configuration.
In the industry, ElementPlus
thoughtfully provides config-provider .
But note that it's only for partial props. Global attrs, global listeners & global hooks are all not supported.
Make your components capable of globally configuring, is a not-that-hard but tiresome drudgery.
Entangled in global / local / default parameters, which one to choose? It should be replaced or shallow merged or deep merged when it comes to plain object type? What if I want all functions triggered instead of ony one when it comes to function type? How to decide when both camel case and kebab case of a same parameter come together?
How?
- Firstly provide an entrance for your component to register globally, this is the foundation.
- Use useGlobalConfig to handle parameters passed by component user, get global props, global attrs, global listeners & global hooks.
- Import those global parameters, meet them with local / default parameters and determine the final value using conclude .
Features
- Support Vue 2.6 / 2.7 / 3
- Provide weight algorithm to deal with trade-off and merging issues of global / local / default parameters.
- Support global props
- Support global attrs
- Support global listeners
- Support triggering both global listener & local listener.
- Support triggering either global listener or local listener.
- Support global hooks (internal API)
- Support deep merge, shallow merge or directly replace plain object type values
- Support merge or directly replace function type values
Vue 3
Install

Global Props
<template>
{{ Msg }}
</template>
<script setup>
import { computed } from 'vue'
import { conclude } from 'vue-global-config'
import { globalProps } from './index' // Entrance for registering globally
const props = defineProps(['msg'])
const Msg = computed(() => conclude([props.msg, globalProps.msg])) // Place the prop of higher priority in the front
</script>
Global Attrs & Listeners
In Vue 3, attrs
includes both attrs & listeners
<template>
<el-input v-bind="Attrs" />
</template>
<script setup>
import { computed, getCurrentInstance, useAttrs } from 'vue'
import { conclude } from 'vue-global-config'
import { globalAttrs, globalListeners } from './index' // Entrance for registering globally
const currentInstance = getCurrentInstance()
// Not required: Bind 'this' to globalListeners, if you need it in the global configuration
for (const k in globalListeners)
globalListeners[k] = globalListeners[k].bind(currentInstance)
const Attrs = computed(() => conclude([useAttrs()], {
default: { ...globalAttrs, ...globalListeners },
// mergeFunction's role is to trigger both global and local listener
// do not use it if you want global listener replaced by local one
mergeFunction: (localEventListener, globalEventListener) => (...args) => {
localEventListener(...args)
globalEventListener(...args)
},
}))
</script>
Global Hooks
<template>
<div v-bind="globalHooks" />
</template>
<script setup>
import { getCurrentInstance } from 'vue'
import { globalHooks } from './index' // Entrance for registering globally
const currentInstance = getCurrentInstance()
// Not required: Bind 'this' to globalHooks, if you need it in the global configuration
for (const k in globalHooks)
globalHooks[k] = globalHooks[k].bind(currentInstance)
</script>
Vue 2
Install
$ npm add vue-global-config
$ npm add vue-global-config @vue/composition-api
Global Props
<template>
{{ Msg }}
</template>
<script>
import { conclude } from 'vue-global-config'
import { globalProps } from './index' // Entrance for registering globally
export default {
props: ['msg'],
computed: {
Msg() {
return conclude([this.msg, globalProps.msg]) // Place the prop of higher priority in the front
},
}
}
</script>
Global Attrs
<template>
<el-input v-bind="Attrs" />
</template>
<script>
import { conclude } from 'vue-global-config'
import { globalAttrs } from './index' // Entrance for registering globally
export default {
computed: {
Attrs() {
return conclude([this.$attrs, globalAttrs]) // Place the prop of higher priority in the front
},
}
}
</script>
Global Listeners
<template>
<el-input v-on="Listeners" />
</template>
<script>
import { conclude, getLocalListeners } from 'vue-global-config'
import { globalListeners } from './index' // Entrance for registering globally
export default {
computed: {
Listeners() {
// Not required: Bind 'this' to globalListeners, if you need it in the global configuration
for (const k in globalListeners)
globalListeners[k] = globalListeners[k].bind(this)
// getLocalListeners's role is to remove hooks in this.$listeners
// Check the getLocalListeners chapter for details
return conclude([getLocalListeners(this.$listeners)], {
default: globalListeners,
// mergeFunction's role is to trigger both global and local listener
// do not use it if you want global listener replaced by local one
mergeFunction: (localEventListener, globalEventListener) => (...args) => {
localEventListener(...args)
globalEventListener(...args)
},
})
},
}
}
</script>
Global Hooks
<template>
<div />
</template>
<script>
import { listenGlobalHooks } from 'vue-global-config'
import { globalHooks } from './index' // Entrance for registering globally
export default {
created() {
// listen global hooks
listenGlobalHooks.call(this, globalHooks)
},
}
</script>
API
useGlobalConfig
Get global props, attrs, listeners & hooks by analysing global parameters passed by component user.
Param
import { useGlobalConfig } from 'vue-global-config'
useGlobalConfig({
'msg': 'some prop',
'placeholder': 'some attr',
'@blur': function () {},
'@hook:mounted': function () {},
})
conclude
Vue
provides local and default configuration of prop, but when wrapping components, there is a strong need for a "global configuration", which may otherwise lead to duplicate configurations for each component instance.
For example, the size and zIndex of ElementUI
support global configuration.
When there are more configurations, there is some judgment as to which configuration is used by the final component due to the different priorities.
When it comes to objects and functions, this can get quite complicated.
The role of conclude
is to help you figure out the final configuration.
Features
- Like Vue's props, it provides requirement, data type and custom validator
- For plain object type prop, deep merge, shallow merge and direct override are supported
- For function type prop, support merge and direct override
- Support for unifying the keys of objects into camel names
- Support for dynamic generation of default values
Param
import { conclude } from 'vue-global-config'
conclude([1, 2, undefined])
How can we know whether a prop is passed or not?
Whether the prop is all equal to undefined
or not
config.type
Same as Vue's prop validation.
config.mergeObject
'deep'
: Deep merge, where the object key of a high-weight prop overwrites the same-name key of a low-weight prop, containing nested objects (the default value)'shallow'
: Shallow merge, where the object key of a high weight prop overwrites the key of the same name of a low weight prop, without nested objectsfalse
: No merging, direct overwriting, objects of high weight prop will directly overwrite objects of low weight prop, consistent with the behavior of value types
config.mergeObjectApplyOnlyToDefault
Off by default, only valid when mergeObject is on.
When on, mergeObject's rules are only applied to the final comparison with default, and previous objects are still directly overwritten.
When off, mergeObject's rules are applied to all object type prop weights.
Usage scenario: The component author wants to merge the component's internal configuration with the component user's configuration, but the component user's own configuration at all levels remains directly overwritten by the rules.
config.mergeFunction
Usage Scenario: When wrapping a component, you may need to listen to certain events of the underlying dependency by configuration.
When exposing the configuration for that dependency, the component user's configuration will conflict with yours.
mergeFunction
provides a customized way to merge function type prop.
For example, the well-known rich text library TinyMCE has a callback called init_instance_callback
in its options.
When wrapping this library, you can use it to do some initialization work and expose TinyMCE's options in order not to break the flexibility of the component.
The problem is that once the component user has configured this callback, it will conflict with your configuration.
Unlike the configuration of other data types, function-type prop is not expected to be directly overridden by the user's configuration, and there is a need for "fusion".
Fusion: executes both functions configured by the component user and functions configured internally by the component.
The function type prop includes two cases:
- prop is itself a function
- prop is an object with function properties
conclude
internally uses Array.prototype.reduce
to perform the function fusion, mergeFunction
will be used as argument 1.
conclude([
() => {
console.log('I am option 1')
},
() => {
console.log('I am option 2')
}
], {
default: () => {
console.log('I am default option')
},
mergeFunction: (accumulator, item) => (...args) => {
accumulator(...args)
item?.(...args)
},
mergeFunctionApplyOnlyToDefault: false,
})()
config.mergeFunctionApplyOnlyToDefault
Is turned on by default and is only available when mergeFunction
is on.
Function merging is, after all, a special behavior, and often only the component author will use this feature.
For component users, the configuration of function types may be more like Primitive Types and just override them.
When turned on, the mergeFunction
rule is only applied to the final comparison with default, and the previous function is still overwritten directly.
When off, mergeFunction
's rules are applied to all function type prop weights.
config.default
Explicitly specifies the default value. If mergeObjectApplyOnlyToDefault
or mergeFunctionApplyOnlyToDefault
is not enabled, there is no need to use this parameter, just put the default value at the end of configSequence
.
config.camelCase
without
const Props = conclude([...], {
default: (userProp) => {
if(userProp.beforeClose !== undefined || userProp['before-close'] !== undefined) {
...
}
},
defaultIsDynamic: true,
})
if(Props.beforeClose !== undefined || Props['before-close'] !== undefined) {
...
}
with
const Props = conclude([...], {
default: (userProp) => {
if(userProp.beforeClose !== undefined) {
...
}
},
defaultIsDynamic: true,
})
if(Props.beforeClose !== undefined) {
...
}
Take a look at this: <el-select value-key="id" valueKey="code" />
, so is id
or code
going to take effect?
The answer is code
because valueKey
is latter.
conclude([{ aB: 1, 'a-b': 2 }])
returns { aB: 2 }
in the same way.
With the global config, the situation can be quite complicated.
What does conclude([{ 'aB': 1, 'a-b': 2 }, { 'a-b': 4, 'aB': 3 }])
return?
{ aB: 1 }
if conclude
merge object before unify the keys, because the merge process will change the order of keys.
It will be unpredictable with more props.
So conclude
choose to unify the keys in advance, so { aB: 2 }
will be the answer, it's intuitive.
Why not take kebab-case as default?
Dynamic default value
Usage scenario: the default value needs to be determined according to the parameters passed by the component user.
conclude([{
a: {
a: 1
}
}, {
a: {
a: 2,
b: 1
}
}], {
default: userProp => ({
a: {
c: userProp.a.a === 1 ? 1 : null
}
}),
defaultIsDynamic: true,
})
getLocalListeners
Vue 2 only. For the purpose of listening local hooks.
In Vue 2, this.$listeners
includes listeners & hooks.
In Vue 3, attrs
includes attrs and listeners.
As a result in Vue 2, hooks passed locally will be improperly passed to the child component within your component, making hook triggered repeatedly.
By using getLocalListeners(this.$listeners)
, your can get pure listeners without any hook.
listenGlobalHooks
Vue 2 only. For the purpose of listening global hooks.
In Vue 2, only Vue components can trigger a hook, HTML elements don't. So triggering both global & local hooks needs hijacking emit
.
Changelog
Detailed changes for each release are documented in the release notes.
Develop
-
Install Deno
-
npm add pnpm @cloydlau/scripts -g; pnpm i
-
Start
pnpm dev3
pnpm dev2.7
pnpm dev2.6