Angular 8 - Creating a Webpack Configuration from Scratch
Monday, August 6, 2018
The Angular CLI has recently been significantly overhauled with v6, utilizing Webpack 4 for additional speed improvements and smaller bundle sizes. The CLI is perfect for a large majority of projects, and is only getting better, but there are occasions where you just need more freedom, or perhaps you just want to use some of Webpacks great loaders, plugins or features that aren’t currently supported from the CLI workspace file.
In Angular CLI v1.x there was the option to eject, which generated a Webpack configuration that could be changed or extended to suit your needs, however this feature has been temporarily disabled. We also used to have a section on the Angular documentation site dedicated to creating a Webpack config for your applications, however this has since been removed also leaving you somewhat on your own to create it.
In this post I will demonstrate how to create a custom Webpack configuration for both development and production that will provide largely the same setup as the CLI does for you. We will also go over some of the fantastic, yet largely unknown Webpack plugins and loaders that the Angular team have developed that aren’t necessarily limited to just an Angular application. The complete configurations can be found at the bottom of the post.
Before we begin, I generated a simple project using the Angular CLI to create a familiar project structure and provide the same starting point for anyone following along, however we will not be using the CLI to build or serve our application. If your application is structured differently than a CLI project some paths may differ.
Let’s start by installing all the dependencies we need to get started:
Next we want to create a new file in the root of our project called webpack.config.js. This will contain our development configuration, in other words, this configuration will prioritize rebuild speed over bundle size.
Lets go through each part of the configuration and briefly explain its purpose:
This setting informs Webpack of the purpose of this configuration file. It will provide some sensible defaults for us automatically. The two main modes are development and production. Development mode will disable features like code minification, as this is slow and unnecessary when developing. If no mode is specified production will be used by default.
This defines several files that Webpack can start building a dependency graph from and begin bundling. The three entry points we want to specify are the main.ts file, which is the starting point of our application code, the polyfills.ts file, which contains all the code that will ensure our application runs in older browsers (…cough… IE…) and finally the styles.css file, which is our global stylesheet.
This option allows us to define what directory our application should be built to and also specify how we want out files no be named. In the case below [name] will be replaced with the corresponding entrypoint name:
This section allows us to configure how our application should be optimized, for example, through minification and code splitting. In the example below we indicate we want a file containing the Webpack runtime code (the boilerplate that handles module loading) to prevent unneccesary duplication. In our production configuration we will also specify some plugins to minify our code here too, but we will omit that in our development configuration as it can make rebuilt times very slow.
In our development build we also want a vendors bundle. Vendor in ths case refers to any third party package, which is unlikely to change often, so we can increase rebuild time by having it in a separate chunk so it doesn’t get processed when we make changes to our application source code.
We can define what should happen when Webpack encounters a specific file type. There are comments above each loader indicating what it does.
Plugins allow use to perform additional actions that aren’t limited to just a specific file like a loader. We can use the following plugins to enhance our build:
This is a plugin by the Angular team, similar to the HtmlWebpackPlugin. It allows us to produce an index.html file that has the appropriate scripts and stylesheets included automatically.
This plugin is used along with the @ngtools/webpack loader to compile our components using the Angular compiler, commonly known as Ahead of Time compilation. In development mode we want to run in JIT mode for faster rebuilds, so we set skipCodeGeneration to true. We can also state we want to name lazy loaded files which can be very useful when debugging.
This plugin simply allows Webpack to report the progress of the build.
Circular dependencies can often occur in large applications, and sometimes this can cause bugs. This plugin can help detect these circular dependencies automatically.
There are some files we may want to include in our dist folder that would not be included by the file-loader or url-loader such as the favicon. We want to copy these files to our output directory and this plugin makes it easy to do so.
Building our Application
Now that we have our configuration file complete we can build our application by running:
Additionally we can run a local development serve that will rebuild automatically when we make any changes. To do this we can use the webpack-dev-server:
Note: You must have webpack, webpack-cli and webpack-dev-server installed globally to run these directly from the command line.
We want to begin by creating a separate Webpack config file, for example webpack.production.config.js. To run Webpack with this configuration we can do:
We want to ensure we set the mode to production.
We want to produce a more accurate sourcemap as part of our production build. This is slower but is a significant help when trying to debug minified code.
We want to make a few changes to the list of loaders for further optimizations.
We want to ensure we use the Angular Build Optimizer loader to reduce our bundle size quite significantly.
We also no longer want to use the style-loader and instead want to extract it to a separate global stylesheet. We can do this using the MiniCssExtractPlugin.
We will make some changes to our production build that result in smaller bundle sizes, largely by adding plugins to minify our code.
The HashedModuleIdsPlugin will produce hashes of our files and use this as the file names. This will result in files getting different names when the contents change preventing cache issues.
The CleanCssWebpackPlugin is another plugin by the Angular team that will allow us to easily minify our stylesheets.
We will make a few changes to our plugins section in our production config.
We want to alter this plugin’s options to ensure we enable Ahead of Time compilation, turn of named lazy files and replace the environment file with the production version.
This plugin will simply produce a global stylesheet for us:
This is another useful plugin by the Angular team that allows us to remove any entrypoints that produce CSS files. Webpack by default would produce a styles.js file if we have a styles.css entrypoint and this file would be completely unnecessary.
There are a few additional plugins created by the Angular team which are worth mentioning, however I have not included them in these configuration files as they would not necessarily be required by every application.
ScriptsWebpackPlugin - This allows you to easily include global scripts in your application. This is great if your application utilizes a package like jQuery or Bootstrap.js that can often be a little bit tricky to include using import statements.
BundleBudgetPlugin - This allows you to specify some bundle size limits. This can either warn you when a bundle exceed the specified limit or will emit an error when the limit is exceeded. This is a great way to ensure the bundle size stays within an acceptable limit to give your users the best possible experience.
The complete range of plugins created by the Angular team can be found here.