Skip to content

Runtime errors when using buildOptimizer. JIT compilation broken. #17663

Closed

Description

@jcgillespie

🐞 Bug report

Command (mark with an x)

  • new
  • build
  • serve
  • test
  • e2e
  • generate
  • add
  • update
  • lint
  • xi18n
  • run
  • config
  • help
  • version
  • doc

Is this a regression?

I don't know. I would guess this is a recent thing arising from the introduction of AOT, Ivy, and the build optimizer.

Description

We faced an issue with an application we were developing where the legacy application had content stored in a database. This content is essentially an Angular template and could not be known at compile-time. The situations and constraints in the project led us to create dynamic components/modules on the fly. This worked beautifully in development and then failed when deployed to production.

In diagnosis, this works as long as the build optimizer is NOT enabled. Having a production build, with the optimizer, but without the buildOptimizer, enabled still works.

I believe I've seen unofficially in other issues that using AOT and JIT together, but since it works with the buildOptimizer disabled, this feels like an issue with the optimizer.

🔬 Minimal Reproduction

Here is a reproduction repo
Once you've npm install'd, you can run the repro this way.
ng serve <-- working
ng serve --configuration=production <--working
ng serve --configuration=production-build-optimized <-- broken.
The only difference between the configurations is the buildOptimizer setting.
You can run all three side-by-side-by-side by adding different --port values.

In a working build, you'll see an additional "Runtime Hello!" message as well as the Angular logo. When broken, those elements will be missing and there will be a console error.

If this isn't a bug, that error message is misleading for this situation.. :)

🔥 Exception or Error


main.7000374ef126bad8069c.js:1 ERROR Error: Uncaught (in promise): Error: Angular JIT compilation failed: '@angular/compiler' not loaded!
  - JIT compilation is discouraged for production use-cases! Consider AOT mode instead.
  - Did you bootstrap using '@angular/platform-browser-dynamic' or '@angular/platform-server'?
  - Alternatively provide the compiler with 'import "@angular/compiler";' before bootstrapping.
Error: Angular JIT compilation failed: '@angular/compiler' not loaded!
  - JIT compilation is discouraged for production use-cases! Consider AOT mode instead.
  - Did you bootstrap using '@angular/platform-browser-dynamic' or '@angular/platform-server'?
  - Alternatively provide the compiler with 'import "@angular/compiler";' before bootstrapping.
    at Ae (main.7000374ef126bad8069c.js:1)
    at Function.get (main.7000374ef126bad8069c.js:1)
    at St (main.7000374ef126bad8069c.js:1)
    at new Af (main.7000374ef126bad8069c.js:1)
    at ud (main.7000374ef126bad8069c.js:1)
    at dd (main.7000374ef126bad8069c.js:1)
    at e.md [as compileModuleAndAllComponentsAsync] (main.7000374ef126bad8069c.js:1)
    at e. (main.7000374ef126bad8069c.js:1)
    at Generator.next ()
    at main.7000374ef126bad8069c.js:1
    at T (polyfills.a8d522dff2c1a5c4b9db.js:1)
    at polyfills.a8d522dff2c1a5c4b9db.js:1
    at a (main.7000374ef126bad8069c.js:1)
    at l.invoke (polyfills.a8d522dff2c1a5c4b9db.js:1)
    at Object.onInvoke (main.7000374ef126bad8069c.js:1)
    at l.invoke (polyfills.a8d522dff2c1a5c4b9db.js:1)
    at i.run (polyfills.a8d522dff2c1a5c4b9db.js:1)
    at polyfills.a8d522dff2c1a5c4b9db.js:1
    at l.invokeTask (polyfills.a8d522dff2c1a5c4b9db.js:1)
    at Object.onInvokeTask (main.7000374ef126bad8069c.js:1)

🌍 Your Environment



     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/
    

Angular CLI: 9.1.4
Node: 13.11.0
OS: darwin x64

Angular: 9.1.4
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Ivy Workspace: Yes

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.901.4
@angular-devkit/build-angular     0.901.4
@angular-devkit/build-optimizer   0.901.4
@angular-devkit/build-webpack     0.901.4
@angular-devkit/core              9.1.4
@angular-devkit/schematics        9.1.4
@ngtools/webpack                  9.1.4
@schematics/angular               9.1.4
@schematics/update                0.901.4
rxjs                              6.5.5
typescript                        3.8.3
webpack                           4.42.0
    

Anything else relevant?

Activity

  1. karwank commented on May 8, 2020

    @karwank

    I'm facing a similar issue. The funny thing is that when I do "ng serve" and having "Live Development Server" started I change the name of "AppModule" to a different name, for example "AppsModule" in both files (app.module.ts and main.ts) it starts to work and vendor.js containst compiler.

  2. jcgillespie commented on May 8, 2020

    @jcgillespie
    Author

    I'm facing a similar issue. The funny thing is that when I do "ng serve" and having "Live Development Server" started I change the name of "AppModule" to a different name, for example "AppsModule" in both files (app.module.ts and main.ts) it starts to work and vendor.js containst compiler.

    @karwank I'm not sure I'm following. You renamed the module and it worked? That didn't work for me, but I might be misunderstanding your steps

  3. added this to the needsTriage milestone on May 11, 2020
  4. karwank commented on May 11, 2020

    @karwank

    It may be related with: #11439

  5. jcgillespie commented on May 11, 2020

    @jcgillespie
    Author

    It may be related with: #11439

    Possibly. I looked at several other issues (many of which are linked in #11439), but this seemed different because it doesn't involve any third-party libraries. This is all just straight Angular functionality. I would agree with a lot of that discussion in 11439: some granularity of control over which optimizations are run would be nice. Better logging or transparency to what IS happening seems like must-have.

  6. karwank commented on May 12, 2020

    @karwank

    After spending a few days on this problem I have found this post: angular/angular#23916 (comment). And point 3 is very sobering. I don't know exactly what you guys want to achieve. But I realized that we only need to have a way to create a dynamic contents which use already existing elements (components). For example:

    <div>
      <p>This is a custom page with previously defined elements</p>
      <app-template-contact-form></app-template-contact-form>
      <app-template-map></app-template-map>
    </div
    

    I realized that I don't need to use JIT for this purpose. Components can be lazy loaded just like Kevin Kreuzer had shown here: https://medium.com/angular-in-depth/lazy-load-components-in-angular-596357ab05d8. So after analyzing these examples: https://github.com/LayZeeDK/ivy-dynamic-rendering, https://stackblitz.com/angular/ovpljlvlyxo, I have written my own service:

    import {
      Injectable,
      Injector,
      ɵrenderComponent as renderComponent,
      Renderer2,
      ComponentFactoryResolver,
      ViewContainerRef
    } from '@angular/core';
    
    @Injectable({
      providedIn: 'root',
    })
    export class DynamicService {
      
      public renderer: Renderer2;
      public injector: Injector;
    
      constructor(
        private cfr: ComponentFactoryResolver,
      ) {}
    
      loadComponentToRef(componentName: string, target: ViewContainerRef) {
        let targetRendererMethod = this.renderComponentToRef;
        this.loadComponent(componentName, target, targetRendererMethod);
      }
    
      loadComponentToNode(componentName: string, target: HTMLElement) {
        let targetRendererMethod = this.renderComponentToNode;
        this.loadComponent(componentName, target, targetRendererMethod);
      }
    
      async loadComponent(componentName: string, target: HTMLElement | ViewContainerRef, targetRendererMethod: any) {
        
        let renderMethod = function(ComponentType: any, target: HTMLElement | ViewContainerRef) {
          let componentFactory = this.cfr.resolveComponentFactory(ComponentType);
          return targetRendererMethod.call(this, componentFactory, target)
        }
    
        switch (componentName) {
          case 'app-template-search':
            await import('@modules/template/search/search.component').then(esModule => { renderMethod.call(this, esModule.default, target) });
            break;
          case 'app-template-map':
            await import('@modules/template/map/map.component').then(esModule => { renderMethod.call(this, esModule.default, target) });
            break;
          default:
            break;
        }
    
      }
    
      renderComponentToNode(componentFactory: any, node: HTMLElement) {
        let compRef = componentFactory.create(this.injector)
        let parent = this.renderer.parentNode(node);
        this.renderer.insertBefore(parent, compRef.location.nativeElement, node)
        this.renderer.removeChild(parent, node);
        compRef.changeDetectorRef.detectChanges();
    
        return compRef
      }
    
      renderComponentToRef(componentFactory: any, ref: ViewContainerRef) {
        let { instance } = ref.createComponent(componentFactory, null, this.injector);
      }
    
    }
    
  7. alan-agius4 commented on May 28, 2020

    @alan-agius4
    Collaborator

    BuildOptimizer, indeed cannot be used with JIT code.

    This also seems related to angular/angular#36255 and angular/angular#37216

    In your case @jcgillespie, you can avoid using JIT mode and using above approach.

  8. maxtacco commented on May 28, 2020

    @maxtacco

    @alan-agius4 We are in the same boat as @jcgillespie. The approach described by @karwank is well-known but not viable when you have hundreds of possible template variations.

  9. modified the milestones: needsTriage, Backlog on May 28, 2020
  10. 17 remaining items

  11. Nixon-Joseph commented on Jul 17, 2020

    @Nixon-Joseph

    PS. Solved my dynamic template issue using a plain JavaScript ‘new Function’ call. Ugly, but now both Angular and me are happy ...

    @loedeman can you provide a quick example of your solution? (trimmed down of course).

  12. dgp1130 commented on Oct 8, 2020

    @dgp1130
    Collaborator

    Spoke with @IgorMinar and @alxhub about this. We don't see any supported use case that benefits from JIT + build optimizer. Build optimizer strips out decorators required by JIT mode, so there's no easy way to make this happen. Build optimizer is intended for production use and JIT has significant bundle size and performance impact due by its very nature, so you'd never have acceptable bundle sizes with this combination anyways (and if you want build optimizer, you clearly care about bundle size). Compiling components at runtime is also an XSS vulnerability in itself, so doing so is not a recommended or supported use case. As a result, we don't think Angular should support such a configuration.

  13. removed
    needs: discussionOn the agenda for team meeting to determine next steps
    on Oct 8, 2020
  14. jcgillespie commented on Oct 8, 2020

    @jcgillespie
    Author

    @dgp1130 This is a disappointing, if not unexpected, response.

    If this isn't going to be a supported scenario, can we get a better, less misleading, error message so people don't waste so much time? The message suggests there is something the user can do to address the issue, but the team has decided this isn't the case.

  15. dgp1130 commented on Oct 8, 2020

    @dgp1130
    Collaborator

    @jcgillespie, agreed that we can do better error messaging here.

    Filed #19016 to introduce a better, compile-time message about this.

  16. alxhub commented on Oct 13, 2020

    @alxhub
    Member

    @FSDRE can you elaborate on what feature you're referring to?

    Angular Ivy was specifically designed to enable AOT + JIT to work together. You can build AOT-compiled applications that load the compiler and dynamically compile components on the fly, and can also benefit from build-time code size optimizations (that is, Terser).

    The Angular build-optimizer is something else entirely. It was designed to eliminate things like decorator artifacts that other optimizers could not statically prove were unused. This only makes sense if your codebase is AOT-only - if you're including JIT code then you need those decorators to survive optimization, because the decorators are what trigger the JIT compiler and carry the information needed to compile each class.

    There's no feature being removed here. buildOptimizer: true just doesn't make sense for mixed AOT and JIT application, because its raison d'etre is to strip out the metadata that would otherwise be needed for JIT support, in the name of smaller AOT-only builds.

  17. alxhub commented on Oct 13, 2020

    @alxhub
    Member

    To elaborate a bit more, it is indeed true that buildOptimizer being a binary setting is overly broad - it would theoretically be possible to apply the build-optimizer only to the AOT parts of a hybrid codebase. However, this is not something the CLI currently can do, and there are lots of challenges around knowing which imported libraries can be run through the optimizer and which might be used in a JIT context. It would be nontrivial to support something like this.

  18. angular-automatic-lock-bot commented on Nov 13, 2020

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  19. locked and limited conversation to collaborators on Nov 13, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions