Contents
Introduction
If, for some strange reason, you want to set up a project using TypeScript with pure Svelte v5.x, style the HTML elements with Tailwind v4.x, compile and bundle with Rollup v4.x, and use a Python-based backend like Django or Flask,Β then read this post.Β
I'm assuming you're already familiar with these technologies.
Iβm a big fan of Ubuntu, so all the commands in this post are based on or executed in a Bash terminal.
At the time of writing, I'm using:
- Ubuntu: 24.04.2 LTS
- Node: v22.14.0
- npm: 10.9.2
Initialization
Let's start by creating a folder and initializing the frontend project using npm
.
mkdir myproject && cd myproject
mkdir myfrontend && cd myfrontend && npm init -y
-y
initializes the new project with default values, which can be changed later inside the package.json
file. Nonetheless, we need to add the property: "type": "module"
to package.json
file, because we are going to use the modern import/export
syntax. Also modify the value of main
property to src/main.ts
.Β You should have something similar to this:
{
"name": "myfrontend",
"version": "1.0.0",
"main": "src/main.ts",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
},
"dependencies": {
}
}
We need a source folder and entry point files.
mkdir src && touch src/main.ts && touch src/app.css
TypeScript
Since TypeScript encourages writing less error-prone code, we are using it instead of JavaScript. To install TypeScript, type inside the project directory:
npm install typescript --save-dev
--save-dev
indicates that this package is for development purposes, therefore it will be saved inside devDependencies
Since TypeScript needs a tsconfig.json
file, let's create it with default settings.
npx tsc --init
This will generate the tsconfig.json
file with the recommended default settings in the current directory. Important: make sure that target
and module
have ESNext
as their values inside the tsconfig.json
file.
Rollup
We are going to install Rollup locally so that we do not pollute the global packages folder.
npm install rollup --save-dev
Create an empty rollup.config.js
file inside myproject/myfrontend
directory.
touch rollup.config.js
It's time to install the necessary Rollup plugins. We will configure Rollup later.
npm install --save-dev rollup-plugin-svelte
npm install @rollup/plugin-node-resolve --save-dev
npm install @rollup/plugin-replace --save-dev
npm install @rollup/plugin-typescript --save-dev
npm install @rollup/plugin-terser --save-dev
Tailwind
Styling elements with Tailwind is straightforward and easy; therefore, we are installing the CLI version.
npm install tailwindcss @tailwindcss/cli --save
Include this line in src/app.css
:
@import "tailwindcss";
Svelte
Time to install the standalone Svelte package:
npm install svelte --save
npm install svelte-preprocess --save-dev
Create the svelte.config.js
file in the root directory (myproject/myfrontend
)
touch svelte.config.js
Also create constants.ts
file inside the src
directory:
touch src/constants.ts
with the following single declaration:
export declare const __PROD__: boolean;
Insert inside the svelte.config.js
the following content:
import { sveltePreprocess } from 'svelte-preprocess'
const production = !process.env.ROLLUP_WATCH
export default {
preprocess: sveltePreprocess({sourceMap: !production}),
};
Create the App.svelte
file inside the src
directory:
touch src/App.svelte
and insert the subsequent code:
<script lang="ts">
let count = $state(0);
</script>
<div class="p-6 bg-white rounded-lg shadow-md max-w-md mx-auto mt-10 space-y-4">
<h1 class="text-3xl font-bold underline">Hello world!</h1>
<p class="text-lg">This is a Svelte app with Tailwind CSS.</p>
<p class="text-lg">Count: {count}</p>
<!-- Use flexbox for button layout -->
<div class="flex space-x-2">
<button class="flex-1 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" onclick={() => count += 1}>
Increment
</button>
<button class="flex-1 bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded" onclick={() => count -= 1}>
Decrement
</button>
<button class="flex-1 bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded" onclick={() => count = 0}>
Reset
</button>
</div>
</div>
Add the following mounting content inside the src/main.ts
file:
import { mount } from 'svelte'
import './app.css'
import App from './App.svelte'
const app = mount(App, {
target: document.body,
})
export default app
Virtual Environment
At this point, we should create and activate a virtual environment with a specific Python version. For this purpose, I use the Conda package manager and Python 3.13:
conda create --yes --name myproject python=3.13 && conda activate myproject
Django
Our current working directory at this point should be myproject/myfrontend
. We need to move back to the myproject
directory and create the backend-related folder:
cd .. && mkdir django_back && cd django_back
Let's create the requirements.txt
file to hold all the required packages.
touch requirements.txt
and add this to it (change the version to the latest):
Django==5.2
We need to install Django. This can be done with the following command:
pip install -r requirements.txt
Django Project Creation
It's time to create a new Django project:
django-admin startproject mysite .
The command django-admin startproject mysite .
initializes a new Django project named mysite
in the current directory (.
). It generates the following structure:
manage.py
: A command-line utility for managing the project (e.g., running the server, migrations).mysite/
: A directory containing the core project files, including:__init__.py
: An empty file indicating that this directory is a Python package.settings.py
: The settings/configuration file for the project.urls.py
: The URL configuration file for routing requests.asgi.py
: The entry point for ASGI-compatible web servers.wsgi.py
: The entry point for WSGI-compatible web servers.
The .
at the end ensures the project files are created in the current directory instead of a new subdirectory.
Django App Initialization
Django provides a command that can be executed inside the folder containing the manage.py
file to create a new application with default folders and files:
python manage.py startapp myapp
The command creates a skeleton of the application. It generates a directory structure with default files needed for the app, including:
myapp/migrations/
: A directory for database migration files.myapp/admin.py
: A file to register models with the Django admin interface.myapp/apps.py
: A file to configure the app.myapp/models.py
: A file to define database models.myapp/tests.py
: A file to write unit tests for the app.myapp/views.py
: A file to define views (logic for handling requests and responses).myapp/__init__.py
: An empty file indicating that the directory is a Python package.
We need to create a new directory named myapp/templates
and two files myapp/templates/index.html
and myapp/templates/base.html
:
mkdir myapp/templates && touch myapp/templates/index.html && touch myapp/templates/base.html
Insert the following content into myapp/templates/base.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Djanog App{% endblock %}</title>
{% block link_head %}{% endblock %}
</head>
<body>
<header>
<h1>Welcome to My Django App</h1>
</header>
<main>
{% block content %}
<p>This is the default content.</p>
{% endblock %}
</main>
<footer>
<p>© 2025 My Django App</p>
</footer>
{% block script_body %}{% endblock %}
</body>
</html>
And add this content to myapp/templates/index.html
:
{% extends "base.html" %}
{% load static %}
{% block link_head %}
<link rel="stylesheet" href="{% static 'frontend/css/app.css' %}">
{% endblock %}
{% block content %}
<p>Hello, World!</p>
<p>This is the index page of my Django app.</p>
{% endblock %}
{% block script_body %}
<script src="{% static 'frontend/js/bundle.js' %}"></script>
{% endblock %}
Add to the myapp/views.py
file this:
from django.views.generic import TemplateView
# Create your views here.
class HomePageView(TemplateView):
template_name = "index.html"
We need to create a URL configuration file:
touch myapp/urls.py
containing the following content:
from django.urls import path
from .views import HomePageView
urlpatterns = [
path("", HomePageView.as_view(), name="index"),
]
Replace the TEMPLATES
variable in mysite/settings.py
with the following:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / "myapp" / "templates"],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Add the STATICFILES_DIRS
variable in mysite/settings.py
:
STATICFILES_DIRS = [
BASE_DIR / "static",
]
Replace the Β mysite/urls.py
file with:
from django.contrib import admin
from django.urls import include, path
from myapp.urls import urlpatterns as myapp_urls
urlpatterns = [
path('admin/', admin.site.urls),
path('', include(myapp_urls)),
]
Create two folders:Β static
and frontend
. These folders will contain static files, like the bundled files from the frontend.
mkdir -p static/frontend
Flask
If you wish to use Flask, you need to install it.
However, first, we need to create the root folder for the Flask files and the requirements.txt
file. I assume you are in the myproject
working directory.
mkdir flask_back && cd flask_back && touch requirements.txt
Add this to the requirements.txt
file (update the version to the latest):
Flask==3.1.0
and install it:
pip install -r requirements.txt
Since we will have bundlers from the frontend, we need to create the templates
directory for our HTTP responses. This directory will hold all the HTML files.
mkdir templates
Furthermore, we are generating the index.html
and base.html
files.
touch templates/index.html && touch templates/base.html
Insert this into base.html
file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Flask App{% endblock %}</title>
{% block link_head %}{% endblock %}
</head>
<body>
<header>
<h1>Welcome to My Flask App</h1>
</header>
<main>
{% block content %}
<p>This is the default content.</p>
{% endblock %}
</main>
<footer>
<p>© 2025 My Flask App</p>
</footer>
{% block script_body %}{% endblock %}
</body>
</html>
and this into the index.html
file:
{% extends "base.html" %}
{% block link_head %}
<link rel="stylesheet" href={{url_for('static', filename='frontend/css/app.css')}}>
{% endblock %}
{% block content %}
<p>Hello, World!</p>
<p>This is the index page of my Flask app.</p>
{% endblock %}
{% block script_body %}
<script src={{url_for('static', filename='frontend/js/bundle.js')}}></script>
{% endblock %}
We need to create the Flask entry point file. Let's call it app.py
:
touch app.py
Add this to app.py
file:
from flask import Flask
from flask import render_template
app = Flask(__name__)
@app.route("/")
def hello_world():
return render_template("index.html")
Create a directory named frontend
inside the static
directory. This directory will contain all the files from the frontend.
mkdir -p static/frontend
Β
Bridging
Rollup Settings
As the final step, we need to ensure that the result of compilation and bundling of files are redirected to the Python framework's sub-directory. This can be achieved by setting up the Rollup configuration file.
Navigate back to the working directory myproject/myfrontend
and update the rollup.config.js
file with the following content:
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import typescript from '@rollup/plugin-typescript';
import replace from '@rollup/plugin-replace';
import terser from '@rollup/plugin-terser';
import resolve from '@rollup/plugin-node-resolve';
import svelte from 'rollup-plugin-svelte';
// the variable `production` is set to true if the environment variable `ROLLUP_WATCH` is not defined
const production = !process.env.ROLLUP_WATCH;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const FLASK_DIR = path.join(path.resolve(__dirname, '../flask_back'), 'static', 'frontend', 'js');
const DJANGO_DIR = path.join(path.resolve(__dirname, '../django_back'),'static', 'frontend', 'js');
console.log('FLASK_DIR', FLASK_DIR);
console.log('DJANGO_DIR', DJANGO_DIR);
// the variable DIR_TO_BACKEND indicates the path for the bundle files to be completed to
const DIR_TO_BACKEND = process.env.BACKEND === 'flask' ? FLASK_DIR : DJANGO_DIR;
console.log('DIR_TO_BACKEND', DIR_TO_BACKEND);
/*
* This function is used to clean the destination directories. Is defined as a plugin in the rollup config
* @param {string} dirs - The directories to clean
*/
function cleanDirs(dirs) {
return {
name: 'clean-dirs',
buildStart() {
const cleanDir = (dirPath) => {
if (fs.existsSync(dirPath)) {
fs.readdirSync(dirPath).forEach((file) => {
const curPath = path.join(dirPath, file);
if (fs.lstatSync(curPath).isDirectory()) {
cleanDir(curPath);
} else {
fs.unlinkSync(curPath);
}
});
}
};
// Clean the directories
dirs.forEach((dir) => {
if (fs.existsSync(dir)) {
cleanDir(dir);
} else {
fs.mkdirSync(dir, {recursive: true});
}
});
}
}
}
/*
* This function is used to create plugins for rollup
* @return {Array} - The plugins to be used in the rollup config
*/
function createPlugins() {
return [
replace({
preventAssignment: true,
__IS_PROD: JSON.stringify(production),
}),
typescript({
sourceMap: !production,
inlineSources: !production,
}),
svelte({
compilerOptions: {
dev: !production,
},
emitCss: true
}),
resolve({
browser: true,
dedupe: ['svelte'],
exportConditions: ['svelte', 'browser'],
}),
production && terser()
]
}
export default [
{
input: "src/main.ts",
output: {
sourcemap: !production,
format: 'iife',
name: 'app',
dir: DIR_TO_BACKEND,
entryFileNames: 'bundle.js',
chunkFileNames: '[name].js',
assetFileNames: '[name][extname]',
},
external: [/\.css$/],
plugins: [
cleanDirs([DIR_TO_BACKEND]),
...createPlugins(),
],
onwarn(warning, handler) {
// Ignore circular dependency warnings from svelte internals
if (warning.code === 'CIRCULAR_DEPENDENCY') {
// Check if the warning involves paths inside node_modules/svelte
const involvesSvelteInternal = warning.ids?.some(id => id.includes('node_modules/svelte/'));
if (involvesSvelteInternal) {
return; // Suppress the warning
}
}
// Let Rollup handle other warnings normally
handler(warning);
},
watch: {
clearScreen: false,
include: 'src/**',
},
}
]
Package.json Settings
To bring everything together, we need to define script entry points inside the package.json
file to enable commands like npm run dev
or npm run prod
.
However, first, we need to install the concurrently
npm package to keep both Rollup and Tailwind running simultaneously:
npm install concurrently --save-dev
At this point, the package.json
file should look like this (packages version might differ):
{
"name": "myfrontend",
"version": "1.0.0",
"main": "src/main.ts",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-replace": "^6.0.2",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.2",
"concurrently": "^9.1.2",
"rollup": "^4.40.0",
"rollup-plugin-svelte": "^7.2.2",
"svelte-preprocess": "^6.0.3",
"typescript": "^5.8.3"
},
"dependencies": {
"@tailwindcss/cli": "^4.1.3",
"svelte": "^5.26.3",
"tailwindcss": "^4.1.3"
}
}
Django
Change the scripts
property to:
"scripts": {
"tailwind:dev-django": "npx tailwindcss -i ./src/app.css -o ../django_back/static/frontend/css/app.css --watch",
"tailwind:prod-django": "npx tailwindcss -i ./src/app.css -o ../django_back/static/frontend/css/app.css --minify",
"rollup:dev": "rollup -c -w",
"rollup:prod": "rollup -c",
"dev-django": "concurrently \"BACKEND=django npm run rollup:dev\" \"npm run tailwind:dev-django\"",
"prod-django": "concurrently \"BACKEND=django npm run rollup:prod\" \"npm run tailwind:prod-django\""
},
Run the development Django command:
npm run dev-django
Run the production Django command:Β
npm run prod-django
In a new terminal (with virtual env activated), inside the Django root directory, execute:
python manage.py runserver 8090
Flask
Change the scripts
property to this for Flask:
"scripts": {
"tailwind:dev-flask": "npx tailwindcss -i ./src/app.css -o ../flask_back/static/frontend/css/app.css --watch",
"tailwind:prod-flask": "npx tailwindcss -i ./src/app.css -o ../flask_back/static/frontend/css/app.css --minify",
"rollup:dev": "rollup -c -w",
"rollup:prod": "rollup -c",
"dev-flask": "concurrently \"BACKEND=flask npm run rollup:dev\" \"npm run tailwind:dev-flask\"",
"prod-flask": "concurrently \"BACKEND=flask npm run rollup:prod\" \"npm run tailwind:prod-flask\""
},
Run the development Flask command:
npm run dev-flask
Run the production Flask command:
npm run prod-flask
In a new terminal (with virtual env activated), inside the Flask root directory, execute:
flask run --debug
Final Project Structure
myproject/
βββ django_back/ # Django backend files
β βββ manage.py
β βββ mysite/ # Django project settings
β βββ myapp/ # Django app
β βββ static/ # Static files for Django (including frontend build)
β βββ templates/ # Django templates
β βββ requirements.txt
βββ flask_back/ # Flask backend files
β βββ app.py
β βββ static/ # Static files for Flask (including frontend build)
β βββ templates/ # Flask templates
β βββ requirements.txt
βββ myfrontend/ # Frontend source files
β βββ node_modules/
β βββ src/
β β βββ App.svelte
β β βββ app.css
β β βββ main.ts
β βββ package.json
β βββ package-lock.json
β βββ rollup.config.js
β βββ svelte.config.js
β βββ tsconfig.json
βββ README.md
GitHub
You can find the complete project in my GitHub repository.
Conclusion
And there you have it! A whirlwind tour of setting up a frontend with TypeScript, Svelte 5, Tailwind 4, and Rollup, all ready to play nice with either a Django or Flask backend.
We went through initializing the project, installing all the necessary bits and pieces for the frontend, setting up TypeScript, configuring Rollup with its plugins, getting Tailwind CLI ready, and installing Svelte. Then, we jumped over to the backend, setting up a Conda environment and creating either a Django or Flask project, complete with templates and static file handling.
Finally, we bridged the gap by configuring Rollup to output the bundled frontend assets directly into the backend's static folders and set up npm scripts to make development and building a bit smoother.
Hopefully, this guide helps you get your project off the ground if you ever find yourself needing this particular combination of technologies. Happy coding!
Credits
Main photo: unsplash
Supporting
Support my work by renting space on my website: The Link