The build process

Overview

Files within the Tui framework have two main formats:

  1. Raw source files such as Vue components, Javascript files, SCSS and tests. These are development files and typically are not served to the web browser (there may be exceptions, such as static images).
  2. Built files, which have been modified during a build process to produce optimised files intended for serving to and caching within the web browser.

Moving from the first, raw state into the second, built state requires a build process to be run using the Command Line Interface (CLI). When the build process is run, source files are modified for example by transpiling Javascript code into a format suitable for legacy web browsers, or compiling SCSS into CSS, or compressing files. Modified files are then output, ready to be served to and cached within the web browser.

Tui relies upon a build process that uses tools such as Webpack to produce multiple compiled versions of source modules. The build files are committed to Git at release time. Totara Core mediates the files produced by the build, it does not use the src files.

Implementation

Tui uses node modules for its build, and relies upon npm to manage this.

Installing node and npm

The first step is to install npm if you don't already have this. There are many ways in which to achieve this, but we suggest you start by reading the official documentation on how to get npm.

If you are using an operating system with its own package manager, or have installed a package manager then you may prefer to get node and npm via your package manager.

Installing the required node modules

Before anything can be run you first need to install the required dependencies. The following command should be run within the top-level directory of the Totara checkout.

npm ci

It is highly recommended to run npm ci which performs a clean node_modules install after you've brought in new release changes for the Totara codebase, or regularly switch Git branches with different release versions. This is because potential changes to /package.json may require dependency changes.

The two types of builds

There are two distinct types of build that Tui can produce:

  • Production
  • Development

Prior to a release both the production and development builds are produced and both are committed.

The difference between the two is that the production builds are minified and contain only the required code to ensure the smallest possible package required for the client, whereas the development builds contain additional information such as debugging code, warnings, and source maps in order to more easily facilitate development.

The production builds are used by default. If you want to use the development builds you need to put Tui into development mode. See Development setup for more information on how to do that.

When developing for Tui you probably want to have Tui set up in development mode so that you can debug your client-side code. If this is the case then you only need to produce the development build during this period. The production build files will only be required when testing a production environment, and when deploying your code to a production site.

Generating production builds

The following command will produce the production builds files.

npm run tui-build-prod

Generate development builds

The following command will produce the development build files.

npm run tui-build-dev

There is an additional command as well that when run continues to run, and when you save changes to a Tui component src file will cause the development build files to be produced for just that component. This tends to be much quicker than producing development build files for all components, and will enable more efficient development.

npm run tui-build-watch

Docker

If you are using Docker with mutagen (such as with the standard totara-docker-dev setup on macOS), you must run the npm run tui-* commands inside Docker, otherwise development builds may not be loaded correctly after switching branches. You can do this simply by adding 't' in front of the npm command.

Bundling files with Webpack

There are several essential libraries, including the Vue.js framework that are required by Tui in order to operate. Rather than statically require these libraries we have opted to roll them into a vendor bundle directly from npm dependencies using Webpack.

The dependencies defined in the top-level packages.json file are separated such that production dependencies are rolled into the vendor bundle, while development dependencies are required to support the pre-production build process and tooling requirements.

Because these libraries are rolled into the vendor bundle during the build process, and included in the distributed build files there is no need for sites to install npm nor our dependencies on any production site.

It is important to note that only the 'tui' Tui component may import libraries into a vendor bundle like this. It is not extensible, nor pluggable. Any components wishing to use libraries should import them directly into their own source directory, and expose them as modules.

Overview

We have opted to wrap webpack in a custom build runner that runs a separate webpack build for each Tui component. This enforces separation between Tui component builds, allows for custom options, and allows us to change the exact details of how builds are run without it being a breaking change for tooling.

Loaders

We use the following standard loaders to process code:

  • Vue files are processed by vue-loader to separate vue files into their constituent parts
  • mini-css-extract-plugin is used to extract CSS from components and static.scss into a bundle file
  • postcss-loader + autoprefixer adds vendor prefixes to generated CSS

Plus several custom loaders to enable things like the cross-bundle module loading and Vue <lang-strings> block. Some notable ones:

  • graphql_loader.js parses GraphQL files to an AST suitable for passing to Apollo Client
  • icons_svg_loader.js converts SVG icons to JSON for consumption

You can reference client/tooling/configs/webpack.config.js to see the full list.

Bundling detail

As Webpack does not support accessing modules from another build out of the box, which is a requirement for a system with a pluggable architecture like Totara, we have layered our own module resolution on top of Webpack's. This custom module system works by registering all available modules from a Tui component in a global registry when that bundle is loaded on a page. Import statements, instead of being resolved at build time by Webpack, are instead transformed to look up the requested module in the global registry.

One bundle is generated per Tui component, with variants for production and development, and legacy and modern (four variants total).

Customising the build

It is possible to customise the Webpack build for your Tui component, add or remove loaders, or even add completely custom Webpack builds.

To do so, create a build.config.js inside the src directory for your component. See client/component/samples/src/build.config.js for an example.

Tips and known limitations

  • When developing for Tui you need to build after making any changes to your Tui componentry.

  • Modules from node_modules cannot be directly imported outside of the Tui core. This is an intentional limitation to ensure plugins can be distributed cleanly.
  • Files inside an 'internal' directory (anywhere in the path) are not exposed to other Tui components. This is a good place to put internal implementation code or third-party dependencies.

Recommended reading