For a side project I am currently working on I was in the need of some interactive (JavaScript) widget. After reading a lot about Vue in the last time, I decided to give it a try. Turns out if you want to add Vue to an existing project and still keep the benefits of single file components, a hot-reloading server etc., you are leaving the common scenario of SPAs and have to work around a few things.
Because I could not find a good resource describing how to integrate Vue in a widget-like manner in Django, this is how you can do it:
The webpack_loader package is a python lib which allows to dynamically load webpack generated bundles. After adding some basic stuff to your settings.py
, you are nearly ready to go. There are some litte things you have to take care of. I have added my vue.config.js
here so you can work around the problems.
In your settings.py
:
WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'vue/',
'STATS_FILE': os.path.join(BASE_DIR, 'static', 'vue', 'webpack-stats.json'),
}
}
Also dont forget to add webpack_loader
to you installed apps. A more detailed setup guide can be found here.
In the template you want to integrate the widget to:
{% load render_bundle from webpack_loader %}
<div class="vueapp" data-component="Hello2"></div>
{% render_bundle 'chunk-vendors' %}
{% render_bundle 'app' %}
The chunk-vendors
bundle contains all vendor code whereas app
contains your app code. I will describe in the following paragraph whats about the div.vueapp
.
To make all components available in your django template for loading, we make them all accessible from a newly created vue instance. To make this as dynamic as possible, we create a new vue instance/app for each element with a vueapp
class. The component we want to load is passed via data-component
attribute.
import Vue from "vue";
import store from "./store";
/* all components */
import Example1 from "./components/Example1.vue";
import Example2 from "./components/Example2.vue";
Vue.config.productionTip = false;
const components = {
Example1,
Example2
};
const apps = document.querySelectorAll(".vueapp");
for (let i = 0; i < apps.length; i++) {
const el = apps[i];
const compName = el.getAttribute("data-component");
const app = components[compName];
new Vue({
el: el,
store,
components: { app },
render: h => h(app)
});
}
Bonus WTF: I ran into a strange issue when adding the for loop here. Every time I added more than one component, only every 2nd component was rendered. Wtf? When debugging, I saw that my loop was not executed enough times. I then noticed that I had used
document.getElementsByClassName
which is a LIVE-element (HTMLCollection). When you are looping over the result of this method and one of the elements its returned is changed/removed (vue will replace the ‘app’ element), the HTMLCollection changes and thus apps.length changes. What the.. Note to self: Always use.querySelectorAll
.
The second thing you need to configure is a vue.config.js
file, which defiens where the files should be written to when building:
const BundleTracker = require("webpack-bundle-tracker");
if (process.env.NODE_ENV === "production") {
module.exports = {
outputDir: "../static/vue",
css: {
extract: false
},
configureWebpack: {
plugins: [
new BundleTracker({ filename: "../static/vue/webpack-stats.json" })
],
output: {
publicPath: "" /* needs to be set because of a webpack_loader bug */
}
}
};
}
You can start the build process now by executing vue build src/django.js
.
You are now able to load a new vue component by using this tag:
<div class="vueapp" data-component="Example1"/>
<div class="vueapp" data-component="Example2"/>
The advantage of this approach is that you can still you the Vue cli/webpack stuff to develop your component and then start the build process to make it available in Django.
If you are aware of a better or simpler approach, do not hesitate to shoot me an email ;)