Migration from Jovo v3
We just released our biggest update so far, Jovo v4! Learn how to migrate your Jovo v3
projects to v4
, or check out our getting started guide.
Introduction
With Jovo v4
, we've completely redesigned how Jovo apps are built. Over the last year, we translated everything we learned from 4 years Jovo into a completely new framework.
In this migration guide, you will learn how to upgrade your Jovo v3
apps to v4
. We recommend creating a fresh Jovo v4
project (by following our getting started guide) and then moving over the pieces step by step.
The TypeScript vs JavaScript section shows the differences of using Jovo in the two programming languages.
The new concepts section introduces new important elements of building a Jovo app, especially components, handlers, and output.
The configuration section highlights changes to how the app, project (CLI), Debugger, and models are configured.
The updated concepts section includes Jovo features that are similar to v3
, but received an upgrade, for example naming conventions, unit testing and entities.
The use v4
and v3
in parallel section describes how you can build some projects in v4
(using the latest CLI), while still being able to maintain older projects.
The integrations and plugins section shows a few more examples of smaller changes to existing Jovo extensions. Take a look at the Jovo Marketplace to find all up to date integrations.
TypeScript vs JavaScript
Jovo v4
comes with major improvements to its underlying TypeScript architecture, using modern features that enable us to enhance the development experience with features like decorators and type inference.
Although we recommend using v4
with TypeScript, you are still able to use it with JavaScript. You can find the difference in the TS and JS templates.
One of the main improvements in Jovo v4
is the possibilities to use decorators as part of handlers. This allows you to have handlers respond to more than just an intent name, making the development more powerful and structured:
// v3 ShowMenuIntent() { // ... } // v4 @Handle({ intents: ['ShowMenuIntent', 'YesIntent'] platforms: ['alexa'], }) showMenu() { // ... }
While decorators are a feature that is not supported by JavaScript yet, it is possible using them in Jovo v4
JS projects. We use Babel to transpile the code into a correct JS format.
New Concepts
There are quite a few concepts that make Jovo v4
more powerful, including:
- Components: Reusable elements that are similar to states in Jovo
v3
- Handlers: Methods of a component, similar to intent handlers in
v3
- Output: What you return to the user, similar to
tell
/ask
inv3
- Input: An object that includes structured meaning derived from the user request
Components vs States
A Jovo component is an isolated part of your app that handles a specific task. Here is an example component with a START
handler (the entry point when another component redirects or delegates to it):
// src/components/YourComponent.ts import { Component, BaseComponent } from '@jovotech/framework'; @Component() class YourComponent extends BaseComponent { START() { // ... } }
Compared to Jovo v3
, a component can be seen as a state, but instead of using followUpState
to determine the state of the next request, we use redirects or delegates to enter a component in the current request:
// v3 { LAUNCH() { // Ask for a yes/no question and route to order state this.$speech.addText('Do you want to order something?'); this.$reprompt.addText('Please answer with yes or no.'); this.followUpState('OrderState') .ask(this.$speech, this.$reprompt); }, OrderState: { YesIntent() { // ... }, NoIntent() { // ,,, }, }, }, // v4 // GlobalComponent.ts LAUNCH() { return this.$redirect(OrderComponent); }, // OrderComponent.ts START() { return this.$send({ message: 'Do you want to order something?', reprompt: 'Please answer with yes or no.', }); }, YesIntent() { // ... } NoIntent() { // ... }
As long as the component is at the top of the $state
stack, the router will try to match one of the component's handlers to the next request.
These are all the component routing features:
// v3 this.toIntent('SomeIntent'); this.toStateIntent('SomeState', 'SomeIntent'); this.toStatelessIntent('SomeIntent');
// v4 this.someHandler(); // Call a different method inside the same component this.$redirect(SomeComponent); // Go to START in SomeComponent this.$redirect(SomeComponent, 'someHandler');
Ideally, using the concept of global handlers, you don't need to rely too much on redirects to handlers in different components.
Instead of nesting all states in the app.ts
as with Jovo v3
, you can register all components there, leading to a more clearly structured app:
const app = new App({ components: [ GlobalComponent, OrderComponent, // ... ], // ... });
Handlers vs Intents
As shown in the components section above, Jovo v4
still uses the concept of handlers. However, they are way more powerful now and can respond to more than just intents.
In v3
, all handlers were methods in an object of nested states. In v4
, they are methods in component classes:
// src/components/YourComponent.ts import { Component, BaseComponent } from '@jovotech/framework'; @Component() class YourComponent extends BaseComponent { handlerA() { // ... } handlerB() { // ... } handlerC() { // ... } }
Similar to Jovo v3
, it is possible to name a handler exactly like the incoming intent it is supposed to respond to:
ShowMenuIntent() { // ... }
This does not offer a lot of flexibility, though. For better control, we recommend using the @Handle
decorator. The @Handle
decorator contains a set of elements that define when a handler should be triggered.. This way, you can even add multiple intents and name the handler however you like.
For example, this handler responds the ShowMenuIntent
and YesIntent
:
import { Handle } from '@jovotech/framework'; // ... @Handle({ intents: ['ShowMenuIntent', 'YesIntent'] }) showMenu() { // ... }
It's also possible to use the @Intents
convenience decorator:
import { Intents } from '@jovotech/framework'; // ... @Intents(['ShowMenuIntent', 'YesIntent']) showMenu() { // ... }
Built-In Handlers
We removed NEW_SESSION
, NEW_USER
, and ON_REQUEST
because they are usually used for data operations where hooks are more fitting.
Here is an example for a NEW_SESSION
type hook:
// src/app.ts import { App, Jovo } from '@jovotech/framework'; // ... const app = new App({ /* app config */ }); app.hook('before.dialogue.start', (jovo: Jovo): void => { if (jovo.$session.isNew) { // ... } }); // Same hook without types app.hook('before.dialogue.start', (jovo) => { if (jovo.$session.isNew) { // ... } });
If you want to use different handlers for different types of users (for example previously using NEW_USER
), you can also use the if
property of the @Handle
decorator:
// src/components/GlobalComponent.ts import { Jovo, Component, BaseComponent, Global, Handle } from '@jovotech/framework'; // ... @Global() @Component() export class GlobalComponent extends BaseComponent { LAUNCH() { return this.$send('Welcome back!'); } @Handle({ types: ['LAUNCH'], if: (jovo: Jovo) => jovo.$user.isNew }) welcomeNewUser() { return this.$send('Welcome, new user!'); } }
Output
One of the most important changes in Jovo v4
is the way output is returned to the user. While v3
relied on lots of helper methods (like tell
, ask
, showQuickReplies
, ...), v4
uses output templates that can be either returned directly using the $send()
method as well as using output classes:
// v3 return this.tell('Hello world!'); // v4 return this.$send('Hello world!'); // Pass a string or an output template return this.$send(HelloWorldOutput); // Pass an output class return this.$send(YesNoOutput, { message: 'Do you like pizza?' }); // Pass an output class and override some elements
This makes it easier to have a clearer separation of logic and output. And the output classes come with a few helpful features that make it possible to abstract common responses.
We also removed the Jovo SpeechBuilder. However, it's now possible to send multiple messages by using $send()
multiple times:
// v3 this.$speech.addText('Hello world!'); // ... this.$speech.addText('Do you like pizza?'); return this.tell(this.$speech); // v4 this.$send('Hello world!'); // ... return this.$send('Do you like pizza?');
Input
Jovo uses a new concept of structured input as part of the RIDR Lifecycle: The interpretation step turns all request data into structured meaning, which is then used by the routing and other services like the Jovo Debugger or unit testing.
For platforms like Alexa (that already send NLU data with the request), the $input
may look like this:
{ type: 'INTENT', intent: 'MyNameIsIntent', }
Some other integrations (like our Web Client) might only send raw text:
{ type: 'TEXT', text: 'My name is Max', }
This text gets turned into structured meaning by using an NLU integration.
{ type: 'TEXT', text: 'My name is Max', nlu: { intent: 'MyNameIsIntent', entities: { name: { value: 'Max', }, }, }, }
Configuration
One of the big improvements of Jovo v4
is a more consistent configuration across all elements:
- App configuration: Add platforms and plugins to your Jovo app
- Project configuration: Configure how the Jovo CLI builds and deploys your project
- Debugger configuration: Customize the Jovo Debugger frontend
- Models: Define the language model schema
App Configuration
In v3
, the app configuration could be found in a config.js
file. In v4
, we've moved that over to app.ts
.
There are specific configuration files for each stage (learn more in the staging docs):
app.ts
: Default configurations for all stagesapp.dev.ts
: Configurations for local development, including Jovo Debugger and FileDb
This is what it looks like in app.ts
:
import { App } from '@jovotech/framework'; // ... const app = new App({ components: [ // ... ], plugins: [ // ... ], logging: { // ... }, routing: { // ... }, });
Platforms and other integrations are now added to the plugins
array of the config. Each plugin can have its own configuration options, which are added to the constructor:
// v3 app.use(new Alexa()); // v4 const app = new App({ plugins: [ new AlexaPlatform({ // Alexa Config }), ], });
Project Configuration
The project configuration (previously project.js
) can now be found in a file called jovo.project.js
in the root of your Jovo project.
Similar to the app configuration, the project config now accepts classes in a plugins
array. Here is an example for Alexa:
const { ProjectConfig } = require('@jovotech/cli-core'); const { AlexaCli } = require('@jovotech/platform-alexa'); // ... const project = new ProjectConfig({ // ... plugins: [ new AlexaCli({ locales: { /* ... */ }, skillId: '<yourSkillId>', askProfile: 'default', files: { /* ... */ }, }), // ... ], });
Debugger Configuration
The new Jovo Debugger comes with lots of new features, including a new configuration file which can be found in jovo.debugger.js
in the root of your Jovo project.
In this file, you can add buttons that you can use in the Debugger frontend. It's possible to use buttons for both individual requests as well as sequences.
const { DebuggerConfig } = require('@jovotech/plugin-debugger'); // ... const debugger = new DebuggerConfig({ locales: [ 'en' ], buttons: [ { label: 'LAUNCH', input: { type: 'LAUNCH' } }, { label: 'yes', input: { intent: 'YesIntent' } }, // ... ] });
Models
The Jovo Model schema now comes with an improved structure, including the following changes:
inputs
are now calledentities
inputTypes
are now calledentityTypes
intents
,entities
, andentityTypes
are now maps instead of arrays
// v3 { "invocation": "my test app", "intents": [ { "name": "YesIntent", "phrases": [ "yes", "yes please", "sure" ] }, { "name": "NoIntent", "phrases": [ "no", "no thanks" ] } ] } // v4 { "invocation": "my test app", "intents": { "YesIntent": { "phrases": [ "yes", "yes please", "sure" ] }, "NoIntent": { "phrases": [ "no", "no thanks" ] } } }
Updated Concepts
The following features have been getting an upgrade as well:
Naming Conventions
In v4
, we're using the following naming conventions:
- Every user-facing first-level method or property is prepended by a
$
sign, for examplethis.$send()
orthis.$user
- Properties or methods of a first-level property don't include a
$
anymore, for examplethis.$user.data
- The exception is if a platform extends a Jovo property, then that property uses a
$
as well, for examplethis.$alexa.$user
, butthis.$alexa.$user.data
Unit Testing
The Jovo Test Suite has received a major update and is more consistent with the new concepts of input and output.
// v3 const { App } = require('jovo-framework'); const { GoogleAssistant } = require('jovo-platform-googleassistant'); const { Alexa } = require('jovo-platform-alexa'); for (const p of [new Alexa(), new GoogleAssistant()]) { const testSuite = p.makeTestSuite(); test('should ask the user if they like pizza', async () => { const conversation = testSuite.conversation(); const launchRequest = await testSuite.requestBuilder.launch(); const responseLaunchRequest = await conversation.send(launchRequest); expect(responseLaunchRequest.isAsk('Do you like pizza?')).toBe(true); }); } // v4 import { TestSuite, InputType } from '@jovotech/framework'; const testSuite = new TestSuite(); test('should ask the user if they like pizza', async () => { const { output } = await testSuite.run({ type: InputType.Launch, // or 'LAUNCH' }); expect(output).toEqual([ { message: 'Do you like pizza?', quickReplies: ['yes', 'no'], listen: true, }, ]); });
Entities vs Inputs
The $inputs
object was renamed to $entities
and some properties have changed:
// v3 this.$inputs.name.value; // raw value this.$inputs.name.key; // resolved value for synonyms this.$inputs.name.id; // ID this.$inputs.name.alexaSkill; // Alexa specific values // v4 this.$entities.name.value; // raw value this.$entities.name.resolved; // previously 'key' this.$entities.name.id; // ID this.$alexa.$entities.name.native; // Alexa specific values
Use v4 and v3 in Parallel
Thanks to our new organization-scoped (@jovotech
) packages, you're able to use v4
and v3
in parallel. v4
is available using jovo
, v3
using jovo3
:
# First, install the Jovo v4 CLI $ npm install -g @jovotech/cli $ jovo # Then, install the Jovo v3 CLI again $ npm install -g jovo-cli $ jovo3
We also updated the Debugger webhook URLs:
v4
:webhook.jovo.cloud
v3
:webhookv3.jovo.cloud
Make sure to update your Jovo v3
project to use the latest version that works with the updated Debugger URL:
$ jovo3 update
Integrations and Plugins
Platforms
As mentioned in the app configuration section, platforms are now added to the plugins
array of the app
constructor.
Platform classes are now also appended by Platform
. Here is an example for Alexa:
// v3 import { Alexa } from `jovo-platform-alexa`; // v4 import { AlexaPlatform } from `@jovotech/platform-alexa`;
The platform properties are now accessed using the platform name, instead of platform app type:
// v3 this.$alexaSkill; // v4 this.$alexa;
While in the past, most platform integrations had specific methods to add something to the response, almost everything can now be done using existing output template elements or the nativeResponse
property.
Here is an example for quick replies on Facebook Messenger:
// v3 handler await this.$messengerBot.showText({ text: 'Do you like pizza?', quickReplies: ['yes', 'no'], }); // v4 output template { message: 'Do you like pizza?', quickReplies: ['yes', 'no'], }
Here is an example for Alexa APL:
// v3 handler this.$alexaSkill.addDirective({ type: 'Alexa.Presentation.APL.RenderDocument', version: '1.0', document: {}, datasources: {}, }); // v4 output template { // ... platforms: { alexa: { nativeResponse: { response: { directives: [ { type: 'Alexa.Presentation.APL.RenderDocument', document: {}, datasources: {}, }, ]; } } } } }
CMS
- Improved configuration, take a look at Google Sheets and Airtable
responses
are now calledtranslations
Databases
- The
lastUsedAt
field was changed toupdatedAt
- The user context feature is now called
$history
Analytics
- Dashbot analytics now has a different configuration: The plugin is added to each platform that it is supposed to track
Plugins
- We updated our middlewares, you can find the names in our RIDR docs
- The plugin structure is almost the same, we just made some changes to the plugin lifecycle hooks.