Andrew Welch · Insights · #JavaScript #vuejs #graphql

Published , updated · 5 min read ·


Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.

A Little Taste of Petite-Vue

Petite-Vue is a tiny 5.8kb JavaScript library that gives you the pow­er of Vue.js, but slimmed down & opti­mized for sprin­kling small bits of inter­ac­tion on your web pages

A little taste of petite vue js

Many web devel­op­ers want to add some inter­ac­tiv­i­ty to their web pages, but using a full frame­work like React or Vue.js or Svelte is overkill for them, and jQuery or vanil­la JS isn’t quite enough.

This is espe­cial­ly true of serv­er-ren­dered HTML sys­tems, such as Craft CMS, Lar­avel, Statam­ic, Ruby on Rails, etc. where much of the state” is con­trolled by the backend.

If this sounds like you, I have fantastic news: Petite-Vue is for you!

Petite-Vue bills itself as a micro-frame­work (only 5.8kb in size) that is opti­mized for pro­gres­sive enhance­ment, allow­ing you to sprin­kle” inter­ac­tiv­i­ty on the fron­tend of pages ren­dered by a serv­er-side backend.

If this sounds some­what like Alpine.js, then you’re right! Alpine.js is a fan­tas­tic frame­work with goals sim­i­lar to Petite-Vue, but there are some advan­tages of using Petite-Vue that we’ll explore in this article.

Link Petite-Vue vs. Alpine.js

True to its name, Petite-Vue is a slimmed-down ver­sion of the pop­u­lar Vue.js fron­tend frame­work. Check out the Com­par­i­son with stan­dard Vue sec­tion of the Petite-Vue repos­i­to­ry if you want to see a break­down of the actu­al differences.

Like Alpine.js, it can be used with­out a build step or bundling process at all, just include the CDN link and away you go:

<script src="https://unpkg.com/petite-vue" defer init></script>

The biggest advan­tage of Petite-Vue is its pedi­gree. Because the API is Vue com­pat­i­ble, good things can happen:

  • If you don’t know Vue.js, learn­ing Petite-Vue is learn­ing a sub­set of Vue, which opens up a much larg­er world to you
  • If you do know Vue.js already, then using Petite-Vue on projects will allow you to hit the ground run­ning, with­out hav­ing to learn new syntax
  • If you start a project with Petite-Vue, and the require­ments change, mov­ing it to full-blown Vue.js gives you a very smooth migra­tion path rather than a dead-end
  • You can lever­age a large, exist­ing ecosys­tem of doc­u­men­ta­tion, knowl­edge, and code that exists already for Vue.js
  • You will have an eas­i­er time hir­ing peo­ple who have Vue knowl­edge to work on your project (or get­ting your­self hired based on your Vue knowledge)
  • Petite-Vue is writ­ten in Type­Script, so even if you don’t use Type­Script, you’ll get the auto-com­ple­tion of types & func­tions in your text edi­tor. Alpine.js is writ­ten in JavaScript, and has no type def­i­n­i­tions (yet!)

These are the rea­sons why adopt­ing Petite-Vue may make sense for you.

We’re going to test exact­ly how easy these the­o­ret­i­cal points are in prac­tice in this arti­cle, so read on!

Link Using Petite-Vue + GraphQL to make practical magic

This arti­cle isn’t going to teach you Petite-Vue; if you’re look­ing for that, check out the Petite-Vue doc­u­men­ta­tion or Dave Rupert’s My petite-vue review article.

Rather we’re going to revis­it an old­er arti­cle I wrote enti­tled Using Vue­JS + GraphQL to make Prac­ti­cal Mag­ic, which shows how to build a sim­ple Vue.js auto­com­plete field with GraphQL.

The arti­cle was writ­ten with Craft CMS as a back­end in mind, but that’s not par­tic­u­lar­ly rel­e­vant for our pur­pos­es. Any­thing that has a GraphQL API should work by just tweak­ing a few things.

We’re going to take this Vue.js auto­com­plete com­po­nent, and con­vert it over to Petite-Vue!

Here’s what the end-result looks like:

Auto Complete Search

Auto-com­­plete search result

I’ve resist­ed the temp­ta­tion to improve upon the code from the old­er arti­cle, pre­fer­ring to keep it as to make the com­par­i­son as direct as possible.

Also to keep things sim­ple, I’m just using Boot­strap & Axios from a CDN for the styling and XHR’s:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

Link GraphQL JavaScript preamble

The auto­com­plete com­po­nent has some set­up code that I’m call­ing the GraphQL JavaScript preamble”:

// Information needed for connecting to our CraftQL endpoint
const apiToken = 'vX45NkKh9jPQ2dBGbYnhcq3YR1mTRhzf';
const apiUrl = '/api';
// What to search for
const searchSections = ['blog'];
const searchPrefix = 'title:';
// The query to search for entries in Craft
const searchQuery =
    `
    query searchQuery($sections: [String], $needle: String!, $limit: Int)
    {
        entries(section: $sections, search: $needle, limit: $limit) {
            title
            url
        }
    }
    `;
// Configure the api endpoint
const configureApi = (url, token) => {
    return {
        baseURL: url,
        headers: {
            'Authorization': `Bearer ${token}`,
            'X-Requested-With': 'XMLHttpRequest'
        }
    };
};
// Execute a GraphQL query by sending an XHR to our api endpoint
const executeQuery = (api, query, variables, callback) => {
    api.post('', {
        query: query,
        variables: variables
    }).then((result) => {
        if (callback) {
            callback(result.data);
        }
        console.log(result.data);
    }).catch((error) => {
        console.log(error);
    })
};

It just has a few con­stants for defin­ing the API token, GraphQL API URL, etc., and the GraphQL query that we’ll be send­ing along to the back­end to return the results. Feel free to adjust this query as you see fit.

This code need­ed zero changes from the orig­i­nal arti­cle to adapt it to Petite-Vue.

Link HTML for the autocomplete search component

Next up we have the HTML for the auto­com­plete search com­po­nent (often referred to as a tem­plate” in Vue parlance):

<form id="demo" autocomplete="off">
    <div class="form-group">
        <label for="searchQuery">Search:</label>
        <input v-model="searchQuery" v-on:keyup="performSearch()" id="searchQuery" class="form-control" type="text" />
    </div>

    <div class="form-group">
        <ul class="list-group">
            <li v-for="(searchResult, index) in searchResults" class="list-group-item">
                <a v-bind:href="searchResult.url">{{ searchResult.title }}</a>
            </li>
        </ul>
    </div>

    <div class="form-group">
       <pre>data: {{ searchResults }}</pre>
    </div>
</form>

Check out the orig­i­nal arti­cle if you want a more detailed expla­na­tion of what the var­i­ous direc­tives here are, but it’s a basic search field with a dis­play of auto­com­plete results from the GraphQL API (if any) below it.

This code need­ed zero changes from the orig­i­nal arti­cle to adapt it to Petite-Vue.

Link The autocomplete search component

Here’s the orig­i­nal Vue 2.x auto­com­plete search com­po­nent from the orig­i­nal article:

// Instantiate our Vue instance
new Vue({
    el: '#demo',
    data: {
        searchApi: axios.create(configureApi(apiUrl, apiToken)),
        searchQuery: '',
        searchResults: {}
    },
    methods: {
        // Perform a search
        performSearch() {
            // If they haven't entered anything to search for, return nothing
            if (this.searchQuery === '') {
                this.searchResults = {};
                return;
            }
            // Set the variables we will pass in to our query
            const variables = {
                sections: searchSections,
                needle: searchPrefix + this.searchQuery,
                limit: 5
            };
            // Execute the query
            executeQuery(this.searchApi, searchQuery, variables, (data) => {
                this.searchResults = data.data.entries;
            });
        }
    }
})

To adapt this to Petite-Vue, what we need­ed to do is essen­tial­ly flat­ten” the object that we’re using, com­bin­ing all of the data, meth­ods, etc. into a sin­gle object. Here’s what it looks like in Petite-Vue:

// The actual petite-vue component
import { createApp } from 'https://unpkg.com/petite-vue?module'

createApp({
    // data
    searchApi: axios.create(configureApi(apiUrl, apiToken)),
    searchQuery: '',
    searchResults: {},
    // methods
    performSearch() {
        // If they haven't entered anything to search for, return nothing
        if (this.searchQuery === '') {
            this.searchResults = {};
            return;
        }
        // Set the variables we will pass in to our query
        const variables = {
            sections: searchSections,
            needle: searchPrefix + this.searchQuery,
            limit: 5
        };
        // Execute the query
        executeQuery(this.searchApi, searchQuery, variables, (data) => {
            this.searchResults = data.data.entries;
        });
    }
}).mount('#demo');

The changes boil down to remov­ing the data: and methods: keys in the object we’re return­ing, and then using the Vue.js 3.x createApp() and .mount() syn­tax for cre­at­ing & mount­ing our com­po­nent to exit­ing DOM elements.

That’s it! It was quite straight­for­ward, which I found very sat­is­fy­ing­ly surprising.

It real­ly was just mov­ing the fur­ni­ture around a lit­tle bit, the core code stayed the same.

Link What about going to full Vue.js?

So okay, that shows pret­ty clear­ly how exist­ing domain knowl­edge can trans­fer both ways between Petite-Vue and full Vue.js.

But what about the claim that if we were work­ing on a project using Petite-Vue, and the scope changed, we could eas­i­ly adapt it to full Vue.js instead? How easy of a tran­si­tion would that be?

Let’s have a look at the Vue.js 3.x ver­sion of the same component:

// The actual vue component
import { createApp } from 'https://unpkg.com/vue@3.2.4/dist/vue.esm-browser.js'

createApp({
    // data
    data: () => ({
        searchApi: axios.create(configureApi(apiUrl, apiToken)),
        searchQuery: '',
        searchResults: {},
    }),
    // methods
    methods: {
        performSearch() {
            // If they haven't entered anything to search for, return nothing
            if (this.searchQuery === '') {
                this.searchResults = {};
                return;
            }
            // Set the variables we will pass in to our query
            const variables = {
                sections: searchSections,
                needle: searchPrefix + this.searchQuery,
                limit: 5
            };
            // Execute the query
            executeQuery(this.searchApi, searchQuery, variables, (data) => {
                this.searchResults = data.data.entries;
            });
        }
    }
}).mount('#demo');

The only dif­fer­ences here are we change the script that we’re import­ing from Petite-Vue to Vue 3.x, and we’re moved our flat­tened” con­fig object back into data: and methods: keys in the object we return.

That’s it. I find that to be very compelling.

Link Stepping Stone

Sim­i­lar to Alpine.js, for many projects, Petite-Vue solves the Goldilocks” prob­lem in spades, being the fit that is just right.

Petite vue js stepping stones

How­ev­er, Petite-Vue has the added super­pow­er of an API that is com­pat­i­ble with its big­ger broth­er Vue.js, which I think has many ben­e­fits in the long term.

In that way, I con­sid­er it to be the per­fect amuse-bouche for many of my projects.

We also talk about Petite-Vue on a CraftQuest​.io live stream, if you want to check that out!