Vue.js
Vue.js is being evaluated as an official library for building UI within the MediaWiki ecosystem. It's currently experimental (based on v2.x) and not ready for wide use as there are a number of unknowns but more importantly, no well thought out conventions yet. This page documents best practices and other knowledge that surfaces during evaluation. In general, this documentation should be minimal because it defines only MediaWiki-specific pieces, and largely refers to the official documentation.
Shared components library Wikimedia Vue UI
As part of the Vue.js Search Widget Case Study and in course of interest by a number of teams in prototyping Vue.js based projects with goal of later in-production use, we've setup shared components library Wikimedia Vue UI (WVUI).
Set up
Two ways to parse Vue.js templates
Pre-built (bandwidth and client performant at the expense of extra initial set up)
For professional developers building high performance experiences, pre-compilation and minificiation is recommended. However, this requires extra per-repo configuration changes which are currently undocumented. This extra configuration will be eliminated or formalized and minimized if the deploy build step RFC succeeds.
On the fly compilation (less performant but easy to develop)
ResourceLoader has built-in support for parsing single-file components. This lets you write Vue.js with very little configuration in the context of a MediaWiki installation which may be valuable in some contexts as well as for developers uncomfortable with compilers. Please note the following limitations:
- Scoped styles are unsupported.
- What you write is what you ship--No ES6, TypeScript, or language compilation support. At time of writing, this means ES5 only.
- Vue.js directive shorthands are unsupported
@
forv-on
and:
forv-bind
. - The version of Less used must match MediaWiki's version (very old). However, everything that works in normal MediaWiki styles works here (e.g., CSSJanus, relative URLs, imports, etc).
How to use on the fly compilation
You can add a .vue file to any package module as if it were a script file:
{
"ResourceModules": {
"ext.myExtension.myModule": {
"packageFiles": [
"myModule/init.js",
"myModule/MyComponent.vue"
],
"messages": [
"myextension-mycomponent-header"
],
"dependencies": [
"vue"
]
}
}
}
The .vue file must contain a template and JavaScript code that exports a component registration object. It can also (optionally) contain styles:
<template>
<div class="mw-my-component">
<h2>{{ $i18n( 'myextension-mycomponent-header' ) }}</h2>
<p><input type="number" v-model="a"> + <input type="number" v-model="b"> = {{ sum }}</p>
</div>
</template>
<script>
module.exports = {
data: function () {
return {
a: 23,
b: 19
};
},
computed: {
sum: function () {
return Number( this.a ) + Number( this.b );
}
}
};
</script>
<style lang="less">
.mw-my-component {
input {
width: 3em;
}
}
</style>
You can then require()
the .vue file just like any other script file. For example, you can use it as a root component as follows:
var Vue = require( 'vue' ),
MyComponent = require( './MyComponent.vue' );
// eslint-disable-next-line no-new
new Vue( {
el: '#my-component-placeholder',
render: function ( h ) {
return h( MyComponent );
}
} );
Or you can use it as a nested component inside another component:
<template>
<div class="parent-component">
<h2>{{ $i18n( 'myextension-parent-component-header' ) }}</h2>
<my-component />
</div>
</template>
<script>
module.exports = {
components: {
'my-component': require( './MyComponent.vue' )
}
};
</script>
Some modules have an init script as their main file (usually called init.js
) that creates a new Vue instance to take over a DOM element on the page and replace it with a Vue component. Other modules just export one or more reusable components; their main file is either a .vue
file with the component that's exported, or a script file that exports several components, e.g.:
module.exports = {
ComponentOne: require( './ComponentOne.vue' ),
ComponentTwo: require( './ComponentTwo.vue' )
// etc.
};
The vue
module in ResourceLoader
MediaWiki core comes with a copy of Vue (v2.x) and some small MediaWiki-specific plugins for Vue. These live in the 'vue'
module. In code that sets up a new Vue instance, (i.e. calls new Vue( ... )
), you should make the 'vue'
module a dependency of your module, then obtain the Vue object using var Vue = require( 'vue' );
. You do not have to call Vue.use()
to register the MediaWiki-specific plugins; this is done for you.
The i18n plugin
The 'vue'
module comes with a small i18n plugin that wraps MediaWiki's i18n system (mw.message
), so you can use MediaWiki i18n messages in your templates. This plugin creates an $i18n()
function that you can use inside templates, which is an alias for mw.message()
. For plain text messages (most cases), you can simply use:
<p>{{ $i18n( 'message-key' ) }}</p>
You can pass parameters either variadically, or as an array:
<p>{{ $i18n( 'message-key', param1, param2 ) }}</p>
<!-- or: -->
<p>{{ $i18n( 'message-key' ).params( [ param1, param2 ] ) }}</p>
The $i18n()
function returns a Message object, so you can use it in all the same ways that you can use mw.message()
in "normal" JS code. Remember that all message keys you use have to be added to the "messages"
array in the ResourceLoader module definition.
Parsed messages
One important limitation of $i18n()
is that you can't use it for parsed messages that return HTML. This is because template interpolations ({{variable}}
syntax) can only return plain text, and are always HTML-escaped:
<!-- This DOES NOT WORK: -->
<p>{{ $i18n( 'category-empty' ).parse() }}</p>
<!-- This renders as: -->
<p><em>This category currently contains no pages or media.</em></p>
The code above displays <em>This category currently contains no pages or media.</em>
, as plain text. To get Vue to accept raw HTML, you have to use the v-html
directive. Because writing <p v-html="$i18n('category-empty').parse()">
is verbose and annoying, the i18n plugin provides a shortcut using the v-i18n-html
directive, which you'll want to use instead unless you need to pass message parameters or reference the message name from a variable.
<p v-i18n-html:category-empty />
<!-- or: -->
<p v-html="$i18n('category-empty', 'param1', 'param2').parse()" />
<!-- Both of these render as: -->
<p><em>This category currently contains no pages or media.</em></p>
See the documentation comments in resources/src/vue/i18n.js
for a more detailed explanation.
Parsed messages with parameters
The v-i18n-html
directive does not currently support passing parameters to the message. Support for this may be added in the future. The recommended way to pass a parameter to a parsed message (or to do other complicated things) is to put the parsed message in a computed property. It's possible to do these kinds of things inline, but using a computed property is recommended for readability.
<template>
<p v-html="namespaceProtectedMessage" />
</template>
<script>
module.exports = {
computed: {
namespaceProtectedMessage: function () {
return mw.message( 'namespace-protected' )
.params( [ namespaceName ] )
.parse();
}
}
};
</script>
Examples
Skin
Extension
Skin + Extension sharing a Vue instance
Gadget
User script
External resources
Most important:
Additional
- Vue.js News (weekly newsletter)
Training vendors
- Vue School
- Frontend Masters
- Vue Mastery (includes some free courses too)
Other Vue.js orgs
GitLab
Testing
You should write tests! The current recommendation is to use Jest.
More information about how to test Vue components in a MediaWiki environment can be found in this guide.
Projects using Vue
Please list any MediaWiki projects that are utilizing Vue during the testing phase here.
- New Searchbar for Desktop Improvements
- Extension:MachineVision
- Content Translation's new dashboard and section translation
See also
- Wikimedia Vue UI
- wikibase-termbox source (WMDE production Vue.js usage)
- Wikibase Vue.js components repo
- Content Translation dashboard - This approach uses webpack based build and offers hot reloading and close integration with Vue dev tools
- ResourceLoader support
- MediaWiki Vue.js Sandbox (test extension)
- MachineVision extension (experiments)