DEV Community

Cover image for Django and Modern JS Libraries - Svelte (3)
Can Burak Sofyalioglu
Can Burak Sofyalioglu

Posted on • Edited on • Originally published at cbsofyalioglu.com

Django and Modern JS Libraries - Svelte (3)

Django and Modern JS Libraries - Svelte

(Note:This article is originally published on cbsofyalioglu.com while building the websites of Istanbul private transfer, Istanbul Cruise Port Transfer and Izmir alarm sistemleri)

Another shameless plug of mine is the article that reviews Best Blogging Platforms for Developers

In the previous part, we built a Django backend and GraphQL API. In this part, we will integrate the Django project with Svelte.

Thus, it's necessary to follow first part of the tutorial .


What is Svelte and How it differs from React?

I told that I like Python and its ecosystem. I also like Just-In-Time compilers and language supersets like Cython, which really boosts Python performance. When I learned that JavaScript is an interpreted language, I tried to look Cython equivalent of it. Because of different browser compilers, I couldn’t find what I want and it made a disappointment. Maybe it is the reason I feel excitement when I give Svelte a chance.

If you didn't try Svelte before, you may give it a chance. Svelte's interactive API and tutorials are also worth to praise. Being familiar with Svelte API and Tutorials is definitely recommended.

When I'm talking about Svelte, I'm strictly speaking about Svelte 3. It is another JavaScript library written by Rich Harris. What makes Svelte special is:

  • It is truly a reactive library and it doesn't use virtual DOM like React. Therefore, there is no VDOM diff calculations.
  • It has a compiler and when you build your application it produces optimized JavaScript code. In the end, Svelte code almost disappear and you have vanilla JavaScript.
  • You can write HTML, CSS and JavaScript in single file component and there will be no global CSS pollution.

Yes, React was revolutionary. However, how many times we have to deal with virtual DOM synchronization problems or the extra burden for even very small operations are the other side of the medallion.

Graphql and Svelte

Svelte Configuration with Webpack from Scratch

Step - 1: Configuring development environment

(Note: if you already installed the node, you can skip this part)

We will use Node backend for the development environment. Therefore, we need to install Node and Node package manager npm. To prevent potential dependency problems, we will create a clean node environment. I will use NVM which is Node version manager, and it allows us to create isolated Node environments. In your terminal, run the code below.

Setup Node Environment with NVM

In your terminal, run the code below.

# install node version manager 
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash

# check installation
command -v nvm

# should prints nvm, if it doesn't, restart your terminal
Enter fullscreen mode Exit fullscreen mode
# install node
# node" is an alias for the latest version

# use the installed version
nvm use node
nvm install node 
Enter fullscreen mode Exit fullscreen mode

Now we can create frontend directory in Django project. Go to the root directory of the project. 'backend/'

In your terminal copy and paste the code.

# create frontend directory
mkdir FRONTEND
cd FRONTEND

# now your terminal directory should be
# backend/FRONTEND 
# create a node project
npm init
# you may fill the rest
Enter fullscreen mode Exit fullscreen mode

Now we can install front-end and development libraries .

# install svelte and other libs 
npm install --save-dev svelte serve cross-env  graphql-svelte

# install webpack and related libs
npm install --save-dev webpack webpack-cli webpack-dev-server

# install webpack loaders and plugins 
npm install --save-dev style-loader css-loader svelte-loader mini-css-extract-plugin

npm install --save node-fetch svelte-routing
Enter fullscreen mode Exit fullscreen mode

Update package.json scripts part as below. Your file should look like this and ignore the versions.

{
  "name": "django-svelte-template",
  "description": "Django Svelte template. ",
  "main": "index.js",
  "scripts": {
    "build": "cross-env NODE_ENV=production webpack",
    "dev": "webpack-dev-server --content-base ../templates"
  },
  "devDependencies": {
    "cross-env": "^7.0.2",
    "css-loader": "^3.5.3",
    "graphql-svelte": "^1.1.9",
    "mini-css-extract-plugin": "^0.9.0",
    "serve": "^11.3.1",
    "style-loader": "^1.2.1",
    "svelte": "^3.22.3",
    "svelte-loader": "^2.13.6",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.11.0"
  },
  "dependencies": {
    "node-fetch": "^2.6.0",
    "svelte-routing": "^1.4.2"
  }
}

Enter fullscreen mode Exit fullscreen mode

Let's create application necessary files and folders for Svelte. In the root directory of the project 'backend/' , open your terminal.

# create HTML file of the project
cd templates
touch index.html

# change directory to backend/FRONTEND
cd ../FRONTEND
mkdir src
touch index.js
touch webpack.config.js

# change directory to backend/FRONTEND/src
cd src
touch App.svelte
touch MovieList.svelte
touch MoviePage.svelte
touch api.js
Enter fullscreen mode Exit fullscreen mode

Step 2 - Webpack configuration

What is webpack ?

Webpack is a module bundler and a task runner. We will bundle all our JavaScript application including CSS styling into two JavaScript files, if you prefer you can output only one file. Because of the rich plugins, you can also do many things with Webpack like compressing with different algorithms, eliminate unused CSS code, extracting your CSS to different files, uploading your bundle to cloud provider like S3 etc…

I made two different Webpack settings in one file. One is for development environment, and the other one is for production environment. Also note that we do not optimize these configurations.

Copy/Paste the following code into *****webpack.config.js***** file.

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');

const mode = process.env.NODE_ENV || 'development';
const isEnvProduction = mode === 'production';

const productionSettings = {
    mode,
    entry: {
        bundle: ['./index.js']
    },
    resolve: {
        alias: {
            svelte: path.resolve('node_modules', 'svelte')
        },
        extensions: ['.mjs', '.js', '.svelte'],
        mainFields: ['svelte', 'browser', 'module', 'main']
    },
    output: {
        path: path.resolve(__dirname, '../static'),
        filename: 'js/[name].js',
        chunkFilename: 'js/[name].[id].js'
    },
    optimization: {
        minimize: true,
        runtimeChunk: false,
      },
    module: {
        rules: [
            {
                test: /\.svelte$/,
                use: {
                    loader: 'svelte-loader',
                    options: {
                        emitCss: true,
                        hotReload: true
                    }
                }
            },
            {
                test: /\.css$/,
                use: [
                    /**
                     * MiniCssExtractPlugin doesn't support HMR.
                     * For developing, use 'style-loader' instead.
                     * */
                    MiniCssExtractPlugin.loader,
                    'css-loader'
                ]
            }
        ]
    },
    devtool: false,
    plugins: [
        new MiniCssExtractPlugin({filename: '[name].css'})
    ],
};

const devSettings = {
    mode,
    entry: {
        bundle: ['./index.js']
    },
    resolve: {
        alias: {
            svelte: path.resolve('node_modules', 'svelte')
        },
        extensions: ['.mjs', '.js', '.svelte'],
        mainFields: ['svelte', 'browser', 'module', 'main']
    },
    output: {
        publicPath: "/",
        filename: 'static/js/bundle.js',
        chunkFilename: 'static/js/[name].chunk.js',
    },
    devtool: 'source-map',
    devServer: {
        historyApiFallback: true,
        stats: 'minimal',
      },
    module: {
        rules: [
            {
                test: /\.svelte$/,
                use: {
                    loader: 'svelte-loader',
                    options: {
                        emitCss: true,
                        hotReload: true
                    }
                }
            },
            {
                test: /\.css$/,
                use: [
                    /**
                     * MiniCssExtractPlugin doesn't support HMR.
                     * For developing, use 'style-loader' instead.
                     * */
                    'style-loader',
                    'css-loader'
                ]
            }
        ]
    },
    mode,
    plugins: [
    ],
}


module.exports = isEnvProduction ? productionSettings : devSettings;
Enter fullscreen mode Exit fullscreen mode

Step 3 - Create a Single-Page-App witth Svelte

First, fill the 'backend/FRONTEND/index.js'.

import App from './src/App.svelte';

const app = new App({
    target: document.body,
});

window.app = app;

export default app;
Enter fullscreen mode Exit fullscreen mode

Next, fill the 'App.svelte' file with proper logic.

<!-- App.svelte -->
<script>
  import { Router, Link, Route } from "svelte-routing";
  import MovieList from "./MovieList.svelte";
  import MoviePage from "./MoviePage.svelte";

  export let url = "";
</script>

<Router url="{url}">
  <nav class="navbar">
    <Link to="/">Home</Link>
  </nav>
  <div class="main-container">
    <Route path="movie/:slug" component="{MoviePage}" />
    <Route path="/"><MovieList /></Route>
  </div>
</Router>

<style>
    .navbar {
        background-color:rgba(0,0,0,0.6);
        display: flex;
        padding: 16px 64px;
        font-weight: bold;
        color:white;
    }
    .main-container {
        margin-top:32px;
        display:flex;
        justify-content: center;
        align-items: center;
        background-color: rgba(0,0,0, 0.15);
    }

</style>
Enter fullscreen mode Exit fullscreen mode

Before routing pages, I will first write the client-side queries. Please open api.js and copy/paste the code below.

import { GraphQLProvider, reportCacheErrors } from "graphql-svelte";

const client = GraphQLProvider({
    url: 'http://127.0.0.1:8000/graphql',
    headers: () => ({
        "content-type": "application/json",
        Accept: 'application/json'
    })
})


client.graphql.on('cache', reportCacheErrors)





// our first query will requests all movies
// with only given fields
// note the usage of string literals (`)
export const MOVIE_LIST_QUERY = `
    query movieList{
        movieList{
            name, posterUrl, slug
        }
    }
`

// Note the usage of argument.
// the exclamation mark makes the slug argument as required
// without it , argument will be optional
export const MOVIE_QUERY = `
    query movie($slug:String!){
        movie(slug:$slug){
            id, name, year, summary, posterUrl, slug
        }
    }
`

// This is generic query function
// We will use this with one of the above queries and
// variables if needed
export async function get(query, variables = null) {
    const response =  await client.get({ query , variables })
    console.log("response", response);
    return response
}


Enter fullscreen mode Exit fullscreen mode

Now, route pages: MovieList.svelte will be shown on homepage as we defined in the above. If user clicks any movie card, then MoviePage.svelte file will be rendered.

Fill the MovieList.svelte.

<script>
    import { Router, Link, Route } from "svelte-routing";
    import { get, MOVIE_QUERY, MOVIE_LIST_QUERY } from "./api.js";

    var movielist = get(MOVIE_LIST_QUERY);

</script>

<div class="wrapper">

    <!-- promise is pending -->
    {#await movielist}
        loading

    <!-- promise was fulfilled -->
    {:then response}
        {#if response.data.movieList.length > 0}
            {#each response.data.movieList as movie}
                <div class="card">
                    <Link to={`/movie/${movie.slug}`}>
                        <img class="poster" alt={movie.name} src={movie.posterUrl} />
                        <p class="movie-title">{movie.name}</p>
                    </Link>
                </div>
            {/each}
        {/if}

    <!-- promise was rejected -->
    {:catch error}
        <p>Something went wrong: {error.message}</p>
    {/await}
</div>
<style>
    .wrapper {
        width:100%;
        height: auto;
        display:flex;
        flex-direction: row;
        flex-wrap: wrap;
    }
    .card {
        box-sizing: border-box;
        position: relative;
        width:200px;
        height:auto;
        margin:16px;
        border-radius: 8px;
        overflow: hidden;
        box-shadow: 0 4px 4px rgba(0,0,0,0.25);
    }
    .poster {
        width:100%;
        height:auto;
        cursor: pointer;
    }
    .movie-title {
        padding:4px 8px;
        font-weight: bold;
        text-decoration: none;
        cursor: pointer;
    }
</style>


Enter fullscreen mode Exit fullscreen mode

Also fill MoviePage.svelte according to this.

<script>
    import { Router, Link, Route } from "svelte-routing";
    import { get, MOVIE_QUERY } from "./api.js";

    // acquired from dynamic route part => /movie/:slug
    export let slug;

    const moviedata = get(MOVIE_QUERY, {slug})

</script>

<div class="wrapper">

    <!-- promise is pending -->
    {#await moviedata}
        <p>Movie {slug} is loading</p>

    <!-- promise was fulfilled -->
    {:then moviedata}
        {#if moviedata.data}
            <div class="movie-container">
                <img 
                    src={moviedata.data.movie.posterUrl} 
                    alt={`${moviedata.data.movie.name} poster`} 
                    class="movie-poster"
                />
                <div class="text-box">
                    <h1 class="movie-title">{moviedata.data.movie.name}</h1>
                    <p class="movie-description">{moviedata.data.movie.summary}</p>
                </div>
            </div>
        {/if}

    <!-- promise was rejected -->
    {:catch error}
        <p>Something went wrong: {error.message}</p>
    {/await}
</div>
<style>
    .wrapper {
        width:100%;
        height: auto;
        display:flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
    }
    .movie-container {
        display: flex;
        flex-wrap: wrap;
        max-width:500px;
    }
    .movie-poster {
        width:250px;
        height:auto;
    }
    .text-box {
        display: flex;
        flex-direction: column;
    }
</style>


Enter fullscreen mode Exit fullscreen mode

Graphql and Svelte with Django

Start Svelte App in Development Environment

In the development environment we will run two different servers. When our Svelte app is running, it requests data from Django server. After the response come, Webpack Development server render the page with proper data. This is only for development stage.

When we finish the front-end development, we will build and bundle client-side app. After then we will start Django server, and it will be the only server we will use in production environment, as I promise before.

Go to the root folder of the Django project. '*backend/'*

Execute the below command and make the Django server ready for front-end requests.

# execute it on the root folder of Django 'backend/'
python manage.py runserver

Enter fullscreen mode Exit fullscreen mode

Open another terminal and change the directory to the 'backend/FRONTEND'

# On another terminal
npm run start

Enter fullscreen mode Exit fullscreen mode

When Svelte app successfully compiled, open your browser 'localhost:8080/'.

You should see similar screen like the image below.

List of movies screen

MovieList.svelte will render the screen

Movie page screen

MoviePage.svelte screen will render this if the user clicks any movie card

What will be happened at the moment?

At this moment, “/“ root page will be rendered. Because of our routing configurations, MovieList.svelte file will be rendered first. If the user clicks any film card, then the MoviePage.svelte file will be rendered regarding its slug value.

We succesfully integrated Django and Svelte. Now make the production build.


Django and Svelte Integration in Production Environment

Now you can stop webpack server while keeping the Django server alive.

In the backend/FRONTEND/ directory, execute the command below.

npm run build

Enter fullscreen mode Exit fullscreen mode

This will build and bundle all your Svelte app in bundle.js file. When bundling process is over, go to the URL of Django server in your browser. --> "127.0.0.1:8000/"

You should see the same screens above. Also note the static folder which has new files coming from webpack bundling.

FINISHED

This is the code repo of all three parts.

(Note:This article is originally published on cbsofyalioglu.com while building the websites of Istanbul Airport Transfer, Istanbul Cruise Port Transfer and Istanbul Travel Guide)

Top comments (2)

Collapse
 
michaeldg7 profile image
michaeldg7

Can, this is such a great presentation!! Django + Svelte as part of a "stack", represent the best of coding simplicity & productivity -- especially for newbie programmers :)

Really looking forward to even more of your presentations!!
Thanks for all y our hard work in putting this together!!

Collapse
 
canburaks profile image
Can Burak Sofyalioglu

Your comment made me happy. Thanks for your feedback. I am trying to improve my presentation.