angular / angular

Deliver web apps with confidence 🚀
https://angular.dev
MIT License
95.11k stars 24.9k forks source link

[4.0.0] bug(compiler-cli): Cannot use JitCompiler with AoT #15510

Closed lacolaco closed 3 years ago

lacolaco commented 7 years ago

I'm submitting a ... (check one with "x")

[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior Since 4.0.0, JitCompiler cannot be used with AoT compilation. It's exported from @angular/compiler but its compiled import path is @angular/compiler/src/jit/compiler

app.module.ts

import { NgModule, Compiler } from '@angular/core';
import { JitCompiler } from '@angular/compiler';

import { AppComponent } from './app.component';

@NgModule({
    declarations: [AppComponent],
    bootstrap: [AppComponent],
    providers: [
        {
            provide: Compiler, useExisting: JitCompiler,
        },
    ]
})
export class AppModule {
}

app.module.ngfactory.ts(summary)

import * as import0 from '@angular/core';
import * as import1 from './app.module';
import * as import2 from './app.component.ngfactory';
import * as import3 from '@angular/compiler/src/jit/compiler';
class AppModuleInjector extends import0.ɵNgModuleInjector<import1.AppModule> {
  _AppModule_0:import1.AppModule;
  __Compiler_1:any;
  constructor(parent:import0.Injector) {
    super(parent,[import2.AppComponentNgFactory],[import2.AppComponentNgFactory]);
  }
  get _Compiler_1():any {
    if ((this.__Compiler_1 == null)) { (this.__Compiler_1 = this.parent.get(import3.JitCompiler)); }
    return this.__Compiler_1;
  }

@angular/compiler/src/jit/compiler is not existing JS file (because of 4.0.0 FESM). webpack bundling crashes by this problem.

Expected behavior

Resolve JitCompiler from @angular/compiler

Minimal reproduction of the problem with instructions

https://github.com/laco0416/ngrepro-0001

  1. git clone
  2. npm install
  3. npm run ngc
  4. see the generated files

What is the motivation / use case for changing the behavior?

Please tell us about your environment: OSX

Alekcei commented 6 years ago

@gatimus you resolved problem?

william-lohan commented 6 years ago

No, I insured I had decorated with @NgModule as well, same results.

Error: Unexpected value 'e' imported by the module 'e'. Please add a @NgModule annotation.

  private createCompoent(template: string) {

    @CustomComponent({
      selector: 'dynamic-template',
      template: template
    })
    class DynamicComponent { }

    return DynamicComponent ;
  }

  private createModule(modules: any[], componentType: any) {
    const metadata = {
      imports: [...modules],
      declarations: [componentType]
    };

    @NgModule(metadata)
    @CustomNgModule(metadata)
    class DynamicModule { }

    return DynamicModule;
  }

  public async render(template: string) {
      const compoent = this.createCompoent(template);
      const module = this.createModule([RouterModule], compoent);
      const moduleWithComponentFactories = await this.compiler.compileModuleAndAllComponentsAsync(module);
      const compoentFactory = moduleWithComponentFactories.componentFactories[0];
      this.view.clear();
      this.view.createComponent(compoentFactory);
  }
Alekcei commented 6 years ago

I used your code. I did not get problem How you get this.compiler? What version you use in ng-cli and angular?

[alekcei@localhost AotAndJit]$ ng -v
    _                      _                 ____ _     ___
   /   \    _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
  / △  \   | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
 / ___  \   | | | | (_| | |_| | | (_| | |      | |___| |___ | |
/_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
               |___/
@angular/cli: 1.1.2
node: 8.1.2
os: linux x64
@angular/animations: 4.2.3
@angular/common: 4.2.3
@angular/compiler: 4.2.3
@angular/core: 4.2.3
@angular/forms: 4.2.3
@angular/http: 4.2.3
@angular/platform-browser: 4.2.3
@angular/platform-browser-dynamic: 4.2.3
@angular/router: 4.2.3
@angular/cli: 1.1.2
@angular/compiler-cli: 4.2.3
@angular/language-service: 4.2.3

I used new project

Alekcei commented 6 years ago

I updated my example to version 4.2.5 Now asks @CustomInput

Can't bind to 'from' since it isn't a known property of 'ui-select'.
1. If 'ui-select' is an Angular component and it has 'from' input, then verify that it is part of this module.
2. If 'ui-select' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
3. To allow any property add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component. ("king !!!!!!!!</ui-button>

Although @Input is

william-lohan commented 6 years ago

@Alekcei this.compiler is injected with:

export function compilerFactory() {
  return new JitCompilerFactory([{ useDebug: false, useJit: true }]).createCompiler();
}

providers: [{ provide: Compiler, useFactory: compilerFactory }]

versions

@angular/cli: 1.1.3
node: 8.1.3
os: win32 x64
@angular/animations: 4.2.5
@angular/common: 4.2.5
@angular/compiler: 4.2.5
@angular/core: 4.2.5
@angular/flex-layout: 2.0.0-beta.8
@angular/forms: 4.2.5
@angular/http: 4.2.5
@angular/material: 2.0.0-beta.6
@angular/platform-browser: 4.2.5
@angular/platform-browser-dynamic: 4.2.5
@angular/router: 4.2.5
@angular/cli: 1.1.3
@angular/compiler-cli: 4.2.5
@angular/language-service: 4.2.5

I'm going to try to duplicate with a simpler project.

Alekcei commented 6 years ago

I need your simple project, Plese add repo

william-lohan commented 6 years ago

@Alekcei I omitted what I though wasn't relevant. On line const module = this.createModule([RouterModule], compoent); I also imported an other module const module = this.createModule([RouterModule, MyModule], compoent);. It seems MyModule also needs to be decorated with both NgModule and CustomNgModule.

Alekcei commented 6 years ago

@gatimus Yes, This must be done for all components and module. I want to try to collect everything via webpack. He' don't remove decorators. It should not remove decorators. decorator @Input on fields also removed. I do not know how to create @CustomInput()

vasuneet commented 6 years ago

@Paladinium :I don't think we are actually getting what is required. Basically what all you are doing can be achieved with the following javascript code itself and we don't need runtime compiler for that. The aot bundled library file whenever be loaded it will take care to scan the document and rendered accordingly:

document.getElementById("#someidentifier").innerHTML = "<some-component [color]="\'red\'" [text]="\'Created with JIT\'">"

using templateContent you are actually adding component selector html only.

But when we say dynamically compiled and rendered component what we should be able to achieve like the following :

<some-component template="!html template-url or template-reference" controller="!typescript controller class reference=''>"

william-lohan commented 6 years ago

@vasuneet I did the following.

<div id="someidentifier"></div>
<a [routerLink]="['another-route']">some link</a>
const html = `<p>...some text...<a [routerLink]="['another-route']">some link</a>...some text...</p>`;
document.getElementById('someidentifier').innerHTML = html;

The "some link" out side the div navigated and the "some link" inside had no behavior.

Alekcei commented 6 years ago

document.getElementById('someidentifier').innerHTML = html; Equivalently [innerHtml] <div [innerHTML]="html">

vasuneet commented 6 years ago

@Alekcei I agree. the solution provided in attachment here is not using Runtime compiler actually. @Paladinium if you can provide the sample code of using RuntimeCompiler with AOT. It will be good.

alexzuza commented 6 years ago

@vasuneet https://github.com/alexzuza/angular2-build-examples/tree/master/ngc-webpack

vasuneet commented 6 years ago

@alexzuza thanks for sharing. This is example of dynamically injecting the already build component. I am looking for the solution for rendering dynamic component which template we will decide on runtime and so we also require runtimecompiler along with AOT. The provided solution is not the example of runtimeCompiler along with AOT.

alexzuza commented 6 years ago

@vasuneet This can be easily found in package.json https://github.com/alexzuza/angular2-build-examples/blob/master/ngc-webpack/package.json#L12-L13

Alekcei commented 6 years ago

@alexzuza value = `<span>Test interpolation {{5 + 6}}</span>`; If using complex component containg attributes. That it will not work.

alexzuza commented 6 years ago

@Alekcei What do you mean? Try it https://github.com/alexzuza/angular2-build-examples/blob/master/ngc-webpack/src/app/app.component.ts#L10

arjenbrandenburgh commented 6 years ago

@Alekcei I used your github project and had the same problem with the @Input decorator. I actually used a very simple workaround: instead of using @Input, I removed that entirely from the component and used 'inputs' instead.

So in your metadata, for example your button component, I would have:

let metaData = {
  selector: 'ui-button',
  template: `
      <button type="button" class="button" [disabled]="disabled"><ng-content></ng-content></button>
  `,
  styleUrls: ['./button.component.less'],
  inputs: [ 'disabled' ]
}
@Component(metaData)
@CustomComponent(metaData)
export class UiButtonComponent  {
    disabled: boolean = false;
}
Alekcei commented 6 years ago

@cybey Thank you! But I probably need to move to the webpack for prod build!

belafarinrod91 commented 6 years ago

@alexzuza, Thanks for your posting. But still, I need to add my dynamic components in the shared.module.ts - how would I be able to change this during runtime ? For instance compiling components from an external resource ?

p3x-robot commented 6 years ago

@Paladinium are you able to use AOT + JIT with Angular 5?

Paladinium commented 6 years ago

@p3x-robot : we're didn't upgrade yet and it will take a while because we're busy doing other stuff. So, I don't know.

@vasuneet I attached a ZIP to this issue a long time ago.

And to all the others: I had many discussion on this issue and provided a working example. I am glad if it works for you. If not, I don't care whether you think it works or not or whether it is using the JIT compiler in an AOT compiled app or not. Everything I knew about this topic is in my previous comments. From now on, I cannot provide any further help - sorry.

snarun commented 6 years ago

@Paladinium .. Are you using Jit Summaries for dynamic compilation of angular files at browser client? I am looking for a solution of resolving the angular application es5.js files at browser client using JIT Compiler. No success so far..

Jackclarify commented 6 years ago

@alexzuza , when I add the case in my code, it throw errorNo NgModule metadata found for 'DynamicHtmlModule'. at NgModuleResolver.webpackJsonp.../../../compiler/esm5/compiler.js.NgModuleResolver.resolve, but I see it has in section

   const compMetadata = new Component({
            selector: 'dynamic-html',
            template: this.html,
        });

image

app.module.ts section is

import { BrowserModule } from '@angular/platform-browser';
import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
import { ChildComponent } from './child/child.component';
import { HtmlOutlet } from './child/html-outlet';

import { NgModule, Compiler, COMPILER_OPTIONS, CompilerFactory } from '@angular/core';

export function createCompiler(compilerFactory: CompilerFactory) {
  return compilerFactory.createCompiler();
}

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent, ChildComponent, HtmlOutlet
  ],
  imports: [
    BrowserModule
  ],
  providers: [
    {provide: COMPILER_OPTIONS, useValue: {}, multi: true},
    {provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS]},
    {provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory]}],
  bootstrap: [AppComponent]
})
export class AppModule { }

package.json here

`["@angular/common": "^5.0.0",
        "@angular/compiler": "^5.0.0",
        "@angular/core": "^5.0.0",](url)
`

os: win10 nodejs 8.5.0 I add those in a small demo, it is ok, but it is failed when I add it to my project. Could you have a look? Thanks.

mlc-mlapis commented 6 years ago

@Jackclarify ... https://github.com/angular/angular/issues/20639#issuecomment-348109570

You cannot use the ngtools package (with or without the CLI), they use optimizations that remove metadata when you compile AoT modules. If you want to lazy load a module AoT compiled, you have to compile it with ngc directly, not with ngtools, and then bundle it with rollup, webpack, system js bundler or closure compiler.

https://github.com/angular/angular/issues/20639#issuecomment-348109829

If you think that the CLI should take this use case into consideration and offer a way to bundle without removing those metadata (and I think that they should) then open an issue on the CLI repository please.

Alekcei commented 6 years ago

cli version 1.5.0. dont remove decorators

Jackclarify commented 6 years ago

@Alekcei , thank you very much, it works. Thanks again, but we might not use if the latest version is not supported. I will appreciate that when cli will not remove decorators from now on.

And thanks @mlc-mlapis too.

Alekcei commented 6 years ago

@Jackclarify
Need to be angular cli, added a flag

Jackclarify commented 6 years ago

@mlc-mlapis Dose it mean that aot and jit compile cannot work together right now, by using ngtools(angular/cli) only?

Jackclarify commented 6 years ago

@Alekcei What do you mean? I'm not clear. And I'm sorry, I find I was wrong by using angular/cli v1.6.6, it can not work. :(

Paladinium commented 6 years ago

Guys, the CLI is removing the meta information that the JIT compiler needs. If you want this to be changed, I recommend that you upvote this CLI issue: https://github.com/angular/angular-cli/issues/9306

mlc-mlapis commented 6 years ago

@Jackclarify ... I don't know anything that it is possible to use CLI 1.5.0 (as @Alekcei wrote here ... https://github.com/angular/angular/issues/15510#issuecomment-359837053).

I just know that there is an open issue https://github.com/angular/angular-cli/issues/9306 that is trying to solve the problem with removing the decorators.

We don't use NgTools / CLI so ... it doesn't affect us ... but I understand that it is a real problem for many others who use it.

matt-duch commented 6 years ago

I'm running into an issue with AOT + Lazy Loaded Modules + JIT @angular 5.1.2 @angular/cli 1.6.6 webpack 3.10.0

Using

new ngToolsWebpack.AngularCompilerPlugin({
            tsConfigPath: helpers.root('tsconfig.json'),
            entryModule: ...,
            sourceMap: true
        }),

If you try to run an app with the JIT Compiler:

export function createCompiler(compilerFactory: CompilerFactory) {
    return compilerFactory.createCompiler();
}
[
        { provide: COMPILER_OPTIONS, useValue: {}, multi: true },
        { provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
        { provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory] },
]

the module loader looks for lazy.mod#LazyMod instead of lazy.mod.ngfactory#LazyModNgFactory (this works when not trying to use the JitCompiler). Manually updating those references to use lazy.mod.ngfactory#LazyModNgFactory then leaves you with Error: No NgModule metadata found for '[object Object]' where the object is LazyMod. I've modified the angular_compiler_plugin to not strip out decorators, and I can see the difference in the output (this is present only with the modification of keeping all decorators):

LazyMod = __decorate([
        Object(__WEBPACK_IMPORTED_MODULE_0__angular_core__["K" /* NgModule */])({
            imports: [...],
            declarations: [...],
            entryComponents: [...]
        })
], LazyMod);

but I still see the same error. I don't think keeping the decorators is the only step required to fix this issue for Angular 5 + Lazy Loading + JIT. Any ideas as to what I could be missing? Any other details I can provide?

I can get a fully functioning app with lazy loading up and running (minus the ability to compile dynamic components), but adding the JIT Compiler breaks with the above issues.

mlc-mlapis commented 6 years ago

@matt-duch ... SystemJsNgModuleLoader or NgModuleFactoryLoader detects if Angular compiler is available through DI. If yes, then it supposes that JIT module is wanted to be loaded ... so 'app/mymodule#MyModule' loads mymodule.js. If no, then it supposes that AOT module is wanted ... so 'mymodule.ngfactory.js' is loaded automatically.

What I am missing is the reason why to lazy load JIT modules at all when the main reason why to use JIT compiling is the ability to get (or construct) any string which contains HTML template (from CMS for example) and compile it during run-time in a browser.

matt-duch commented 6 years ago

@mlc-mlapis Sorry for any confusion. Everything is AOT compiled (main app, and lazy loaded feature modules so all templates are inlined). They internally (in one place) make use of a component that renders tables dynamically based on database configurations. In order to provider a Compiler for that one component, I'm adding the JitCompilerFactory as seen in various threads around using JIT compiled dynamic components within AOT apps. If there's a better way to provider a JitCompiler for that component, I'd be happy to try. Or, is there a way to supply the JITCompiler as above, and tell the Loader to treat the lazy loaded modules as AOT anyways?
Separate from this, I attempted to create a JitCompiler under a const JitCompiler = new InjectionToken('JitCompiler'), which the dynamic (JIT based) table component used instead of the default Compiler injectable, but the directives it depends on (paging, loading helpers, etc) then JIT compiled using templateUrl, rather than using the already AOT compiled version present in the bundles.

laharshah commented 6 years ago

Reply to the Comment

I am on Angular 4.3.3

I read the conversation above but failed to get the actual breakpoint or the fix. It would be really helpful if someone explains why it wasn't working for many and then as mentioned in the comment it worked by looking at the code shared.

Really Appreciate this discussion! Thank you.

Here is the stackoverflow question with the thing I have tried.

Render-html-element-stringwith-the-custom-directive-on-element-angular-4-3

Jackclarify commented 6 years ago

Hi everyone, I convert the dynamic template module to a ng5 lib and it work, but I do not know why it is ok, is the reason that it export the decorators by NgxDynamicOutletModule? Can anyone help me? Thanks.

ngx-dynamic-outlet.umd.js: ` var NgxDynamicOutletModule = (function () { function NgxDynamicOutletModule() { }

NgxDynamicOutletModule.forRoot = function () {
    return {
        ngModule: NgxDynamicOutletModule,
    };
};
NgxDynamicOutletModule.decorators = [
    { type: NgModule, args: [{
                imports: [
                    CommonModule
                ],
                declarations: [
                    HtmlOutlet
                ],
                exports: [
                    HtmlOutlet
                ],
                providers: [
                    { provide: COMPILER_OPTIONS, useValue: ɵ0, multi: true },
                    { provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
                    { provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory] }
                ]
            },] },
];
NgxDynamicOutletModule.ctorParameters = function () { return []; };
return NgxDynamicOutletModule;

}()); ` https://github.com/fresherchen/ngx-dynamic-outlet https://www.npmjs.com/package/ngx-dynamic-outlet

mlc-mlapis commented 6 years ago

@Jackclarify ... do you mean the line export * from './html-outlet'; ?

Jackclarify commented 6 years ago

@mlc-mlapis thanks for your replay, I think it is not only for the line export * from './html-outlet';, the compile tool (ng tool in my project, and ngc in the lib package) might be the real reason, I'm not sure, I will try to verify it.

Alekcei commented 6 years ago

There is an option, take a viewDef(html), and renderType(styles) from the factories created by the AOT compiler. At the same time, JIT will skip the compilation

rdkmaster commented 5 years ago

@Jackclarify I've tried your repo located here, it works under a certern condition only, I use the code in the playgroud dir of the repo, it works, without or with -aot -prod build option.

When I try a more complex scenario, and it doesn't work. I did the following things:

1. makes the dynamic html complex

I added an angular components lib named jigsaw to the origin playgroud code, it looks like this after the modification:

    content = `<a jigsaw-button (click)="showQuestion(&apos;fever chills normal&apos;)">
        I have a fever/chills. Is this normal?
        </a>`;

I add a directive provided by jigsaw named jigsaw-button to the a tag.

2. add the source of html-outlet to the project, not imported them from node_modules

I did this because the origin html-outlet code do not import jigsaw's module, I need to add the jigsaw's module to the dynamic module to be compiled to make sure the jigsaw-button directive works correctly.

after these 2 steps, the code can be compiled, and, I get a runtime exception with -aot -prod compile option:

ERROR Error: No NgModule metadata found for 'r'.
    at t.resolve (vendor.e1e804e278036e05e784.bundle.js:1)
    at t.getNgModuleMetadata (vendor.e1e804e278036e05e784.bundle.js:1)
    at t._loadModules (vendor.e1e804e278036e05e784.bundle.js:1)
    at t._compileModuleAndAllComponents (vendor.e1e804e278036e05e784.bundle.js:1)
    at t.compileModuleAndAllComponentsAsync (vendor.e1e804e278036e05e784.bundle.js:1)
    at t.compileModuleAndAllComponentsAsync (vendor.e1e804e278036e05e784.bundle.js:1)
    at n.ngOnChanges (main.e2fa971268483487ba40.bundle.js:1)
    at vendor.e1e804e278036e05e784.bundle.js:1
    at vendor.e1e804e278036e05e784.bundle.js:1
    at jo (vendor.e1e804e278036e05e784.bundle.js:1)

BTW, the modified code works without -prod -aot compile option...

3. hack into compiled html-outlet code, and it works again

After a lot of retries, I think maybe the precompiled html-outlet code in the node_modules could be the magic key, since I can not rebuild the repo by the way provided by the repo npm run build, I started to hack the compiled html-outlet code, I added the reference to jigsaw and add JigsawButtonModule to the dynamic module's import array. After recompiled the testing app, I found it works!!

BUT Why??? anyone can help to answer the question?

Jackclarify commented 5 years ago

@rdkmaster , unfortunately, my repo is inherited from ngx-dynamic-template. It can only achieve sample function, the directive/pipe will not work when importing to this module. The way might be work is that trying to implement the feature which directive/pipe did before importing to this module.

gmaggiodev commented 5 years ago

@IgorMinar @tbosch Any news about this issue? We are really in trouble because we have an application with a huge dynamic part that use JIT Compiler but have a startup delay (15 secs.). We need AOT to speedup the initial loading but we need also JIT Compiler!!! Please don't leave us alone... in the dark!

akvaliya commented 4 years ago

Is there any plan to support this?

alxhub commented 4 years ago

Hi all,

Indeed, JIT and AOT in Angular with the View Engine runtime were independent - it wasn't really possible to mix and match the two. This was a systemic constraint, not a bug in the implementation.

The good news is that a major design goal for Angular Ivy was to support JIT/AOT interop. In Angular v9+ it's possible to build applications which are AOT compiled but consume dynamic, JIT-compiled components, or even the other way around if desired. There are still a few hiccups (#37216 for example) but overall the support is there.

maxtacco commented 4 years ago

Thank you @alxhub. Just like you said Angular9 works fine for us in AOT mode with some JIT dynamic components. The only issue left is devkit/terser optimizations during AOT builds.

mehtadhaval commented 3 years ago

In Angular v9+ it's possible to build applications which are AOT compiled but consume dynamic, JIT-compiled components, or even the other way around if desired.

@alxhub : I couldn't find any example of this. Can you point me in right direction ?

alan-agius4 commented 3 years ago

Hi all,

AOT+JIT interop with any build optimizations is not a supported path and we don't recommend this approach to be used for production. The main reasons are that this approach increases drastically the bundle sizes, performance overhead and exposes your application to cross-site scripting (XSS) since compiling components at runtime is a XSS vulnerability in itself.

I created an issue to capture and document this: https://github.com/angular/angular/issues/39624

angular-automatic-lock-bot[bot] commented 3 years ago

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.