Making a Vue.js application usable from the outside: the widget pattern

Hello there! It's been a while since I posted anything, but today I'd like to take a moment to tell you about a simple yet quite efficient pattern we've been using at my current job regarding Vue.js applications.

As with any front-end framework, Vue.js isn't really opinionated regarding how you're supposed to inject configuration into your SPA, assuming this configuration comes from your back-end service. A common pattern in this era of API-centric services is to dedicate a JSON endpoint to configuration that your JavaScript code will fetch immediately upon boot. This allows us to keep all back-end considerations aside, but has several disadvantages as well:

  • limited flexibility if the network or your back-end itself fail (the more you can provide for an offline and/or degraded experience, the better)
  • somewhat extraneous network round-trip for something you could have provided locally to begin with
  • your JS code still needs to know which URL to request, which is already configuration; how do you provide that?

For all these reasons, we have began to experiment with a little something we like to call the widget pattern. Nothing crazy here: we essentially wrap the new Vue declaration inside a build function, which is then called from public/index.html:

<body>
<div id="app"></div>
<script>
document.addEventListener("DOMContentLoaded", function() {
window.App.build("#app", { /* ... */ });
});
</script>
</body>

We then use its second argument in src/main.js to inject config into our app, which we can variabilize depending on the environment if we need to, by swapping wildcards with environment variables from Docker for example:

import Vue from "vue";
import App from "./components/App";

export default App; // more on that later

export function build(el, config) {
new Vue({
el,
render: h => h(App, props: { /* ... */}),
});
}

All we need to do to make this work is to expose our root module in vue.config.js:

module.exports = {
configureWebpack: {
output: {
library: "App",
libraryTarget: "umd"
}
}
};

This gives us the flexibility we need, and forces us to reflect on our app's root component's input, as its props are used to receive the aforementioned configuration values. We thus also made an habit of exporting this root component as default, which means we can use our app on its own as before or use it as a "widget" (hence the "pattern" name) in a larger Vue.js codebase or a legacy application.

If you're using something similar or have something to say about this way of doing things, use the comment section below!