© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
V. GagliardiDecoupled Django https://doi.org/10.1007/978-1-4842-7144-5_2

2. JavaScript Meets Django

Valentino Gagliardi1  
(1)
Colle di Val D’Elsa, Italy
 
This chapter covers:
  • How JavaScript fits into Django

  • JavaScript ecosystem and tooling

  • JavaScript frontend libraries and frameworks

Despite its reputation of being a toy language, JavaScript has become a mature tool in recent years.

For bad or good, JavaScript is everywhere these days. JavaScript tooling has grown exponentially as well, with new libraries and techniques pouring fast in the ecosystem. In this chapter, we introduce the modern JavaScript scene. You will understand what JavaScript tools do, and how they fit within Django. We also take a look at the most popular frontend libraries and frameworks.

JavaScript and Django in Production

To better understand how modern JavaScript fits into Django, we should think not only of a local development environment, but first and foremost of the typical production context.

Django in production is a different beast from its development counterpart. First, Django in development has a local server that can serve static files, that is, JavaScript, images, and CSS. However, the same development server can’t handle production loads, let alone the security around a production setup. For this reason, in a production environment, we usually employ the following components:
  • The Django project itself

  • A reverse proxy, such as NGINX, to serve static files, which also acts as an SSL termination

  • A WSGI or ASGI server, such as Gunicorn, Uvicorn, or Hypercorn (more on ASGI in the next chapter)

How does JavaScript fit into this? When we deploy Django on a production server, we run python manage.py collectstatic to group static files for all the Django apps in a single place, identified by the STATIC_ROOT configuration variable. To put things in context, suppose we have a Django project with an app named quote and a JavaScript file in ~/repo-root/quote/static/quote/js/index.js. Assuming we configured STATIC_ROOT as follows, where /home/user/static/ is an existing folder on the production server:
STATIC_ROOT = "/home/user/static/"
When we run python manage.py collectstatic, static files land in /home/user/static/, ready to get picked up by any Django template that references the static template tag. For this to work, the STATIC_URL configuration must point to the URL used for serving static files. In our example, we imagine a subdomain named static.decoupled-django.com:
STATIC_URL = "https://static.decoupled-django.com/"
This URL is usually served by an NGINX virtual host, with a location block pointing at the value configured in Django’s STATIC_ROOT. Listing 2-1 illustrates how you would call a static file (JavaScript in this case) from a Django template.
{% load static %}
<!DOCTYPE html>
<html lang="en">
<body>
<h1>Hello Django!</h1>
<div id="root"></div>
</body>
<script src="{% static "quote/js/index.js" %}"></script>
</html>
Listing 2-1

A Django Template Referencing a Static File

In the actual HTML, the URL becomes:
<script src="https://static.decoupled-django.com/quote/js/index.js"></script>

This is the most simple situation, where we have one or more Django apps, each with its own JavaScript files. This approach works well for tiny apps, where the JavaScript code for the entry point of the application fits within the 200KB limit. By entry point in this context, we mean the first JavaScript file that the browser has to download to kickstart the whole application. Since this book is about “decoupled Django” we need to think about more complex setups where the JavaScript payload served to the user could go well over 200KB. Also, the bigger a JavaScript application grows, the more we need to structure our code in a modular fashion, which leads us to talk about JavaScript ES modules and module bundlers.

The Need for Module Bundlers

Up until 2015, JavaScript didn’t have a standard module system on the frontend. Whereas Node.js always had require() from the beginning, the situation was really scattered on the frontend with different and competing approaches like AMD modules, UMD, and CommonJS.

Finally, in 2015, ES modules landed in ECMAScript. ES modules offer a standard approach to code reuse in JavaScript, while also enabling powerful patterns like dynamic import for improving performances in bigger applications. Now, the problem for a typical frontend project is that ES modules are not the only asset available to the developer. There are images, style files such as CSS or SASS, and different types of JavaScript modules to include. Let’s also not forget that ES modules are a rather new artifact, and traditional module formats are still out there. A JavaScript project might use new libraries based on ES modules, but need as well to include code distributed as CommonJS. Moreover, ES modules are not supported by older browsers.

Another challenge for modern frontend developers lies in the size of a typical JavaScript application, especially when the project requires a lot of dependencies. To overcome these issues, specialized tools known as module bundlers saw the light. The goals of a module bundler are manifold. This tool can:
  • Assemble different types of JavaScript modules into the same application

  • Include different types of files and assets in a JavaScript project

  • Improve the performance of the application with a technique known as code splitting

In brief, module bundlers offer a unified interface for collecting all the dependencies of a frontend project, assembling them, and producing one or more JavaScript files called bundles, in addition to any other asset (CSS and images) for the final application. One of the most popular module bundlers these days is webpack, which is also used in the most important CLI tools for project scaffolding in the JavaScript land (create-react-app, Vue CLI). In the next section, we explore why webpack is important for the Django developer who needs to deal with a lot of JavaScript.

Webpack Fights Django (the Need for Code Splitting)

Code splitting in JavaScript refers to the ability to serve the minimum amount possible of JavaScript to the client, while loading all the rest on-demand.

An understanding of code splitting is not strictly necessary for the average Django developer, but Python teams approaching medium to bigger Django projects that require a lot of interactivity in the frontend must know about this concept. In the previous sections, I mentioned a theoretical limit of 200KB for the entry point of a JavaScript application. Near to this number, we risk offering a terrible navigation experience. JavaScript has a cost for any device, but the performance degradation becomes even more dramatic on low-end devices and slow networks (I suggest always keeping an eye on “The Cost of JavaScript” by Addy Osmani, link in the resources). For this reason, it’s of utmost importance to apply a series of techniques to the final artifact. One such technique is code minification , where the final JavaScript code is stripped out of comments, whitespace, and while functions, and variable names are mangled. This is a well-known optimization that almost any tool can accomplish. But a more powerful technique, exclusive of modern module bundlers, called code splitting, can shrink down the resulting JavaScript files even more. Code splitting in a JavaScript application applies at various levels:
  • At the route level

  • At the component level

  • On user interactions (dynamic import)

To some extent, CLI tools like Vue CLI and create-react-app already offer sane defaults out of the box when it comes to code splitting. In these tools, webpack is already configured to produce efficient output, thanks to a basic form of code splitting known as vendor splitting . The effect of code splitting on a JavaScript application is visible in the following example. This is the result of running npm run build on a minimal project configured as a single-page application:
js/chunk-vendors.468a5298.js
js/app.24e08b96.js
Subsequent slices of the application, called chunks , land in different files than the main entry point, and can be loaded in parallel. You can see here that we have two files, app.24e08b96.js and chunk-vendors.468a5298. The js. app.24e08b96.js file is the entry point of the application. When the application loads, the entry point requires the second chunk, named chunk-vendors.468a5298.js. When you see vendors in a chunk name, it’s a sign that webpack is doing the most basic form of code splitting: vendor splitting. Vendor dependencies are libraries like lodash and React, which are potentially included in multiple places across a project. To prevent dependency duplication, webpack can be instructed to recognize what is in common between the consumers of a dependency, and splits the common dependencies into single chunks. Another thing you can notice from these file names is the hash. In app.24e08b96.js, for example, the hash is 24e08b96, which is calculated from the file content by the module bundler. When the content of the file changes, the hash changes as well. The important thing to keep in mind is that the order in which the entry point and the chunks appear in the script tag is paramount for the app to work. Listing 2-2 shows how our files should appear in the HTML markup.
<-- rest of the document -->
<script src=/js/chunk-vendors.468a5298.js></script>
<script src=/js/app.24e08b96.js></script>
<-- rest of the document -->
Listing 2-2

Two Chunks As They Appear in the HTML

Here, chunk-vendors.468a5298.js must come before app.24e08b96.js, because chunk-vendors.468a5298.js contains one or more dependencies for the entry point. Keeping our focus on Django, you can imagine that to inject these chunks in the same exact order, we need some system for pairing the appearance order of each file with the static tag in our templates. A Django library called django-webpack-loader was meant to ease the usage of webpack within Django projects, but when webpack 4 came out with a new configuration for code splitting, splitChunks, django-webpack-loader stopped working.

The takeaway here is that JavaScript tooling moves faster than anything else, and it’s not easy for package maintainers to keep up with the latest changes. Also, messing up with the webpack configuration is a luxury not everybody can afford, not counting the risk of configuration drift and breaking changes. When in doubt, before fighting webpack or touching its configuration, use this mini-heuristic to decide what to do: if the JavaScript section of an app is over 200KB, use the appropriate CLI tooling and serve the application as a single-page app within a Django template, or as a decoupled SPA. We will explore the first approach in Chapter 5. If the JavaScript code fits within the 200KB limit instead, and the amount of interactive interactions are low, use a simple <script> tag to load what you need, or if you want to use modern JavaScript, configure a simple webpack pipeline with vendor splitting at least. Having outlined the foundations of module bundlers, let’s now continue our tour of the modern JavaScript tooling.

Note

JavaScript tooling, and webpack in particular, are too much of a moving target to cover in a book without the risk of providing outdated instructions. For this reason, I don't cover the setup of a webpack project here. You can find a link to an example of such a setup in the resources.

Modern JavaScript, Babel, and Webpack

As developers we are quite fortunate, because most of the time we have access to fast Internet connections, powerful machines with many cores, plenty of RAM, and modern browsers.

If this shiny new snippet of JavaScript works on my machine, then it should work virtually everywhere, right? It’s easy to understand the appeal of writing modern JavaScript. Consider the following example, based on ECMAScript 5:
var arr = ["a", "b"];
function includes(arr, element) {
  return arr.indexOf(element) !== -1;
}
This function checks if a given element is present in an array. It is based on Array.prototype.indexOf(), a built-in function for arrays, which returns -1 if the given element is not found in the target list. Now consider instead the following snippet, based on ECMAScript 2016:
const arr = ["a", "b"];
const result = arr.includes("c");

The second example is clearly more concise, understandable, and palatable for developers. The drawback is that older browsers don’t understand Array.prototype.includes() or const. We can’t ship this code as it is.

Tip

Both caniuse.com and the compatibility tables at developer.mozilla.org are invaluable resources for understanding if a given target browser supports modern syntax.

Luckily, fewer and fewer developers need to worry about the dreaded Internet Explorer 11, but there are still a lot of edge cases to take into account. As of today, the most compatible JavaScript version is ECMAScript 2009 (ES5), which is a safe target. To keep both JavaScript developers and users happy, the community came up with a category of tools called transpilers, of which Babel is the most popular incarnation. With such a tool at our disposal, we can write modern JavaScript code, pass it into a transpilation/compilation pipeline, and have compatible JavaScript code as the final product. In a typical setup, we configure a build pipeline where:
  1. 1.

    Webpack ingests ES modules written in modern JavaScript.

     
  2. 2.

    A webpack loader passes the code through Babel.

     
  3. 3.

    Babel transpiles the code.

     

The webpack/Babel duo is ubiquitous these days, used by create-react-app, Vue CLI, and more.

A Word on TypeScript

TypeScript is the elephant in the room for most developers.

As a statically typed declination of JavaScript, TypeScript is more similar to languages like C# or Java. It is widespread in the Angular world, and it is conquering more and more JavaScript libraries, which now ship with type definitions by default. Whether you like TypeScript or not, it is a tool to keep in consideration. In Chapters 8, 11, and 12, we work with TypeScript in React.

JavaScript Frontend Libraries and Frameworks

The JavaScript landscape has changed dramatically over the years. jQuery still owns a large market share.

But when it comes to client-side applications, these are being written or rewritten with modern frontend libraries like React and Vue.js, or full-fledged frameworks like Angular. Django is mostly powered by HTML templates, but when the time comes it can be paired with virtually any JavaScript library. These days the scene is dominated by three competitors:
  • React, the UI library from Facebook, which popularized (but not pioneered) a component-based approach to writing interfaces

  • Vue.js, the progressive UI library from Evan You, former Angular developer, which shines for its progressiveness

  • Angular, the battery-included framework, based on TypeScript

Of this trio, Vue.js is the most progressive. Angular has more batteries included (just like Django), but has a steep learning curve. React instead is the most liberal because it does not impose any constraint on the developer. You pick whatever library you need. Whether this is an advantage or not, I leave the opinion for you. What is important to keep in mind is that the core UI library is just the starting point for a number of dependencies to solve another set of problems that arise when writing medium to bigger client-side applications. In particular, you will sooner or later need:
  • A state management library

  • A routing library

  • A schema validation library

  • A form validation library

Each UI library has its own orbit of satellite sub-libraries to handle the aforementioned concerns. React leans on Redux or Mobx (and more recently also on Recoil.js) for state management, and on React Router for routing. Vue.js uses Vuex for state management, and Vue Router for routing. Angular has a bunch of different approaches to state management, but NgRx is the most widespread. Ultimately, all these libraries and frameworks can work well as external clients for Django, paired either as:
  • Client-side applications fetching data from a Django REST/GraphQL API

  • Server-side rendered or static site generators with Django as a content source

We explore both topics in more detail later in the book. In the next section, we take a quick look at some alternatives to the traditional single-page approach.

Lightweight JavaScript UI Libraries

Other than Angular, Vue, React, and Svelte, there is a growing number of lightweight JavaScript mini-frameworks, born to ease the most mundane tasks on the frontend and to provide just enough JavaScript to get going.

In this category we can mention the following tools:
  • AlpineJS

  • Hotwire

  • Htmx

Hotwire is a set of tooling and techniques popularized by Ruby on Rails and its creator, David Heinemeier Hansson. At the time of this writing, there is experimental work called turbo-django aiming at porting these techniques into Django. Along the same lines there is also a new Django framework called django-unicorn . All these tools offer a less JavaScript-heavy approach to building interactive interfaces. They will be worth a look once they start to gain traction in the wild.

Universal JavaScript Applications

Node.js is an environment for running JavaScript code outside of browsers. This means servers and CLI tools.

The majority of the tools we mentioned in this chapter are JavaScript-based, and since they run on the command line, they need a JavaScript environment, which Node.js provides. Now, if you pair this with frontend libraries that are capable of running on any JavaScript environment, not only browsers (like React and Vue.js), you obtain a particular breed of JavaScript tooling that is taking the scene by storm with a JavaScript-centric approach to server-side rendering.

We already mentioned server-side rendering in Chapter 1, when talking about MVC web frameworks. In contrast to traditional server-side rendering, where the HTML and the data is generated by a server-side language like Ruby, Python, or Java, in the JavaScript-centric approach to server-side rendering, everything is generated by JavaScript on Node.js. What does this mean for the end user and for the developer? The main difference between a client-side application based on JavaScript and a server-side rendered app is that the latter generates the HTML before sending it to the user, or to the search engine crawler. This approach has a number of advantages over pure client-side applications:
  • It improves SEO for content-heavy websites

  • It improves performances, since the main rendering effort is pushed to the server

For the developer instead, universal JavaScript applications are the holy grail of code reuse since everything can be written in a single language, JavaScript. The reasoning behind these tools and the motivations for using them are roughly the following:
  • We already have a big client-side app and we want to improve its performances, both for the end user and for crawlers

  • We have a lot of common code between the frontend and the Node.js backend, and we want to reuse it

However, as with any technology, universal JavaScript applications have their own drawbacks. For a Django shop focused on Python and maybe a sprinkle of JavaScript with React or Vue.js, maintaining a parallel architecture based on Node.js can be taxing. These setups need a Node.js server in order to run, with all the maintenance burden and complexity it entails. Platforms like Vercel and Netlifly ease the deployments of these architectures, but there are still things to keep in mind. The most popular tools available today for creating universal JavaScript applications are:
  • Next.js for React

  • Nuxt.js for Vue.js

  • Angular Universal for Angular

There are probably a million more tools out there. In Chapter 7, we focus on Next.js.

Static Site Generators

While the approach to server-side rendering offered by tools like Next.js and Nuxt.js is indeed interesting, static site generation should be the first choice in all those cases where search engine optimization is paramount and there is little to no JavaScript-driven interaction on certain pages (think of a blog, for example).

The current scenario for static site generation with JavaScript includes:
  • Gatsby

  • Next.js for React

  • Nuxt.js for Vue.js

  • Scully for Angular

Next.js and Nuxt.js can work in two modes: server-side rendering and static site generation. To source data from the backend, these tools offer interfaces for making plain HTTP requests to a REST API, or alternatively GraphQL. Gatsby instead makes exclusive use of GraphQL, and might not be the right tool for every team.

Testing Tooling

The whole Chapter 8 is devoted to testing Django and JavaScript applications. In this section, we briefly introduce the most popular testing tools for JavaScript. They fall into the conventional categorizations of testing.

Unit testing:
  • Jest

End-to-end testing:
  • Cypress

  • Puppeteer by Google

  • Playwright by Microsoft

For unit testing and integration testing between multiple units, Jest is the most popular tool to this date. It can test pure JavaScript code and React/Vue.js components as well. For end-to-end testing and functional testing, Cypress is the most feature-complete test runner, and plays well with Django too, with Puppeteer and Playwright gaining traction. Truth be told, Jest and Cypress can be thought more as wrappers around existing testing libraries: Jest builds on top of Jasmine, while Cypress builds on top of Mocha, as they borrow a high number of methods from these libraries. However, their popularity is sparked by the fluent testing API they provide, in contrast to more traditional tools.

Other Ancillary JavaScript Tools

I would be remiss not to mention ancillary JavaScript tools , so important for the modern JavaScript developer.

In both the Python and JavaScript land, there are code linters. For JavaScript, ESLint is the most widespread. Then we have code formatters like Prettier. At the intersection between pure JavaScript code and design systems we find Storybook, a powerful tool for building design systems. Storybook is used widely in the React and React Native community, but compatible with the most popular frontend libraries like Vue and Svelte. Together with testing tools, linters, formatters, and UI tools make a powerful arsenal for every JavaScript and Django developer.

Summary

This chapter explored the boundaries of Django and client-side applications. You learned about:
  • JavaScript and Django in production

  • Module bundlers and code splitting

  • How webpack integrates into Django

  • JavaScript tooling as a whole

  • Universal JavaScript applications

In the next chapter, we introduce the asynchronous Django landscape.

Additional Resources

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.86.235.207