Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide options to enable/disable the removal of decorators and replacement of resources in a prod build with AOT #9306

Closed
Paladinium opened this issue Jan 21, 2018 · 61 comments
Labels
area: @angular-devkit/build-angular feature Issue that requests a new feature
Milestone

Comments

@Paladinium
Copy link

Paladinium commented Jan 21, 2018

This is a feature request referring to the prod build with AOT using the CLI. A more general title would be "Provide options to make the applied transformations configurable". For my use case, it is sufficient to just consider the two specific options described below - but other developers might have other use-cases.

Requirements

It would be very helpful to me and many other developers if the compiler options would provide means to control the removal of decorators in a prod build with AOT. There are two ways that could make it happen. Either in tsconfig.json as a parameter to angularCompilerOptions, e.g.:

  "angularCompilerOptions": {
    "aotRemoveDecorators": false
  }

Or as a switch to the ng-command, e.g.

ng build --prod --aotRemoveDecorators=false

The wording of such an option is yet to be discussed. I personally would favor to distinguish options that are only valid for either AOT or JIT by having a prefix/suffix in the option's name as indicated.

Additional Requirement

For the use-case described below, it is also important to replace style and template URLs by their content. This is what is done in JIT mode just a few lines above the referenced code (see link below). Now either the proposed option implies this behavior (which I don't like since it is not obvious) or another option gives the developer control for that behavior, too, e.g.

ng build --prod --aotRemoveDecorators=false --aotReplaceResources=true

Use-Case

There are Angular apps that use standard Angular means. To make those applications run fast, it makes sense to use AOT. However, many of those applications also have a need to build Angular components dynamically and invoke the JIT compiler. I am aware that it seems unusual to use JIT and AOT in one app, but it is also a reality that many applications have this need (just do a search for AOT and JIT in the angular github issues). It has also been shown that it is indeed possible to make AOT and JIT work together (e.g. see angular/angular#15510). However, since the CLI removes the decorators in a prod build, the JIT compiler cannot compile the components and is emitting errors like "Please add a @NgModule annotation".

To make it work nowadays, it requires to hand-craft the build yourself using webpack or another bundler, but it is much more complex. Instead, it would be much more convenient to rely on the Angular CLI and use the powerful ecosystem that the CLI provides. The cost of adding such an option seems to me negligible compared to the pain many developers have. And I would highly appreciate if the CLI team would consider it.

The code responsible for the removal

The code responsible for the removal is located here:

this._transformers.push(removeDecorators(isAppPath, getTypeChecker));

Workarounds

I don't know any workaround and I would be very happy to hear some. For example, I was wondering if it is possible to override the function "removeDecorators" to just do nothing? Any other ideas?

@Paladinium
Copy link
Author

Paladinium commented Jan 21, 2018

For the sake of completeness, I reference some of the issues I found that are related to this feature request (either because the decorators are removed or because developers have issues with AOT and JIT):

angular/angular#15510
angular/angular#20875
angular/angular#20864
#6866
#5359
#2799
#6992
#8525

@p3x-robot
Copy link

p3x-robot commented Jan 22, 2018

Yeah, I think we need both options, because we use pure webpack and don't use ng build --prod --removeDecorators=false at all, it would be just have a dual config the removeDecorators. I suppose.

@Paladinium Paladinium changed the title Provide an option to enable/disable the removal of decorators in a prod build with AOT Provide options to enable/disable the removal of decorators and replacement of resources in a prod build with AOT Jan 22, 2018
@ocombe
Copy link
Contributor

ocombe commented Jan 24, 2018

@hansl would you accept a pull request for that? If so, in what form: config parameter, cli parameter, something else?

@Paladinium
Copy link
Author

Thanks a lot, @ocombe , for providing help for this issue!

@Alekcei
Copy link

Alekcei commented Jan 27, 2018

Yes, it flag very need

@pawelkondraciuk
Copy link

pawelkondraciuk commented Jan 29, 2018

I see that some people did nasty hack to remove all decorators that come from @angular/core

function shouldRemove(decorator: ts.Decorator, typeChecker: ts.TypeChecker): boolean {
const origin = getDecoratorOrigin(decorator, typeChecker);
return origin && origin.module === '@angular/core';
}

There should be a better way to decide which decorators should be preserved than preserving all of them. For example, I only need decorated modules from a specific directory.

I did some research and it turns out that if you reexport NgModule, @ngtools will treat it as external decorator and preserve it :)

// ngmodule.decorator
import { NgModule } from '@angular/core';
export { NgModule };
import { NgModule } from './ngmodule.decorator';

@filipesilva filipesilva added the feature Issue that requests a new feature label Feb 1, 2018
@p3x-robot
Copy link

@pawelkondraciuk where do you put this?

image

import { NgModule } from '@angular/core';
export { NgModule };
import { NgModule } from './ngmodule.decorator';

Error

Running "webpack:cory-build-aot" (webpack) task
Hash: ea4a0b4e45dd70086af4                                                         
Version: webpack 3.10.0
Time: 2091ms
 2 assets
   [0] ./test/angular-webpack/angular/polyfills.ts 0 bytes {1} [built]
   [1] ./test/angular-webpack/angular/bundle.aot.ts 0 bytes {0} [built]

ERROR in test/angular-webpack/angular/ngmodule.decorator.ts(1,10): error TS2300: Duplicate identifier 'NgModule'.
test/angular-webpack/angular/ngmodule.decorator.ts(3,10): error TS2300: Duplicate identifier 'NgModule'.

@pawelkondraciuk
Copy link

Put import { NgModule } from './ngmodule.decorator'; in desired file where you want to preserve decorator

@p3x-robot
Copy link

Same error. :( I even added like this:

ngmodule.decorator.ts

import { NgModule, ModuleWithProviders } from '@angular/core';
export { NgModule, ModuleWithProviders };

module.ts

import {
    NgModule,
    ModuleWithProviders
} from './ngmodule.decorator';

@NgModule({
    imports: [
        etc,
    ],
    // export
    declarations: [
        etc,
    ],
    providers: [
        etc,
        forwardRef(() => Boot),
    ],
    exports: [
        etc,
    ],
    entryComponents: [],
})
export class CorifeusModule {

    constructor(private boot: Boot) {
        this.boot.boot();
    }

    public static forRoot(): ModuleWithProviders {
        return {
            ngModule: CorifeusModule,
//            providers: providers,
        };
    }
}

test module.ts

import { NgModule } from '../../../src/ngmodule.decorator';

@NgModule({
    imports: [
        HttpClientModule,
        BrowserModule,
        CorifeusModule,
        RouterModule.forRoot(routes),
    ],
    declarations: [
        Application,
        Http,
        Layout,
        Color,
        AuthHome,
        AuthLogin,
    ],
    providers: [
        SettingsService
    ],
    bootstrap: [ Application ]
})
export class Module {

    constructor(
        private settings: SettingsService,
    ) {
        const json = require('../json/settings.core.json');
        settings.extend('core', json);
    }
};

Same error

Running "webpack:cory-build-aot" (webpack) task
Hash: ea4a0b4e45dd70086af4                                                         
Version: webpack 3.10.0
Time: 3584ms
 2 assets
   [0] ./test/angular-webpack/angular/polyfills.ts 0 bytes {1} [built]
   [1] ./test/angular-webpack/angular/bundle.aot.ts 0 bytes {0} [built]

ERROR in : TypeError: Cannot read property 'getTsProgram' of undefined
  at AngularCompilerPlugin._getTsProgram (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/@ngtools/webpack/src/angular_compiler_plugin.js:188:62)
  at getTypeChecker (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/@ngtools/webpack/src/angular_compiler_plugin.js:521:43)
  at ast_helpers_1.collectDeepNodes.filter (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/@ngtools/webpack/src/transformers/remove_decorators.js:14:60)
  at Array.filter (<anonymous>:null:null)
  at standardTransform (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/@ngtools/webpack/src/transformers/remove_decorators.js:14:14)
  at transformer (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/@ngtools/webpack/src/transformers/make_transform.js:14:25)
  at /home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/typescript/lib/typescript.js:2888:86
  at reduceLeft (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/typescript/lib/typescript.js:2581:30)
  at /home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/typescript/lib/typescript.js:2888:42
  at transformRoot (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/typescript/lib/typescript.js:67711:82)
  at Object.map (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/typescript/lib/typescript.js:1879:29)
  at Object.transformNodes (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/typescript/lib/typescript.js:67699:30)
  at Object.emitFiles (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/typescript/lib/typescript.js:70529:28)
  at emitWorker (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/typescript/lib/typescript.js:74374:33)
  at /home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/typescript/lib/typescript.js:74334:66
  at runWithCancellationToken (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/typescript/lib/typescript.js:74425:24)
  at Object.emit (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/typescript/lib/typescript.js:74334:20)
  at defaultEmitCallback (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/@angular/compiler-cli/src/transformers/program.js:33:20)
  at AngularCompilerProgram.emit (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/@angular/compiler-cli/src/transformers/program.js:241:30)
  at AngularCompilerPlugin._emit (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/@ngtools/webpack/src/angular_compiler_plugin.js:754:49)
  at Promise.resolve.then.then.then (/home/patrikx3/Projects/patrikx3/corifeus/corifeus-web/node_modules/@ngtools/webpack/src/angular_compiler_plugin.js:587:54)
  at <anonymous>:null:null
  at process._tickCallback (internal/process/next_tick.js:160:7)


Child html-webpack-plugin for "index.html":
     1 asset
       [0] ./node_modules/html-webpack-plugin/lib/loader.js!./test/angular-webpack/index.html 261 bytes {0} [built]
Warning:  Use --force to continue.

Aborted due to warnings.


Execution Time (2018-02-03 13:40:18 UTC+1)
cory-ensure-protractor   1.8s  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 31%
loading grunt-webpack   312ms  ▇▇▇▇▇ 5%
webpack:cory-build-aot   3.7s  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 63%
Total 5.8s

It just removes some other decorators, but which??? It's cool for you it works :/

@p3x-robot
Copy link

p3x-robot commented Feb 3, 2018

Angular 2019.12.31-2359

@p3x-robot
Copy link

p3x-robot commented Mar 2, 2018

I looked at it on the latest @ngtools/webpack, but no change on the remove decorators function in 4 months.
With Angular 6, I cannot use AOT at all. So sad.
With Angular 4, I used AOT+JIT at once, by now, AOT is not even usable anymore.

@p3x-robot
Copy link

@Paladinium I think you help me a lot before with AOT and JIT at once, so here is a help for you:
https://gist.github.com/p3x-robot/e12ed76acb7033638b4179149546bb73

@p3x-robot
Copy link

p3x-robot commented Mar 8, 2018

@Paladinium besides, the aot on my pages.corifeus.com is double speed instead of jit so it works! 🔢

@p3x-robot
Copy link

i just upgraded everything, just removed the decorators and that's it, it works, like magic.

@Paladinium
Copy link
Author

Thanks, @p3x-robot . It's funny, we do the same workaround as you but with Gradle (which is our main build invoking the CLI - not going into details why) just after npm install. It is renaming removeDecorators by replaceResources in angular_compiler_plugin.js. One could also think of applying such a hack as part of the postinstall hook in packages.json if not using gradle. However, this is very unstable meaning that it can break anytime the CLI changes.

This is why I hope that @ocombe , besides being busy with many other tasks, can eventually help to solve this issue. In my view, he understands the pain and the use-cases described in this feature request.

@p3x-robot : with our help, others should now be able to figure out what to do. I highly appreciate that you shared it here! If you ask me, we should let the Angular guys do their job and avoid adding further comments.

@benjaminlefevre
Copy link

Could the commit of marcelamsler be merged on angular-cli? I need also to skip removal of decorators in aot mode :(

@p3x-robot
Copy link

#10059

@Paladinium
Copy link
Author

In my view, the commit is only 50% correct. For JIT to work, the resources must also be replaced which is the IF condition just above where @marcelamsler made his change where function 'replaceResources' is called. Therefore, instead of having a narrow argument like 'skipRemoveDecorators', maybe a more general like 'enableJitInAot' might provide a higher level of abstraction.

@benjaminlefevre
Copy link

Yep

I also noticed the build-optimizer option of angular-cli AOT compilation (default: true since Angular 5), is stripping the decorators as well :( so workaround for me, is to apply the hack above, and also to set build-optimizer to false. Definitively too much.....

@p3x-robot
Copy link

#10060

@p3x-robot
Copy link

but they are not allowing this merge...

@mlc-mlapis
Copy link

mlc-mlapis commented Apr 3, 2018

... just to notice ... using JitCompiler together with AOT mode is available right now -> and it means using any HTML code in run-time in fact. The only problem is CLI and its decorators removal when an app is built.

If any other way of compiling and building is used (Rollup, SystemJS, CC, ...) then it works just fine because decorators are contained in compiled code via ngc by default and it is necessary to remove them (regexp is the simplest way how to do it) if the minimum bundle sizing is required.

@laynelin
Copy link

Is there any update of this feature request?
I really wish this feature can be implemented.

@p3x-robot
Copy link

i can only this:

https://gist.github.com/p3x-robot/e12ed76acb7033638b4179149546bb73

plus i depreciated jit, i only use aot.
more thinking but you can use aot.
the worst is to rebuild on the server and thats it. so wicky to hack with jit , rebuild the app is the best i think, secure, fast, you will do the same thing hacking wih jit...

@p3x-robot
Copy link

for the size i use gzip... wicked quick pure aot...

@rodrigoEclipsa
Copy link

rodrigoEclipsa commented Apr 20, 2018

I found a viable solution to this problem. can create an angular element
Here is an example:
https://github.com/nrwl/playbook-angular-v6-elements-example

Then insert the script main.js and inline.js at run time and go!

you can create and compile components that can be loaded at run time, this technique does not need the application to know the components at compile time and get the benefits of the compiler

@marcelamsler
Copy link

marcelamsler commented Jul 9, 2018 via email

@theknet
Copy link

theknet commented Nov 2, 2018

@marcelamsler From your example above, do I need to know about the @app/aot.decorators?

At the moment during my runtime I'm getting a StaticInjectError:
Uncaught (in promise): Error: StaticInjectorError[n -> n]: StaticInjectorError(Platform: core)[n -> n]: NullInjectorError: No provider for n!

Could this be my DI for the Compiler from @angular/core?

@marcelamsler
Copy link

marcelamsler commented Nov 2, 2018

No, I also use the compiler from core directly. Make sure that all Modules for all components, pipes are passed in in "modules"-input. Also make sure, that your custom components you want to use (and their dependencies are using the re-exported decorators. Here is my current code with some minor improvements (supports replacing/changing template)

reexports

import { Component, Directive, HostListener, Input, NgModule, Pipe } from '@angular/core';

export { Component, Directive, HostListener, Input, NgModule, Pipe };

Here the dynamic component:

import { Compiler, EventEmitter, Input, Output, ViewChild, ViewContainerRef } from '@angular/core';
import { Component, NgModule } from '@app/aot.decorators';

/*
 This Component allows you to compile a template at runtime. Just pass in the template, the needed modules and a context. Make sure that
 context is not a complete component, but an object with attributes. Because you would overwrite internal attributes.
 e.g. <cu-dynamic [template]="details.html" [context]="{task: task}"></cu-dynamic>
 With the example above you could use a template like this <div>{{task.id}}</div>
 */
@Component({
    selector: 'cu-dynamic',
    template: '<ng-container #vc></ng-container>'
})
export class DynamicComponent {
    @ViewChild('vc', {read: ViewContainerRef}) viewContainer: ViewContainerRef;
    @Input() public modules: any[];
    @Output() public onTemplateError: EventEmitter<string> = new EventEmitter();
    private _context: any;
    private container;
    private _template: string;

    constructor(private compiler: Compiler) {
    }

    get context(): any {
        return this._context;
    }

    @Input()
    set context(value: any) {
        this._context = value;
        this.replaceComponentInDOM(this._template, this._context);
    }

    get template(): string {
        return this._template;
    }

    @Input()
    set template(value: string) {
        this._template = value;
        try {
            this.replaceComponentInDOM(this._template, this._context);
        } catch (e) {
            this.onTemplateError.emit(e.message);
        }
    }

    public static createComponent(compiler: Compiler, template: string, modules?: any[]): any {
        @Component({template})
        class TemplateComponent {
        }

        @NgModule({
            declarations: [TemplateComponent],
            imports: modules
        })
        class TemplateModule {
        }

        const mod = compiler.compileModuleAndAllComponentsSync(TemplateModule);
        const factory = mod.componentFactories.find((comp: any) =>
            comp.componentType === TemplateComponent
        );

        return factory;
    }


    private replaceComponentInDOM(template: string, context: any) {
        if (template && context) {
            const component = DynamicComponent.createComponent(this.compiler,
                template,
                this.modules
            );

            this.viewContainer.remove(0);
            this.container = this.viewContainer.createComponent(component);
            Object.assign(this.container.instance, context);
            this.container.changeDetectorRef.detectChanges();
        }
    }
}

@lefoulkrod
Copy link

@robwormald As others have expressed, there are companies (like mine) that want to use Angular but have use cases which would be very well served by being able to dynamically compile an html template into a component at runtime, knowing the risks and performance implications. You may want to reconsider this as a valid use case.

@lefoulkrod
Copy link

@marcelamsler I'm able to make this work without re-exporting the decorators. I only have to set build-optimizer=false. Of course this increases my output main.js file size by 40%.

@rodrigoEclipsa
Copy link

@lefoulkrod why not use angular element?

@Ilya93
Copy link

Ilya93 commented Dec 27, 2018

Can I dynamically compile an html template into a component at runtime without build-optimizer=false ? Any update of this issue?

@p3x-robot
Copy link

this is not something they want to care about ...
before i used this hack:
https://gist.github.com/p3x-robot/e12ed76acb7033638b4179149546bb73

@ngbot ngbot bot added this to the Backlog milestone Jan 24, 2019
@dmitry-kostin
Copy link

where is my angular 1.x ...

@PascalGit1
Copy link

Is there an update or work around for this issue yet?

@ayerix
Copy link

ayerix commented Oct 16, 2019

Here are some of my observations relative to this problem (all 3 ways implies aot compilation enabled and combination of lib with custom decorators and project importing it)

  1. the problem is not observed when project imports the lib (with custom decorators) directly non-compiled (not from node_modules, but from inner folder as sources)
  2. the problem is not observed when project imports the lib from node_modules, as external but disables optimization
  3. the problem is not observed when importing project compiles to 'es5' target with aot

The problem is raised only when these factors meet together: optimization enabled (by default), es2015 target (by default) and library with it's own decorators is external to the project (comes from node_modules)

Still very waiting this flag (preserving custom annotations from external node_modules lib)! Probably it may be done by providing some ability to export custom decorators with lib module same way as it is done for pipes and services?

Angular team, please pay attention to this, your users are unhappy without it! It's actually a kind of trap: you writing custom decorators and all works well until you develop all the stuff. Next you go live of your project and everything wraps into a singularity because angular decides you don't need them as well as they don't and "optimizing" it without any care or choose.

Just take a look at this instruction how to achive this functionality here: https://blog.angularindepth.com/converting-typescript-decorators-into-static-code-using-tsquery-tstemplate-and-transforms-8c65d606a517 It's a kind of Fifty Shades of Grey. Do you think it's normal to rewrite AST tree in 2019 to bring this to project?

@agrinko
Copy link

agrinko commented Nov 1, 2019

I have similar use-case: I need an Angular module which is compiled and stored on a remote server, and loaded into the main app in the runtime. Having in-browser compiler in the AOT build of the main app would solve the issue.

But since there is no solution for this yet (at least none of the solutions described above helped me, I was still getting other errors), I ended up with a different approach: compiling my remote module as an AOT bundle, using a custom builder script for that. And that is where this great article came handy: https://blog.angularindepth.com/building-extensible-dynamic-pluggable-enterprise-application-with-angular-aed8979faba5
This solution is more straightforward and requires fewer hooks and workarounds (basically, all hooks and workarounds end up in one place: custom builder). Although it requires time to set it up and run (took me ~5 hours).

@alan-agius4 alan-agius4 added needs: discussion On the agenda for team meeting to determine next steps triage #1 labels May 27, 2020
@dgp1130
Copy link
Collaborator

dgp1130 commented Jun 25, 2020

This came up in a CLI meeting recently. @robwormald's previous comment is still a pretty accurate description of our feelings on the matter. Using JIT in a prod application causes a lot of performance and security issues. JIT is intended for development use only, and we don't expect that to change anytime soon.

@dgp1130 dgp1130 closed this as completed Jun 25, 2020
@dgp1130 dgp1130 removed the needs: discussion On the agenda for team meeting to determine next steps label Jun 25, 2020
@agrinko
Copy link

agrinko commented Jun 26, 2020

Here's a link to a relevant discussion, if anybody still needs AOT-compilation of a separate module which is then included to the main application in the runtime via HTTP request: angular/angular#20875 (comment)

@angular-automatic-lock-bot
Copy link

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.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Jul 27, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: @angular-devkit/build-angular feature Issue that requests a new feature
Projects
None yet
Development

No branches or pull requests