0

I need to be able to replace a fairly large module in my Angular app with a very simple one in order to make my Server Side Rendering build load (avoiding all the unnecessary requires for a component that's too rich to work on the server anyway). Without the replacement the code is trying to load window or document. Some libraries I was able to replace simply with a null-loader but in this instance I need cut off an entire dependency tree by trimming this one specific Angular module.

Despite all my attempt to override the original module, Angular (@ngtools/webpack) still somehow succeeds in generating require calls for the components I was hoping to trim ...

I tried:

  • Using hostReplacementPaths parameter to the compiler plugin
new AngularCompilerPlugin({
    tsConfigPath: 'tsconfig.ssr.json',
    entryModule: path.join(__dirname, 'src/js/landing/landing.server.module.ts#LandingServerModule'),
    hostReplacementPaths: {
      [path.resolve('./src/js/app/help.module.ts')]: path.resolve('./src/js/landing/server-mock.module.ts')
    }
  })
  • Using NormalModuleReplacementPlugin
new NormalModuleReplacementPlugin(
      /help\.module\./,
      resource => {
        resource.requestresource.request.replace('app/help.module.ts', 'landing/server-mock.module.ts')
      },
    ),
  • file-replace-loader
{
        test: /help\.module\.ts/,
        loader: 'file-replace-loader',
        options: {
          condition: 'if-replacement-exists',
          replacement: path.resolve('./src/js/landing/server-mock.module.ts'),
          async: true,
        }
      },

I suspect the angular compiler is scanning the files for dependencies outside of webpack ...

Any ideas how I could override a single Angular module with a mock module at build time?

RushPL
  • 4,732
  • 1
  • 34
  • 44

2 Answers2

0

I found a solution. I had to create an intermediate module src/js/app/mock-intermediate-version-of-the-module.ts that re-exports the module I need to trim

import { NgModule } from '@angular/core';
import { ModuleThatYouNeedToReplaceAtBuildTime } from '../app/module-to-replace-at-build-time';

@NgModule({
  exports: [
    ModuleThatYouNeedToReplaceAtBuildTime,
  ],
})
export class IntermediateVersionOfTheModule {
}

and also create a mock version of the IntermediateVersionOfTheModule:

src/js/app/mock-intermediate-version-of-the-module.ts

import { NgModule } from '@angular/core';

@NgModule({
})
export class LandingHelpModule {
}

And then in AngularCompilerPlugin configuration pass the following:

new AotPlugin({
  tsConfigPath: 'tsconfig.ssr.json',
  entryModule: path.join(__dirname, 'src/js/landing/landing.server.module.ts#LandingServerModule'),
  hostReplacementPaths: {
    [path.resolve('intermediate-version-of-the-module.ts')]: path.resolve('./mock-intermediate-version-of-the-module.ts')
  }
})

The trick is replace a module that's just an intermediate one, that re-exports the actual module which we want to trim. The angular compilers scans our entire tree of modules and can't deal with any components without modules and duplicated modules with the same components.

Hope it's useful folks!

RushPL
  • 4,732
  • 1
  • 34
  • 44
0

I'm having the same problem. We use file overrides quite a lot and I wrote an webpack resolver plugin for file replacements. After switching to AOT, the plugin no longer works. The overrides are only reflected once at the first build. But somehow, @ngtools/webpack still refers to the non-overriden files, so changes in an overriden file aren't detected and recompiled by the watcher.

I also tried hostReplacementPaths, but in my case, this is not a solution because it seems that hostReplacementPath replacments are not "transitive". So a relative import in a file, which is inserted via hostReplacment will not be found in the new context.

My solution for now is to use JIT for development and switch to AOT in production, although this is a bit error prone. The documentation of ngtools/webpack somewhat indicates that the resolvement in JIT works somewhat different.

skipCodeGeneration. Optional, defaults to false. Disable code generation and do not refactor the code to bootstrap. This replaces templateUrl: "string" with template: require("string") (and similar for styles) to allow for webpack to properly link the resources. (https://www.npmjs.com/package/@ngtools/webpack)

Enabling skipCodeGeneration requires the usage of platformBrowserDynamic to bootstrap the app and some minor modifications in the webpack config (non ngfactory style).

I guess it is some weird mechanism in AOT compilation that will get fixed in the future, but I don't have any hope for that.

chumbalum
  • 1
  • 1