[Vuejs]-How can values be passed to Vue.js from within a server-rendered template (i.e. integrate Vue.js app with a django template)

0👍

Here is a first solution approach for django + Vue.js v2. It includes some stuff that is only relevant for django CMS. But the general principle applies to any server-side rendering CMS or web framework.

Enable Vue.js support in your Webpack config, something like the below:

frontend/webpack.config.js:

'use strict';


const path = require('path');
const MiniCssExtractPlugin = require(`mini-css-extract-plugin`);
const VueLoaderPlugin = require('vue-loader/lib/plugin');

const isServerMode = process.env.NODE_ENV === 'production';


const config = {
    mode: 'development',
    entry: {
        // here, any number of entrypoints (bundles) can be defined
        "marketplace-app": './frontend/blocks/marketplace-app/index.js',
    },
    output: {
        filename: '[name].bundle.js',
        path: __dirname + '/dist/',
        publicPath: `http://localhost:8090/assets/`,
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/,
            },
            {
                test: /\.svg$/i,
                exclude: /fonts/,
                loader: 'svg-url-loader',
            },
            {
                test: /\.(sass|scss|css)$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            sourceMap: true,
                            plugins: () => {
                                return [
                                    require('precss'),
                                    require('autoprefixer'),
                                ];
                            },
                            hmr: true,
                        }
                    },
                    {loader: 'css-loader', options: {sourceMap: true}},
                    {loader: 'sass-loader', options: {sourceMap: true}},
                ]
            },
            {
                // images
                test: /\.(jpe?g|png|gif)$/i,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            query: {
                                hash: 'sha512',
                                digest: 'hex',
                                name: '[name].[ext]'
                            }
                        }
                    },
                    {
                        loader: 'image-webpack-loader',
                        options: {
                            query: {
                                bypassOnDebug: 'true',
                                mozjpeg: {progressive: true},
                                gifsicle: {interlaced: true},
                                optipng: {optimizationLevel: 7},
                            }
                        }
                    }
                ]
            },
            {
                test: /\.(svg)(\?[\s\S]+)?$/,
                // svg fonts cannot be processed the way we do with svg images above
                // therefore they are handled separately here
                include: /fonts/,
                use: [
                    'file-loader'
                ]
            },
            {
                test: /\.woff2?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
                loader: 'file-loader',
            },
            {
                test: /\.(ttf|eot)(\?[\s\S]+)?$/,
                loader: 'file-loader',
            },
            {
                test: /\.modernizrrc.js$/,
                use: ['modernizr-loader'],
            },
            {
                test: /\.modernizrrc(\.json)?$/,
                use: ['modernizr-loader', 'json-loader'],
            },
            {
                test: /\.vue$/,
                use: [{
                    loader: 'vue-loader'
                }, /*{
                    loader: 'eslint-loader' // You can uncomment this if you want compiling to fail if linting fails
                }*/]
            },
        ],
    },
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.vue'],
        modules: [
            path.resolve('frontend'),
            'node_modules'
        ],
        alias: {
            modernizr$: path.resolve(__dirname, '/frontend/.modernizrrc'),
            vue: process.env.NODE_ENV === 'production' ? 'vue/dist/vue.min.js' : 'vue/dist/vue.js',
        }
    },
    devServer: {
        contentBase: path.resolve(__dirname, `frontend`),
        headers: {'Access-Control-Allow-Origin': '*'},
        host: `localhost`,
        port: 8090,
        hot: true,
        inline: true,
    },
    plugins: [
        new VueLoaderPlugin(),
        new MiniCssExtractPlugin({filename: '[name].css'}),
    ],
    devtool: 'eval-source-map',
    optimization: {
        // the default config from webpack docs, most of it might be useless
        splitChunks: {
            chunks: 'async',
            minSize: 30000,
            maxSize: 0,
            minChunks: 1,
            maxAsyncRequests: 5,
            maxInitialRequests: 3,
            automaticNameDelimiter: '~',
            name: true,
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10,
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true,
                },
            },
        },
    },
};


if (isServerMode) {
    config.mode = 'production';
    config.devtool = 'none';
    config.output.filename = '[name].js';
    config.output.publicPath = '/static/dist/';
}


module.exports = config;

example for the server-side template (i.e. in django this could be something like backend/marketplace/plugins/marketplace_list_plugin/templates/marketplace_list_plugin/marketplace-list-plugin.html):

Notes:

  • This demos how to import one value (an endpoint url) from the django template into the Vue.js application.
  • supports local dev env mode with hot reloading via webpack dev server based on the value of a global setting variable settings.DJANGO_ENV
  • settings.WEBPACK_DEV_URL needs to be set to publicPath from the Webpack dev server setting in webpack.config.js
  • data-is-reload-on-page-edit class needs to be tied to CMS functionality that reloads the js sources upon a frontend editing action, if CMS has such functionality.
{% load i18n staticfiles static thumbnail %}

{% comment %}   this is a vue widget from frontend/blocks/marketplace-app/index.js   {% endcomment %}

<div id="{{ instance.anchor_id }}">
    <div class="marketplace-vue-widget" data-endpoint-url='{% url 'marketplace:article-list' %}'></div>
</div>

{% if settings.DJANGO_ENV.value == 'local' %}
    <script data-is-reload-on-page-edit defer src="{{ settings.WEBPACK_DEV_URL }}/marketplace-app.bundle.js"></script>
    <link rel="stylesheet" href="{{ settings.WEBPACK_DEV_URL }}/marketplace-app.css">
{% else %}
    <script data-is-reload-on-page-edit defer src="{% static 'dist/marketplace-app.js' %}"></script>
    <link rel="stylesheet" href="{% static 'dist/marketplace-app.css' %}">
{% endif %}

Frontend setup (frontend/blocks/marketplace-app/index.js):

import Vue from 'vue';
import App from './app.vue'


// this relates to backend/marketplace/plugins/marketplace_list_plugin/templates/marketplace_list_plugin/marketplace-list-plugin.html
// CMS plugins could (in theory) be added more than once to a page,
// we take care to allow the vue instance to be mounted on any occurrences

// create a constructor for your widget
let Widget = Vue.extend({
    render(h) {
        return h(App, {
            props: { // load stuff from data attributes in the django template
                endpointUrl: this.$el.getAttribute('data-endpoint-url'),
            }
        })
    },
});

// mount the widget to any occurences
let nodes = document.querySelectorAll('.marketplace-vue-widget'); // unique selector!
for (let i = 0; i < nodes.length; ++i) {
    new Widget({el: nodes[i]})
}

app.vue:

<template src='./app.html'></template>
<script src="./app.js"></script>
<style lang="scss" scoped src="./app.scss"></style>

app.js:

// example code

export default {
    props:["endpointUrl"],
    data() {
        return {}
    },
    computed: {},
    mounted() {},
    watch: {},
    methods: {},
    components: {},
}

Leave a comment