Skip to content

Clientside Tooling

Imogen Hardy edited this page Dec 20, 2023 · 3 revisions

Contents

Everything that builds support-frontend is kicked off via npm/yarn scripts, which are defined in package.json.

Webpack

At its core, webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it recursively builds a dependency graph that includes every module your application needs, then packages all of those modules into one or more bundles.1

We use webpack to build all of our client-side assets. It takes inputs like the following:

  • JS/TS source files
  • .tsx React component files
  • SCSS

and outputs web-ready assets:

  • Bundled and minified JavaScript
  • CSS

The Four Components of Webpack

There are four steps involved in the Webpack build pipeline.

Entry - The starting point for a collection of assets, for example a main.js file that imports other modules. There can be more than one entry point for different collections of assets. Relevant docs.

Loaders - Transform a wide range of inputs into modules that webpack can work with. Relevant docs.

Plugins - Perform any other processing you want to apply to your assets, minification for example. Relevant docs.

Output - The final, combined bundle or bundles that are sent to the browser. Relevant docs.

The Config File

Webpack configuration is typically managed via a single file, but due to every page on the support site being in effect its own self-contained React app we use a slightly more complex setup.

Our various entrypoints are found in webpack.entryPoints.js, and loaded via webpack.common.js. The common config itself is consumed by webpack.dev.js and webpack.prod.js to serve our different needs for bundling in the two environments.

There is also a simplified Webpack setup for server-side rendering, via webpack.ssr.js.

Despite the complexity in this specific project, the fundamentals of a Webpack config file remain the same. It should export a JavaScript object containing your chosen options, and typically will include the following sections:

module.exports = {
  entry: ...,
  output: ...,
  module: ...,
  plugins: ...,
};

entry - contains one or more input files.

output - defines a single output file, or contains options for building multiple outputs.

module - includes loader configuration and other options for handling different types of input file.

plugins - is an array of plugins.

There may also be a number of other properties in this object, including, for example, options to configure webpack-dev-server.

WebpackDevServer

We use webpack-dev-server for development in support-frontend; it speeds up development by watching for source changes and automatically recompiling any affected assets. We have it set up as a proxy that sits in front of the Play app server. As a result, there are typically three types of events that you will find the dev server handling in our codebase.

  1. Source File Changes

    Whenever a file changes on disk, webpack-dev-server will recompile any relevant assets. It will then push the updated assets directly to the browser, which then automatically reloads the page (you don't have to manually refresh!).

  2. Asset Request

    Whenever a page is loaded in the browser, a bunch of requests for assets are made. Rather than sending these to the Play app server, it handles these requests itself, as the assets themselves are managed by Webpack. Any compiled assets are kept in memory, so while the dev server is running you won't see them on the disk.

  3. Other Requests

    Whenever a request is made that the dev server doesn't know how to handle it passes it on to the Play server. This is usually what happens when, for example, form POSTs are made.

Example: Webpack Config for a Checkout Page

const webpack = require('webpack');

module.exports = {

  entry: './assets/checkoutPage.js',
  output: {
    path: './dist',
    filename: 'checkoutBundle.js',
  },
  module: {
    rules: [
      { test: /\.js$/, use: 'raw-loader' },
    ],
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
  ],

};

The module section contains a series of 'rules' for different types of input file. Each rule contains a test to see if the rule should be applied to a given type of file. In this example a regex is used to test to any file with the .js extension. For any such file the raw-loader is used, which simply takes the input file and converts it into a string. We've also import the webpack module here to get access to the built-in optimisation plugins, so that we can use UglifyJS. This plugin minifies the outputted bundle.

Babel

A JavaScript compiler.2

There are plenty of recent (and not-so-recent) additions to JavaScript that make it much more pleasant to write. Unfortunately, only up-to-date browsers support a lot of these features. Babel solves this problem by allowing you to write your source code using modern JavaScript features, and then having that transpile to older JavaScript supported by a wider range of browsers. It even allows you to specify the list of browsers that you want to support, so that it converts exactly what it needs to, no more, no less.

Plugins and Presets

The transformations that Babel performs are determined by the plugins that you apply, and a preset is a pre-defined configuration or collection of plugins. An important one is babel-preset-env, which can be used to transform modern JavaScript features for old browsers. You specify your environment, e.g. what browsers you want to support, and it will figure out the list of plugins needed to transform your code for that environment. Another preset that we use in this codebase is babel-preset-react, which includes the plugins required for transforming Flow and JSX.

Babel and Webpack

Babel can be used as part of a webpack build via the babel-loader, with configuration specified in a .babelrc file. An example webpack config that applies this loader can be seen here:

...
module: {
  rules: [
    {
      test: /\.(js|jsx)$/,
      exclude: /node_modules/,
      loader: 'babel-loader',
    },
  ],
}
...

Here the test specifies that the babel-loader is applied to all files with the .js and .jsx extensions, which means that all matching files will be run through babel. The corresponding babel configuration looks like this:

{
  "presets": ["react", "env"]
}

The packages needed to provide the loader and corresponding presets are then added to package.json:

{
  "dependencies": {
    "babel-loader": "^7.1.3",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.23.0"
  }
}

Footnotes

1 The Webpack Site 2 The Babel Site

πŸ™‹β€β™€οΈ General Information

🎨 Client-side 101

βš›οΈ React+Redux

πŸ’° Payment methods

πŸŽ› Deployment & Testing

πŸ“Š AB Testing

🚧 Helper Components

πŸ“š Other Reference

1️⃣ Quickstarts

πŸ›€οΈ Tracking

Clone this wiki locally