-
Notifications
You must be signed in to change notification settings - Fork 13
Clientside Tooling
Everything that builds support-frontend is kicked off via npm/yarn scripts, which are defined in package.json
.
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
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.
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.
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.
-
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!).
-
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.
-
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.
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.
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.
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 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"
}
}
- Redux Glossary
- Why Redux Toolkit?
- Writing state slices with Redux Toolkit
- Handling action side effects in Redux
- Presentational and Container Components
- Scoped actions and reducers
- Server Side Rendering
- Form validation
- CI build process
- Post deployment testing
- Post deployment test runbook
- TIP Real User Testing
- Code testing and validation
- Visual testing
- Testing Apple Pay locally
- Test Users
- Deploying to CODE
- Automated IT tests
- Deploying Fastly VCL Snippets
- Archived Components
- Authentication
- Switchboard
- How to make a fake contribution
- The epic and banner
- Environments
- Tech stack
- Supported browsers
- Contributions Internationalisation
- Payment method internationalisation in Guardian Weekly
- Print fulfilment/delivery
- Updating the acquisitions model
- Runscope testing
- Scala Steward for dependency management
- Alarm Investigations
- Ticker data
- Ophan
- Quantum Metric
- [Google Tag Manager] (https://github.com/guardian/support-frontend/wiki/Google-Tag-Manager)