Skip to content

Creating your first plugin

We'll focus on a frontend-only plugin as that is the simplest to get started with.

Prerequisites

Before you begin this, you'll need:

You can find some examples over at karrot/plugin-examples.

We'll use vite for this project, you can use other tools, but this is recommended.

Assuming you have karrot-frontend up and running elsewhere, go to a new directory, and we'll create your plugin project. It'll be a separate project to karrot-frontend, so don't create inside that folder.

yarn or npm or pnpm or bun or deno

There are lots of ways to run and develop JavaScript in 2024. We use yarn (classic) with Karrot, but I'm sure you can adapt this guide for another tool if you prefer it.

Initialize the project

bash
yarn create vite

I chose these options (the Vue option is important, the others you can choose what you want):

✔ Project name: … frontend-only
✔ Select a framework: › Vue
✔ Select a variant: › JavaScript

Now we can go into the directory we just created:

bash
cd frontend-only

Install the dependencies:

bash
yarn

Start the dev server to check it's working:

bash
yarn dev

You should see something like this:

  VITE v5.2.4  ready in 101 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

When you visit http://localhost:5173/ you should see some content!

TIP

You might have a different address to visit check what it printed out for you

⚙️ Setup to be a plugin

We need to make some changes now to prepare it to be a Karrot plugin. This is the boring bit before we get round to the fun stuff.

Externalizing libraries

When you run your plugin, Karrot will make some libraries available. We don't want them to get included twice (once in Karrot and once in your plugin), so we externalize those libraries.

This means when your plugin is built, it won't include them as it ✨ knows ✨ those libraries will be provided externally.

First we need a vite plugin to help us out:

bash
yarn add -D vite-plugin-externalize-dependencies

Then, open your vite.config.js (or vite.config.ts if you selected Typescript earlier), which should look approximately like this:

js
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
})

And add code so it looks something like this:

js
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import externalize from "vite-plugin-externalize-dependencies"

// Libraries that will be provided by Karrot
// You can leave any out that you don't actually want to use
const externals = [
  "vue",
  "vue-router",
  "quasar",
  "axios",
  "@tanstack/vue-query",
  "@karrot/plugin",
]

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    // Karrot uses vue!
    vue(),
    // Externals support during development
    externalize({ externals }),
  ],
  build: {
    // Karrot plugin system uses this manifest to know which files are in the plugin
    manifest: true,
    rollupOptions: {
      // Entry point, this is the file that we'll load initially, should export a Karrot plugin
      input: ["./src/main.js"],
      // Externals support for build
      external: externals,
      // This is "library mode" and ensures we include the plugin in the export
      preserveEntrySignatures: "strict",
    },
  },
})

Install externals locally

You might find it useful to have those externals also installed in your project, so your editor can offer you coding assistance. This is a good idea.

The externalize configuration only affects how it's exported.

So, you can install the dependencies locally with:

bash
yarn add vue vue-router quasar axios @tanstack/vue-query

If you want to match the exact versions we use in Karrot you can check the karrot-frontend package.json and yarn.lock files.

For more casual use though, you should be fine with whatever it installs 😃. We tend to keep up with version updates.

When it runs it'll always use the same version as Karrot does, so there will be no conflicts - but if the versions are very different your plugin might not run.

🚧 In the future we might publish the @karrot/plugin package and include type information.

🧹 Clean out the template files

The vite template gives you files we don't need, so you can remove:

  • index.html
  • everything in src

☁️ Dreaming ...

One day we could create our own template project, so you can just run yarn create karrot-plugin 😃

Define our plugin

Then we need to define our plugin in src/main.js:

js
import { unref } from "vue"
import { setCssVar } from "quasar"
import HelloWorld from "./components/HelloWorld.vue"
import { useModifySidenav, useCurrentGroupService } from "@karrot/plugin"

// This is our plugin definition
export default {
  // This will be called when the page first loads
  // It's a quasar boot file, so you can read their docs
  // https://quasar.dev/quasar-cli-vite/boot-files/
  boot({ router }) {
    // Change the colour scheme!
    // You can see more options at https://quasar.dev/style/color-palette#how-it-works
    setCssVar("primary", "#581845")

    // Add a new page within the groups section
    // This is just a normal route definition
    // See https://router.vuejs.org/guide/advanced/dynamic-routing.html
    router.addRoute("group", {
      name: "hello-world",
      path: "hello-world",
      meta: {
        breadcrumbs: [
          {
            name: "🌎",
          },
        ],
      },
      component: HelloWorld,
    })
  },

  // This is called when the app boots
  // It works like a vue setup entrypoint
  // See https://vuejs.org/api/composition-api-setup
  setup() {
    // We need to pass groupId for routes as we won't always
    // have a groupId available in the current route (e.g. on the profile page)
    // Note that it's a ref, so we need to unref it later or call .value on it
    const { groupId } = useCurrentGroupService()

    useModifySidenav((entries) => {
      // Put our new menu item at the top
      entries.splice(0, 0, {
        name: "hello-world",
        label: "Hello World",
        icon: "fas fa-globe",
        // This is the route name we want to link to
        to: { name: "hello-world", params: { groupId: unref(groupId) } },
      })
    })
  },
}

Not everything from quasar is available

You can check the list of things we export here src/plugin/quasar.js

Maybe in the future this changes 😃 If there is something you need, please ask!

Then create the <HelloWorld> component in src/components/HelloWorld.vue:

vue
<template>
  <h1>Hello World!</h1>
</template>

🔌 Connecting your plugin to the karrot-frontend

Ensure your plugin dev server is running. Now, we need to tell your karrot-frontend dev server about your plugin.

In the karrot-frontend directory, create or edit a .env file to add the following line:

bash
LOCAL_PLUGINS=http://localhost:5173/src/main.js

Check the address!

Check that is the correct address for your plugin dev server, as it might not always be that address.

Now go and open the karrot-frontend site, which should be at http://localhost:8080 and if you're logged in you should be able to use your new menu entry! 🥳

🧢 Recap

So, what we just did:

  • created a new plugin project using vite
  • configured vite to work for building a plugin rather than a whole app
  • created a basic Karrot plugin
  • connected it to our locally running karrot-frontend

What we didn't do:

  • build the plugin ready to be installed in a Karrot instance
  • use any data or APIs
  • use Quasar components

That's all for now folks! 🐰 🥕 👋