This documentation will cover both the building of the UI code, and also how it is built and integrated with Strimzi.
This UI is built using two tools, Babel and Webpack. Webpack acts as our main build and bundling tool, while Babel acts as a transpiler - meaning the UI codebase can make use of the latest and greatest ECMAscript syntax, and Babel will polyfill where appropriate to provide cross browser support. The below will detail choices we have made regarding how the build works, configuration and considerations to be aware of, and the end output. The aim of this stack is to have a fast and efficient build for day to day development, but also the smallest possible built bundles so users do not need to wait a long time for all required assets to be retrieved by their browser.
To maintain a small built bundle size, we take advantage of Webpack's treeshaking capabilities to only include what is used in the built output. This will require developers of the UI to be aware of the following two points:
- Where possible, use ECMAscript 6/ES2015 versions of 3rd party dependencies. For example, rather than using
lodash
, uselodash-es
. This is so the Webpack can detect the variousimport
andexport
of modules, and thus remove any which are never used. - Modules exported in this codebase are expected to be pure - ie no side effects while
import
ing orexport
ing the content of a module. As a result,sideEffects: false
is set in thepackage.json
, meaning Webpack when building in production mode can prune functions and whole modules if required (and thus side effects, if any, may not occur if that module is not required). If a module/file does have side effects, these can be included still, by adding them tosideEffects
as an array, i.e.sideEffects: ['path/to/module/with/sideEffect.js']
. This should however only be a last resort.
Webpack (and it's plugins) are highly customisable. This section will detail the choices we have made around how and what is built, where it is built to, and the roles and responsibilities of the various plugins which enable this.
The UI makes extensive use of Webpack's alias features. This is done for two reasons:
- It allows for abstraction at build time. By importing view layers via an alias the resolution of that alias can be changed at build time, enabling the ability to swap view layer implementations without needing to make changes in the production code. For instance:
- If a component has multiple view layer implementations:
...
View.carbon.js
View.patternfly.js
...
- And the component exports itself via the View alias:
export * from 'View'
- And the View alias is defined as follows:
...
View: `./View.${VL_SUFFIX}.js`
...
where VL_SUFFIX
is a suffix, in this case mapping to in this case either carbon
or patternfly
. At build time, one of the two layers is resolved, and thus available for use elsewhere.
- It makes importing code far cleaner and easier. For example, a traditional import of a module in another directory:
import { myFunction } from '../../Modules/MyModule';
with the following Webpack configuration:
...
alias: {
MyModule: path.resolve(__dirname, 'Modules/MyModule')
}
...
becomes
import { myFunction } from 'MyModule'
For convenience, all top level module directories from client
will be automatically aliased via a helper in the webpack configuration, as well defining dynamic view and styling aliases as described above, as well as an alias for model code.
In addition to aliasing, the webpack configuration will be as follows:
Option | Value | Purpose |
---|---|---|
entry | client/Bootstrap/index.js |
Build entry point. Note: the scss/css is expected to be imported by individual view layer code, which is imported (directly or indirectly) from this file. Details to follow in Code Style approach documentation regarding this styling approach and why. |
mode | production or development (provided via config file used) |
Build mode. If production, code will be minified and have developer helpers (warnings etc) removed from the built output |
output.path | dist (via constant) |
All output to be placed in the dist directory |
output.publicPath | `` (empty string) | The public path of static/public content included by Webpack. Is relative to the URL. |
output.filename | [name].bundle.js |
Name of the built entry bundle. Will be main.bundle.js once built as we have one entry point |
module.rules | Array of rules - see here for details | Rules/tests to run on modules being built. Depending on the file being parsed, different transformations should be run |
plugins | Array of plugins - see here for details | Additional tools to customise the build to produce the required output |
optimization.minimize | true if production build, else false |
Use Webpack minimization only when performing a production build |
optimization.minimizer | [TerserPlugin with this configuration, optimize-css-assets-webpack-plugin ] |
When a production build, minimise the built output using TerserPlugin and optimize-css-assets-webpack-plugin |
optimization.splitChunks.chunks | all |
When building, check and chunk up code as much as possible, regardless of what/how it is used |
optimization.splitChunks.name | false |
Recommended setting - keeps chunk names consistent |
resolve.alias | Array of aliases | See section above |
devServer | Object | See section below |
To keep the configuration as minimal and readable as possible, configuration will be defined in separate files per build mode - ie one for development
and one for production
. These configurations will extend a common configuration file. See this section for more details.
Webpack allows file specific loaders or utilities to be invoked as a part of the build process. In the case of this build, these are as follows:
Rule | Plugin/loader(s) | Purpose |
---|---|---|
/(\.css|.scss)$/ |
style-loader (dev only), miniCssExtractPlugin.loader (production only), css-loader , sass-loader |
Handle scss/css loading/references. If dev mode, use style-loader for speed, else use miniCssExtractPlugin.loader (in combination with the miniCssExtractPlugin plugin) to produce/emit css file(s) |
/(\.js)$/ |
babel-loader |
Perform babel transpile on all JS files. This will be configured with presets for recent browsers, and enable caching to improve build performance |
/\.(woff(2)?|ttf|eot)$/ |
file-loader |
For any font file, use file-loader to package the font to the output.path and replace/update any imports of those fonts to this location. These will be directed to a 'fonts' directory |
/\.(jpg|gif|png|svg)$/ |
file-loader |
For any image file, use file-loader to package the image to the output.path and replace/update any imports of those images to this location. These will be directed to a 'images' directory |
The module loading rules will be provided via helper functions from a common configuration file, but allow for modification. See this section for more details.
We also make use of the following plugins:
Plugin | Responsibility |
---|---|
TerserPlugin | When a production build, minimises the built JS output |
optimize-css-assets-webpack-plugin | Minimises/dedupes built css output |
html-webpack-plugin | Handles templating of built output into a provided index.html file |
mini-css-extract-plugin | Compresses and emits a css file(s) containing all styling for the UI |
compression-webpack-plugin | Compresses built output further using gzip algorithm, and emits these compressed files. Depending on headers provided by the browser, these gziped versions will be served to the browser, rather than the uncompressed versions |
webpack-bundle-analyzer | At build time, produce a report regarding the JS bundle size (useful for understanding bloat and duplication). At dev build time this is an html file (which is then hosted by webpack-dev-server ), and a json file at production build time. Each report is written to the generated/bundle-analyser directory |
BannerPlugin | Used to add a copyright header to built css code. Other types handled by other plugins (eg TerserPlugin) |
Where appropriate, plugins will be provided via helper functions from a common configuration file, but allow for modification to their configuration. See this section for more details.
These options will all be provided to the TerserPlugin via it's constructor.
Option | Value | Purpose |
---|---|---|
terserOptions.output.comments | false |
Remove all comments in the output |
terserOptions.output.preamble | Strimzi header | Adds the Strimzi header to the built JS output |
terserOptions.keep_classnames | true |
Keep original class names |
terserOptions.keep_fnames | true |
Keep original function names |
terserOptions.mangle.safari10 | true |
Works around known Safari issues |
Given the above configuration, built output will be created in a dist
directory. This will contain:
dist/
index.html
favicon.ico
main.bundle.js
<hash1>.bundle.js
<hash2>.bundle.js
....
main.bundle.js.gz
<hash1>.bundle.js.gz
<hash2>.bundle.js.gz
....
main.css
<hash1>.bundle.css
<hash2>.bundle.css
...
images/
image1.svg
...
fonts/
font1.ttf
...
To enable efficient development, this UI makes use of webpack-dev-server. This re-uses the above Webpack configuration, but adds helpful developer features, such as file watching and hot reloading of changes. When run, all content is built and served from memory, with static assets such as images being served from the configured public path. Configuration of the dev server is in the devServer
section of the webpack.config.js
file, and a brief summary of each option can be found below:
Option | Value | Reason |
---|---|---|
contentBase | [dist (via constant), generated/bundle-analyser/bundles.html ] |
Source for static files. This is the built output directory, where static files will land as a part of the first build during dev start up. In addition, include the output of webpack-bundle-analyzer , so a developer can access a /bundles.html file through webpack-dev-server , showing current bundle size/distribution |
watchContentBase | true |
Reload if a static file changes |
compress | true |
Serve all content via .gz files - makes rebuilds/hot changes faster to retrieve |
inline | true |
Enforce default setting, recommended when using hot reloading option |
hot | true |
Enables hot reloading of content when files change |
proxy | TBD | Used to proxy requests for backend data when developing the UI |
overlay.warnings | false |
In case of an warning, do not show an overlay |
overlay.errors | true |
In case of an error, show an overlay over the UI showing it |
host | localhost |
The hostname to use for the server. Can be overridden using the DEV_HOSTNAME environment variable. |
port | 8080 |
The port to use for the server. Can be overridden using the DEV_PORT environment variable. |
By default, webpack-dev-server
will host the UI at https://localhost:8080/
. Both the hostname and port can be overridden/changed via the DEV_HOSTNAME
and DEV_PORT
environment variables respectively.
The above UI build is implemented in the build directory
. It is broken down into common configuration (and a function that will return it, helper functions and build constants), and individual use cases, such as dev and prod builds.
This will be completed once strimzi/proposals#6 has been finalized.