Migration to Vue 3
In 2023, Storyblok launched a new SDK for developing field plugins. While older field plugins only worked with Vue 2.5, the new SDK lets you develop with any frontend framework, including Vue 3 and React. The new SDK also has newer features, in-depth documentation, TypeScript support, and is actively maintained. This tutorial guides you through the process of upgrading a legacy field plugin from Vue 2.5 to Vue 3.
Prerequisites
Understanding the legacy field plugin
The simplistic version of legacy field plugin may look like this:
const Fieldtype = {
mixins: [window.Storyblok.plugin],
template: `<div><input v-model="model.example" /></div>`,
methods: {
initWith() {
return {
plugin: 'test-field-plugin',
example: 'Hello World!'
}
},
pluginCreated() {
console.log('plugin:created')
}
},
watch: {
'model': {
handler: function (value) {
this.$emit('changed-model', value);
},
deep: true
}
}
}
Whenever model
changes, it emits a changed-model
event to its parent. There is a hidden parent component that is automatically injected by Storyblok. It receives this event and forwards it to the Visual Editor via postMessage
. All communication occurred using $emit('some-event')
.
Understanding the new field plugin
We no longer recommend creating a field plugin directly in the field plugin editor. Instead, we provide a new CLI that helps you create one. To do so, run the following command and select the Vue 3 template.
npx @storyblok/field-plugin-cli@latest create --template vue3
In the newly created project folder, open the src/components/FieldPluginExample/Counter.vue
file.
const {
data,
actions: { setContent },
} = useFieldPlugin()
// ...
setContent(value)
Most importantly, this.$emit('changed-model', value)
is now plugin.actions.setContent(value)
.
The Vue 2 component above will become like the following:
<!-- src/components/Fieldtype.vue -->
<script setup>
import { useFieldPlugin } from '@storyblok/field-plugin/vue3'
const plugin = useFieldPlugin()
</script>
<template>
<div>
<input
:value="plugin.data.content"
@input="plugin.actions.setContent($event.target.value)"
/>
</div>
</template>
Getting data
In the legacy version, the mixin injected all the data into this
of the root component.
const Fieldtype = {
mixins: [window.Storyblok.plugin],
template: `<div><input v-model="model.example" /></div>`,
methods: {
initWith() {
return {
plugin: 'eltest-0707',
example: 'Hello World!'
}
},
pluginCreated() {
console.log('plugin:created')
}
},
watch: {
'model': {
handler: function (value) {
console.log('💡 data', {
blockId: this.blockId,
contentmodel: this.contentmodel,
model: this.model,
schema: this.schema,
spaceId: this.spaceId,
storyId: this.storyId,
storyItem: this.storyItem,
token: this.token,
uid: this.uid,
userId: this.userId,
});
this.$emit('changed-model', value);
},
deep: true
}
}
}
However, with the useFieldPlugin
, it's much simpler:
const plugin = useFieldPlugin();console.log(plugin.data);
Actions
Due the introduction of the useFieldPlugin
composable, you run actions differently.
Set Content
// before
this.$emit('changed-model', value);
// after
const plugin = useFieldPlugin();
plugin.actions.setContent(value);
Toggle Modal
// before
this.$emit('toggle-modal', booleanValue);
// after
const plugin = useFieldPlugin();
plugin.actions.setModalOpen(booleanValue);
Getting Current Context
// before
this.$emit('get-context');
this.$onGetContext(() => {
console.log(this.storyItem);
})
// after
const plugin = useFieldPlugin();
plugin.actions.requestContext(); // this updates `plugin.data.story` asynchronously, which triggers a re-render.
Asset Selector
// before
// You had to include this proxy component:
<sb-asset-selector :uid="uid" field="your_model_attribute"></sb-asset-selector>
// after
const plugin = useFieldPlugin();
const asset = await plugin.actions.selectAsset();
console.log(asset.filename);
Summary of changes
- Added
content
: In the legacy version, you had to send an object containingplugin
property (e.g.{ plugin: "my-plugin", some: "value" }
). Unlike before, nowcontent
can be anything: string, boolean, number, object, array, etc. You can use anything as long as it can be serialized. Pass content from a field plugin to Storyblok's Visual Editor viapostMessage
. To learn more about what you can send, read the structured code algorithm. - Removed
initWith
:initWith
was a method called by the mixinwindow.Storyblok.plugin
. Thanks to theuseFieldPlugin
composable, it is no longer required. - An empty string is given to your field plugin at
plugin.data.content
by default. - Removed
pluginCreated
: You can use the regular lifecycle callbacks such asonMounted()
. - Removed default style: A default css file used to be injected (https://plugins.storyblok.com/assets/css/index-latest.css). With the new version, you have full control over the styling.
- Removed
api
: Thestoryblok-js-client
is no longer included by default. You can include it by runningnpm install storyblok-js-client
. - Removed
$sb
API Reference
To learn more about all the APIs, you can read the API Reference.