How to use Angular with Webpack
Integrating Webpack with Angular allows developers to fine-tune their build process beyond the default capabilities of the Angular CLI.
With over 25 years of experience in software development and as the creator of CoreUI, I have architected numerous enterprise-level build pipelines that require specialized configurations.
While the Angular CLI handles most needs out of the box, the most efficient and modern solution for custom requirements is to use the @angular-builders/custom-webpack package.
This approach allows you to extend the internal Webpack configuration without “ejecting” from the CLI, maintaining the benefits of the Angular ecosystem while gaining full control over the bundling process.
Important: Starting with Angular 17, new projects use the application builder powered by esbuild and Vite by default. The Webpack-based browser builder is still available but is considered legacy. Use the custom Webpack approach described below only when you have specific requirements that cannot be met by the modern builder. For the default Vite-based setup, see how to use Angular with Vite.
Use the @angular-builders/custom-webpack builder to merge your own Webpack configurations into the standard Angular build pipeline.
npm install -D @angular-builders/custom-webpack
To start customizing your build, you first need to install the custom builder. This package acts as a bridge between the Angular CLI and your specific Webpack requirements. Once installed, you can modify your angular.json file to point to this new builder instead of the default one, allowing you to inject custom loaders, plugins, or optimization strategies.
1. Modifying angular.json
The core of the integration lies in updating your angular.json file. You must change the builder property under the architect section for both build and serve targets.
{
"projects": {
"my-app": {
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./extra-webpack.config.js",
"replaceDuplicatePlugins": true
},
"outputPath": "dist/my-app",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json"
}
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"options": {
"browserTarget": "my-app:build"
}
}
}
}
}
}
In this configuration, we replace the default @angular-devkit/build-angular:browser with @angular-builders/custom-webpack:browser. The customWebpackConfig object specifies the path to your external configuration file. Setting replaceDuplicatePlugins to true ensures that if you define a plugin that Angular already uses, your version will take precedence. If you are just starting, you might want to learn how to create a new Angular project first to see the default structure.
2. Creating the Webpack Config
Create a file named extra-webpack.config.js in your project root. This is where you write standard Webpack configuration code. A practical use case is adding Gzip compression and Sentry source map uploads — capabilities the Angular CLI does not provide natively.
const webpack = require('webpack')
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
plugins: [
new webpack.DefinePlugin({
'APP_VERSION': JSON.stringify('1.0.0'),
'PRODUCTION': JSON.stringify(process.env.NODE_ENV === 'production')
}),
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8
})
]
}
The DefinePlugin injects global constants into your application — useful for feature flagging or version tracking across environments. The CompressionPlugin generates pre-compressed .gz files alongside your build output, which your web server can serve directly without runtime compression overhead. Since this is a standard Node.js module, you have access to all Node APIs and any Webpack-compatible plugins you’ve installed via npm.
3. Advanced Loaders Integration
If you need to handle specific file types like SVG as inline DataURLs or optimize images during the build, you can add specialized loaders to your extra-webpack.config.js.
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
use: [
{
loader: 'svg-url-loader',
options: {
limit: 10000
}
}
]
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
disable: true
}
}
]
}
]
}
}
In this snippet, we integrate svg-url-loader to inline small SVG files as DataURLs, reducing HTTP requests. We also set up image-webpack-loader for compressing images. These adjustments are common in high-performance applications where asset size is a primary concern. Note that these rules only apply to files imported directly in your TypeScript or CSS — files listed in the assets array of angular.json are copied as-is.
4. Environment Variable Injection
One of the most frequent reasons for using custom Webpack in Angular is to inject environment variables that are managed outside of the environment.ts files, such as those from a CI/CD pipeline.
const webpack = require('webpack')
const dotenv = require('dotenv')
dotenv.config()
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify(process.env.API_URL),
'process.env.ANALYTICS_ID': JSON.stringify(process.env.ANALYTICS_ID)
})
]
}
Using DefinePlugin with explicit JSON.stringify wrappers is safer than EnvironmentPlugin because it performs a direct text replacement at build time. The variables become string literals in the compiled output — no runtime process object is needed. To use these in your TypeScript code, add a type declaration file:
// src/typings.d.ts
declare var process: {
env: {
API_URL: string
ANALYTICS_ID: string
}
}
This keeps your secrets out of the source code and allows for dynamic configuration changes without modifying the application logic.
5. Optimization Strategies
Customizing Webpack also allows you to implement specific optimization strategies, such as splitting chunks differently or excluding large libraries provided via CDN.
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
externals: {
'moment': 'moment'
}
}
The splitChunks configuration here explicitly creates a separate bundle for all code residing in node_modules. This is particularly beneficial for caching, as vendor libraries change less frequently than application code. The externals property is used to exclude specific libraries from the bundle, assuming they are provided via a CDN or a separate script tag. This can significantly reduce build times and final bundle sizes.
6. Building and Running
After setting up your configurations, you can run your application using the standard Angular CLI commands. The custom builder handles the integration behind the scenes.
# Run the development server
ng serve
# Build for production
ng build --configuration production
# Build with custom environment variables
API_URL=https://api.example.com ng build --configuration production
When you execute ng serve, the custom-webpack:dev-server builder will merge your extra-webpack.config.js with the internal configuration and start the development server. Similarly, ng build will apply your custom rules and plugins to the production bundle. This seamless integration ensures that your custom build logic is consistently applied across all stages of the development lifecycle.
Best Practice Note:
While extending Webpack offers great flexibility, always prioritize the default Angular CLI features whenever possible to avoid unnecessary complexity. The modern application builder with esbuild and Vite covers most use cases — only reach for custom Webpack when you need specific plugins like CompressionPlugin, SentryWebpackPlugin, or custom asset processing that the built-in builder cannot handle. At CoreUI, we use custom builders primarily for integrating specialized asset pipelines that are not supported natively. If you are looking for a pre-configured, high-performance setup, our Angular Dashboard Template and Free Angular Admin Template provide a solid foundation that balances customization with CLI standards. For standard UI tasks, always leverage built-in components like the CoreUI Button to ensure consistency and accessibility.



