angularwebpackng-bootstrapangular13

How to package ng-bootstrap in Angular using webpack


I'm working on a hybrid Angular 13 application, and using webpack to package it for production, but I'm having an issue the ng-bootrap library. There are AngularJS components in the application using modules from the angular-ui library, I'm not sure if this is part of the issue.

I've added the ng-bootstrap library to package.json, so I can use the modules in the application, without causing any issue, but when I add any module to the app.module file, the production build will not open in a browser. However, the development build opens without issue

these are the relevant entries in the package.json file

"dependencies": {
  ...,
  "@ng-bootstrap/ng-bootstrap": "^12.1.2",
  "@popperjs/core": "^2.10.2"
},
"devDependencies": {
  ...,
  "@ngtools/webpack": "^13.3.9",
  "@types/webpack": "^5.28.0",
  "@types/webpack-env": "^1.16.3",
  "clean-webpack-plugin": "^3.0.0",
  "copy-webpack-plugin": "^8.1.1",
  "css-minimizer-webpack-plugin": "^3.0.1",
  "html-webpack-plugin": "^5.5.0",
  "image-webpack-loader": "^7.0.1",
  "karma-webpack": "^5.0.0",
  "webpack": "^5.39.1",
  "webpack-bundle-analyzer": "^4.4.2",
  "webpack-cli": "^4.7.2",
  "webpack-dev-server": "^3.11.2",
  "webpack-manifest-plugin": "^5.0.0",
  "webpack-merge": "^5.8.0",
  "webpack-nano": "^1.1.1"
}

app.module.ts

import { NgbAlertModule } from '@ng-bootstrap/ng-bootstrap';

@NgModule({
  imports: [
    ..., 
    NgbAlertModule
  ],

The vendors.aot.ts file has import statements for the library, plus its dependencies

import '@ng-bootstrap/ng-bootstrap';
import '@popperjs/core';
import '@angular/localize';

The build does complete successfully, but when I open a browser, I get this exception in the console log

app-6279414c.346772f…ed2a2ea.bundle.js:1 Uncaught TypeError: Cannot read properties of undefined (reading 'call')
    at d (app-6279414c.346772f….bundle.js:1:160677)
    at i (app-6279414c.346772f….bundle.js:1:164659)
    at 3268 (common-b48beae3.f498…7.bundle.js:2:12304)

Is there anything missing from the webpack production file(below), or do any of the existing options need to be modified?

webpack.production.js

const webpack = require("webpack");
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { AngularWebpackPlugin } = require('@ngtools/webpack');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');

module.exports = {
  entry: {
    'polyfills': './src/project/ng2/polyfills.ts',
    'vendor': './src/project/ng2/vendors.aot.ts',
    'ng1': './src/project/app.module.ajs.ts',
    'app': './src/project/ng2/main.aot.ts'
  },
  output: {
    filename: '[name].[contenthash].bundle.js',
    path: path.resolve(__dirname, './src/app'),
    publicPath: ''
  },
  mode: 'production',
  resolve: {
    extensions: [".ts", ".tsx", ".js"]
  },
  module: {
    rules: [{
      test: /\.css$/,
      use: [ MiniCssExtractPlugin.loader, 'css-loader' ]
    }, {
      test: /\.tsx?$/,
      enforce: 'pre',
      loader: 'ts-loader',
      options: {
        configFile: 'tsconfig.json'
      },
      exclude: [
        path.resolve(__dirname, './src/project/ng2'),
        /node_modules/
      ],
    }, {
      test: /\.[jt]sx?$/,
      loader: '@ngtools/webpack',
      include: [
        path.resolve(__dirname, './src/project/ng2'),
        /node_modules/
      ],
    }, {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/env'],
          plugins: ['@babel/plugin-proposal-class-properties', 'angularjs-annotate']
        }
      }
    }, {
      test: /\.html$/,
      exclude: /node_modules/,
      include: [ path.resolve(__dirname, './src/project/ng2') ],
      loader: 'html-loader'
    }, {
      test: /\.html$/,
      exclude: [
        path.resolve(__dirname, './src/project/ng2'),
        /node_modules/
      ],
      loader: 'raw-loader'
    }, {
      test: /\.(png|woff|woff2|eot|ttf|svg|gif)$/,
      use: [{
        loader: 'url-loader',
        options: {
          limit: 5000,
          name: 'images/[name].[ext]'
        }
      }]
    }, {
      test: /\.mjs$/,
      use: {
        loader: 'babel-loader',
        options: {
          plugins: ['@angular/compiler-cli/linker/babel'],
          compact: false,
          cacheDirectory: true,
        }
      }
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/project/index.html',
      minify: true,
      scriptLoading: 'defer',
      inject: true
    }),
    new MiniCssExtractPlugin({
      filename: 'styles.[contenthash].css'
    }),
    new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: ['**/*']
    }),
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      "window.jQuery": "jquery",
      moment: 'moment'
    }),
    new CopyWebpackPlugin({
      patterns: [{
        context: './src/project',
        from: '**/*.html',
        globOptions: {
          dot: true,
          gitignore: true,
          ignore: ["**/*index.html", "**/ignored-directory/**"],
        },
        to: './',
        force: true
      }]
    }),
    new webpack.IgnorePlugin({
      resourceRegExp: /^\.\/locale$/,
      contextRegExp: /moment$/,
    }),
    new webpack.ContextReplacementPlugin(/\.\/locale$/, 'empty-module', false, /js$/),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new AngularWebpackPlugin({
      tsconfig: './tsconfig.aot.json',
      entryModule: path.resolve(__dirname, './src/project/ng2/app.module#AppModule'),
      skipCodeGeneration: true
    }),
    new WebpackManifestPlugin()
  ],
  optimization: {
    moduleIds: 'deterministic',
    mangleWasmImports: true,
    mergeDuplicateChunks: true,
    minimizer: [
      `...`,
      new CssMinimizerPlugin(),
    ],
    splitChunks: {
      chunks: 'all',
      minSize: 3000,
      minRemainingSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      enforceSizeThreshold: 50000,
      maxSize: 600000,
      name: 'common',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
          idHint: 'node_module_vendor'
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
          idHint: 'ng2'
        }
      },
      usedExports: true
    }
  }
};

Solution

  • Turns out the issue was in the webpack configuration. Removing this line resolved the issue

    new webpack.optimize.ModuleConcatenationPlugin(),
    

    The ModuleConcatenationPlugin is not needed in production mode, as it is already enabled, as noted on the webpack website: https://webpack.js.org/plugins/module-concatenation-plugin/

    By default this plugin is already enabled in production mode and disabled otherwise.