Django project structure for modern frontend ES6 module Javascript / Typescript

I’m doing some frontend modernization on a Django project and am looking for feedback on the following:

  • Have you converted a traditional Django web application with page-level <script> includes of JavaScript to module-based js built with the aid of a toolchain? Please share any details you care to.
  • Have you used esbuild or webpack or parcel to accomplish the things I’m describing here? What seems to be missing in the second tree I’ve proposed?
  • Are you aware of any resources or best practices outlining the problem I’m describing and methodologies? (I just saw @michael-yin 's post on Django and Webpack) Perhaps there are more?
  • Any suggestions or feedback on the plan to put Typescript source in app-level subfolder app_name/frontend/src?
  • Is anyone purposefully avoided (or still avoiding) modernizing their javascript / frontend because it adds a build process and project structure changes like this?

Background

I’m adapting a Django project that previously has had vanilla ES6 javascript included at page-level blocks using the tag. This JavaScript-enhanced page UI is delivered using Django’s normal templates and views.

I’m moving the project to use npm / yarn to manage dependencies and converting the JavaScript to strict Typescript. I’m also adding a development and CI toolchain using esbuild to properly bundle my code with tree-shook dependencies to deliver minified the JavaScript source.

I previously kept js below the /assets/ folder, which was my sole STATICFILES_DIRS. Here’s an abridged tree of that with the js folder expanded

assets
├── css
├── files
├── fonts
├── img
├── js
│    ├── custom
│    │    ├── app
│    │    │    ├── accountsettings
│    │    │    ├── articles
│    │    │    └── common
│    │    ├── mp
│    │    │    └── utils.js
│    │    └── utils
│    │        ├── ajax-utils.js
│    │        ├── auth-utils.js
│    │        └── string-utils.js
│    └── template
│        ├── app.min.js
│        └── default.js
├── plugins
│    ├── cropper
│    │    ├── cropper.min.css
│    │    └── cropper.min.js
│    └── dropzone
│         ├── dropzone.min.css
│         └── dropzone.min.js
└── webfonts

The custom/app folder contains the app names which contain app-specific JavaScript.

Loading of the js in the template was old school with no module imports.

For example, a profile photo editing template had a template block Which would all be included using {% block pagejs %}...{% endblock pagejs %} with loose JavaScript files like:

// External libraries
<script src="{% static 'plugins/cropper/cropper.min.js' %}"></script>
<script src="{% static 'plugins/dropzone/dropzone.min.js' %}"></script>
// js used between different apps
<script src="{% static 'js/custom/app/common/authUtils.js' %}"></script>
// Page-specific JS
<script src="{% static 'js/custom/app/accountsettings/profile-image.js' %}"></script>

My plan is to move app-specific js into a frontend folder in each application, and the common js into a core application that I use for project-wide code.

Then, when my local esbuild watch notices a change in any of the applications’ FE code, it rebuilds the Typescript code and then runs a bash script to move it to the appropriate a project_name/app_name/static folder which Django picks up during the collect static phase of CI.

I’m including a proposed / in process updated tree for this below, including the common templates folder, which followed the same pattern above of using app-name-specific subfolders.

Bundling in development and deployment

For local development, here’s a gist of the esbuild bundler watch I made: An example javascript api-based `esbuild` build script with a watch and automatic post-build bash script execution. · GitHub to behave like Django’s watchdog. Note, the postbuild.sh file simply moves the js bundle to the appropriate app folder.

For deployment, I run yarn run build, and use a postbuild script to run the same bash file.

  "scripts": {
    "build": "esbuild ./articles/frontend/src/articles.ts --bundle --target=es2017 --minify --outfile=./articles/frontend/build/articles-fe.js",
    "postbuild": "./postbuild.sh"
  }

django_project_name

├── .github
├── Dockerfile
├── README.md
├── accountsettings
│    ├── apps.py
│    ├── forms.py
│    ├── frontend
│    │    ├── build
│    │    └── src
│    │        ├── fetchHooks.js
│    │        ├── profile-image.js
│    │        ├── profile.js
│    │        └── routes.js
│    ├── migrations
│    ├── tests
│    ├── urls.py
│    ├── utils.py
│    └── views.py
├── articles
│    ├── ...
│    ├── frontend
│    │    ├── build   
│    │    └── src
│    │        ├── fetchHooks.ts
│    │        └── routes.ts
│    └── ...
├── build.js
├── core
│    ├── ...
│    ├── frontend
│    │    ├── build   
|    |    └── src
│    │        ├── apiConfig.js
│    │        ├── authUtils.js
│    │        └── fetchUtils.js
│    └── ...
├── entrypoint.sh
├── eslintrc.js
├── manage.py
├── node_modules
├── package.json
├── postbuild.sh
├── requirements.txt
├── templates
│    ├── 403.html
│    ├── ...
│    ├── accountsettings
│    ├── base
│    │    ├── base__.html
│    │    ├── general_layout_.html
│    │    ├── includes
│    │    │    ├── _header.html
│    │    │    ├── _analytics.html
│    │    │    └── _sidebar.html
│    │    └── login_layout_.html
│    └── core
│         ├── admin
│         │    ├── change_password.html
│         │    ├── create_user.html
│         │    ├── dashboard.html
│         │    └── manage_users.html
│         ├── home
│         │    └── index.html
│         └── public_content
│             ├── landing.html
│             ├── privacy.html
│             └── terms.html
├── tsconfig.json
└── yarn.lock

Heads up, that I cross-posted this to /r/django and got some helpful feedback on simplifying to a single project_dir/frontend/ directory that mirrors the project_dir/templates/directory (that also uses app-named subfolders) already in use.

The recommendation was also to use ManifestStaticFileStorage. to take advantage of Django’s manifest building capability.

Read their reply in full here.

You might find this guide helpful as one way of accomplishing what you’re trying to do: Integrating a Modern JavaScript Pipeline into a Django Application

As the reddit thread suggested, I recommend keeping your front end contained to a single folder, and compiling it into static bundle files that you can use.

The following diagram illustrates how you could set things up.

And here’s a reference project structure that I like to use:

I recommend keeping the bundles .gitignored and building them as part of your CI/CD workflow.

Hope that helps!

This is very helpful, thank you.

I’m curious, it looks you prepare your manifest / file hashing with webpack, rather Han leaning on Django’s ManifestStaticFileStorage. Is that right?

Did you consider this, or are there particular reasons you let webpack do this?

I’m planning to use esbuild, which has file name hashing also.