angularwebpackbabeljsnrwl-nx

NX Angular project with automatic polyfills depending on .browserslistrc


Our software needs to support old browser versions. Since Angular does not automatically polyfill for old browsers I would like to add this feature through a custom webpack configuration.

I am using NX together with Angular (v17). What I tried so far was to use the webpack executor of angular and define a customWebpackConfig in my project.json:

"build": {
  "executor": "@nx/angular:webpack-browser", <---
  "outputs": ["{options.outputPath}"],
  "options": {
    "outputPath": "dist/apps/basic-interactivity",
    "index": "apps/basic-interactivity/src/index.html",
    "main": "apps/basic-interactivity/src/main.ts",
    "polyfills": ["zone.js"],
    "tsConfig": "apps/basic-interactivity/tsconfig.app.json",
    "inlineStyleLanguage": "scss",
    "assets": ["apps/basic-interactivity/src/favicon.ico", "apps/basic-interactivity/src/assets"],
    "styles": ["apps/basic-interactivity/src/styles.scss"],
    "scripts": [],
    "customWebpackConfig": {
      "path": "apps/basic-interactivity/webpack.config.js"<---
    }
  }

My webpack.config.js looks like this:

module.exports = {
  module: {
    rules: [
      {
        test: /\.(?:js|mjs|cjs)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              [
                '@babel/preset-env',
                {
                  useBuiltIns: 'entry',
                  corejs: {
                    version: 3,
                    proposals: true
                  },
                  targets: {
                    chrome: '58',
                    ie: '11'
                  }
                }
              ]
            ]
          }
        }
      }
    ]
  }
};

I also defined a .browserslistrc but this had no effect either. The correct polyfills are not loaded.

How can I achieve the automatic polyfilling of my angular application?


Solution

  • After two days of research and with the help of MillerSvt in a Github issue I could figure out how to configure everything properly. Here is a guide.

    Install dependencies

    npm i -D @babel/preset-env
    npm i -D core-js
    

    Using "executor": "@nx/angular:webpack-browser"

    In your project.json use the following executor and customWebpackConfig

    "targets": {
        "build": {
          "executor": "@nx/angular:webpack-browser",
          ...
          "options": {
            ...
            "customWebpackConfig": {
              "path": "apps/YOUR_APP_NAME/custom-webpack.config.js"
            }
          },
    

    The custom-webpack.config.js looks like this:

    export default ({ module, ...config }) => {
      return {
        ...config,
        module: {
          ...module,
          rules: [
            // Adding babel-loader before all others, because angular loader
            // should already receive a valid file with polyfills as input
            {
              test: /\.(?:js|mjs|cjs|ts)$/,
              // Exclude libraries that don't need to be polyfilled
              exclude: /node_modules[\\/](?!@some-untrusted-library)/,
              use: {
                loader: `babel-loader`,
                options: {
                  presets: [
                    [
                      `@babel/preset-env`,
                      {
                        corejs: `3.37`,
                        useBuiltIns: `usage`,
                        configPath: `apps/YOUR_APP_NAME/.browserslistrc`
                      }
                    ]
                  ]
                }
              }
            },
            ...(module?.rules ?? [])
          ]
        }
      };
    };
    

    Using "executor": "@angular-builders/custom-webpack:browser"

    If not already done you have to install @angular-builders/custom-webpack

    npm i @angular-builders/custom-webpack
    

    In your angular.json/project.json use the following executor and customWebpackConfig

      "targets": {
        "build": {
          "executor": "@angular-builders/custom-webpack:browser",
          ...
          "options": {
            ...
            "customWebpackConfig": {
              "path": "apps/YOUR_APP_NAME/custom-webpack.config.js",
              "mergeRules": { "module": { "rules": "prepend" } }
            }
          }
          ...
    

    The custom-webpack.config.js looks like this:

    module.exports = {
      module: {
        rules: [
          {
            test: /\.(?:js|ts|mjs|cjs)$/,
            // Exclude libraries that don't need to be polyfilled
            exclude: /node_modules[\\/](?!@some-untrusted-library)/,
            use: {
              loader: `babel-loader`,
              options: {
                presets: [
                  [
                    `@babel/preset-env`,
                    {
                      corejs: `3.37`,
                      useBuiltIns: `usage`
                    }
                  ]
                ]
              }
            }
          }
        ]
      }
    };
    

    .browserslistrc

    Babel relies on .browserslistrc in which you define the browser support. By default the .browserslistrc from the root of the project is used. If you want to use app specific .browserslistrc you have to configure the path (configPath) to it in the custom-webpack.config.js like this:

    {
       corejs: `3.37`,
       useBuiltIns: `usage`,
       configPath: `apps/YOUR_APP_NAME/.browserslistrc`
    }
    

    Troubleshooting

    My .browserslistrc is ignored

    In case you don't get the desired output you can delete the angular cache (rm -rf .angular/cache) and run the build script again.

    I don't see a difference in my polyfills.js

    The polyfills from the custom-webpack.config.js are added to the specific files which they require. So if in your main.js something has to be polyfilled, the polyfills are added to the main.js.

    The polyfills.js only include polyfills from the polyfills property in your angular.json/project.json