DEV Community

Cover image for Customizing Angular CLI 6 build  - an alternative to ng eject
JeB
JeB

Posted on • Edited on

Customizing Angular CLI 6 build  - an alternative to ng eject

So your Angular 6 project just went a bit beyond TODO app and you have to customize your build configuration.
The question is how?

Angular CLI 1.x ng eject VS Angular CLI 6 builders

In Angular CLI 1.x (Angular 5) you had ng eject command for this, which was ejecting the whole underlying webpack configuration and allowing you to modify it as you please.

In Angular CLI 6 this command has been temporarily disabled, and I suspect that it will be deprecated soon due to the new concept called Builders.
With the new Angular CLI you can customize the build process by defining your own builders as well as using one of the builders provided by the community.

Extending underlying webpack configuration

So lets go ahead and customize our build by extending the underlying webpack configuration:

  • Install @angular-builders/custom-webpack: npm i -D @angular-builders/custom-webpack.
    Note that it requires version @angular-devkit/build-angular>=0.7.0 so you might need to install it (if not yet installed):
    npm i -D @angular-devkit/build-angular

  • In angular.json change the @angular-devkit/build-angular:browser builder to @angular-builders/custom-webpack:browser:

    "architect": {
       ...
      "build": {
          "builder": "@angular-builders/custom-webpack:browser"
          "options": {
                ...
          }
      ...
    }
Enter fullscreen mode Exit fullscreen mode
  • If you build a universal app and would like to modify your server build configuration use @angular-builders/custom-webpack:server instead of @angular-builders/custom-webpack:browser

  • Add customWebpackConfig to the build target options :

    "architect": {
       ...
      "build": {
          "builder": "@angular-builders/custom-webpack:browser"
          "options": {
                "customWebpackConfig": {
                   "path": "./extra-webpack.config.js"
                }  
                ...
          }
      ...
    }
Enter fullscreen mode Exit fullscreen mode

Check out the full description of customWebpackConfig object here.

  • Create extra-webpack.config.js in the root of your application (or whatever path you specified in angular.json).
  • Fill it with extra configuration you need (plain webpack configuration).
  • Note that in contrary to ng eject this configuration will be merged with the default Angular build configuration so you only have to configure the part you want to change/add. For example, if you want to add another webpack loader, your webpack config will look like this:
    module.exports = {
      module: {
        rules: [
          {
            test: /\.cool$/,
            use: 'cool-loader'
          }
        ]
      }
    };
Enter fullscreen mode Exit fullscreen mode

And what about ng serve?

If you want to run ng serve with custom webpack configuration (given you performed all the above steps) you should do the following:

  • Install @angular-builders/dev-server
  • Replace @angular-devkit/build-angular:dev-server inside the serve target with @angular-builders/dev-server:generic
  • Run ng serve

Additional sources

You can check out this electron-angular-native project for the example of Electron Angular application with node addons built with Angular CLI 6.

What is next

In the next article we will learn how to create your own builder.

Top comments (15)

Collapse
 
chauhanvishaal profile image
chauhanvishaal

This is amazing to see the hybrid solution actually working.
Thank you very much Evgeny.

At the moment I am have two problems I am trying to resolve.
1) make use of string-replace-webpack-plugin to replace index.html content
I think I have got the configuration right however the loader rule for it just doesnt seem to execute.

I also tried a different plugin webpack-html-string-replace-plugin but it needs usage of HtmlWebpackPlugin which messes up with the ng cli default webpack configuration causing rendering issues.

2) override default builder for protractor such that it is compatible with build and serve architect/targets

Any experience customizing these ?

Loader for string-replace-webpack-plugin
{
//Replace baseUrl with appropriate value as per dev or production mode
test: /index.html$/,
exclude: /node_modules/,
use: [ { loader: StringReplacePlugin.replace({
replacements: [
{
pattern: /@baseUrl/,
replacement: function (match, p1, offset, string) {
// if (isProd)
// return "";
// else if (isDev)
return "/vmi/";
}
}
]
})
}]
}

Collapse
 
jeb profile image
JeB

Hi,
Thank you very much for the kind words.
Regarding 1 - have you added an instance of StringReplacePlugin to plugins array as the documentation suggests? Another possible issue is the order in which the loader is applied. Prepending your loader instead of appending it might work as well. Here is an example of issue that was fixed by simply changing the order.

Regarding 2 - there is no such a builder but it's definitely an option. Please create a feature request on github and I'll look into it.

If the proposed solution for 1 doesn't work, please open an issue on github so that we could continue the investigation.

Collapse
 
jeb profile image
JeB

Regarding 2: they use dev-server target for protractor tests, so using @angular-builders/dev-server should be enough.

Collapse
 
theoiorga profile image
Theo

Thank you for writing this article. I'm currently working on an app that has multiple elements, so I'm trying to build all the elements into a single file.

Following you steps, I get this error: An unhandled exception occurred: Could not find the implementation for builder @angular-builders/dev-server:generic

Does anyone has any idea why?

Collapse
 
jeb profile image
JeB

I believe you're trying to use it with a newer version of @angular-builders. It's been deprecated, you should be using @angular-builders/custom-webpack:dev-server instead. Refer to the documentation.

Collapse
 
giolf profile image
Giovanni Far • Edited

This solution seems not working correctly:
Did you try to customize the webpack.config file adding plugins such as 'webpack-dashboard' or 'webpackmonitor'?

I guess no! right?

---UPDATE---

I didn't read the "serve" part of your topic.
Now everything works perfectly

Collapse
 
rohith47 profile image
Rohith S R

Is was able to over-ride entry , output, and some plugins using this strategy. But unfortunately didn't find a way to override / stop the generation of vendor chunk by cli and generate it manually using webpack-dll plugin. Any way out ?

Collapse
 
jeb profile image
JeB

Actually stopping vendor chunk generation is pretty easy - just specify "vendorChunk": false in your dev-server (or build) configuration. Like this:

    "serve": {
      "builder": "@angular-builders/dev-server:generic",
      "options": {
        "browserTarget": "my-app:build",
        "vendorChunk": false
      },

As for DllPlugin, I have never used it so can't help you here.

I'm sure though if you figure that out there are folks that would be interested in that. So keep me posted!

Collapse
 
umananan profile image
umananan

Hi I am using @angular/cli": "~6.1.1", and Followed the steps you specefied, but getting error when trying to build the solution. Build statement used -

ng build --prod --aot --vendor-chunk --common-chunk --delete-output-path --build-optimizer --stats-json

chema validation failed with the following errors:
Data path ".builders['browser']" should have required property 'class'.
Error: Schema validation failed with the following errors:
Data path ".builders['browser']" should have required property 'class'.
at MergeMapSubscriber._registry.compile.pipe.operators_1.concatMap.validatorResult [as project]

Collapse
 
felipecarrillo100 profile image
felipecarrillo100

I need to modify the uglifyOptions from the UglifyJSPlugin, any idea how to do that?

Collapse
 
jeb profile image
JeB • Edited

Sure, please take a look at this comment. Although Angular CLI is not using UglifyJsPlugin directly, not since Webpack 4. I think you need the optimization option in Webpack.
Let me know how it turned out. If it still doesn’t work for you, kindly open an issue on github and let us continue the discussion there.

Collapse
 
felipecarrillo100 profile image
felipecarrillo100

Hi,

If you see the
node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/common.js

You can see it requests

const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

Then it is configured in the same file a bit further.

I'm using a 3rd party tool that requires setting the uglifyOptions.compress.unused to false.
Since the default is true I have some issues with this library.

My objective is to use extra-webpack.config.js

to modify and add the uglifyOptions compress "unused" to false. Could you give me some guidance on how to get there?

    compress: {
        pure_getters: buildOptions.buildOptimizer,
        // PURE comments work best with 3 passes.
        // See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926.
        passes: buildOptions.buildOptimizer ? 3 : 1,
        // Workaround known uglify-es issue
        // See https://github.com/mishoo/UglifyJS2/issues/2949#issuecomment-368070307
        inline: wco.supportES2015 ? 1 : 3,

unused:false
}

Thread Thread
 
jeb profile image
JeB

In your plugins section add UglifyJSPlugin with compress.unused set to false.

If Angular CLI uses this plugin, your configuration will take preference and will override the compress.unused of the default UglifyJSPlugin.

Thread Thread
 
jeb profile image
JeB • Edited

@felipecarrillo100 , here are few insights:

  1. Since this commit they are using TerserPlugin instead of UglifyJsPlugin.
  2. In current implementation you cannot override the instance of a specific plugin inside minimizer array. Plugins merge was implemented for plugins array but not for the rest. If you'd like this functionality to be added, please open a feature request on github. PR is also welcome.
  3. If you need it now and don't have time to wait for this feature the only option you have is overriding the whole optimization entry. For this you have to:
    • Specify optimization: replace in mergeStrategies object in builder configuration
    • Define all the optimization configuration as it is defined in Angular along with your modifications inside your extra-webpack.config.js. Angular optimization definitions can be found in
node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/common.js  

and in

node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js.
Thread Thread
 
jeb profile image
JeB

You can do it easily now with version 7.4.1. Just export a function in your config file, it will receive the original config and expected to return a modified config. You can do whatever you like with the config that you receive.