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