Skip to content

Dynamic require only works after hot reload / incremental build #6629

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

Closed
pierrebeaucamp opened this issue Jun 9, 2017 · 9 comments
Closed
Assignees
Labels
P5 The team acknowledges the request but does not plan to address it, it remains open for discussion severity1: confusing

Comments

@pierrebeaucamp
Copy link

pierrebeaucamp commented Jun 9, 2017

Bug Report or Feature Request (mark with an x)

- [x] bug report -> please search issues before submitting
- [ ] feature request

Related issues might be #5275 and / or #4431

Versions.

@angular/cli: 1.0.0 (e)
node: 7.8.0
os: linux x64
@angular/common: 4.1.3
@angular/compiler: 4.1.3
@angular/core: 4.1.3
@angular/forms: 4.1.3
@angular/http: 4.1.3
@angular/platform-browser: 4.1.3
@angular/platform-browser-dynamic: 4.1.3
@angular/router: 4.1.3
@angular/cli: 1.0.0
@angular/compiler-cli: 4.1.3

Also reproducible using @ngtools/webpack version 1.4.1, 1.4.0, 1.2.7. I did not check any version before 1.2.7.

Further, the bug was also reproduced on OSX.

Repro steps.

After running ng new, I modified the following files like this:

src/app/app.component.html
<h1>
  {{title}}
</h1>

{{foobar}}
src/app/app.component.ts
import { Component } from '@angular/core';

declare function require(string):string;

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  foobar:string;

  constructor() {
    let filename = 'foobar';
    this.foobar = require('./' + filename + '.html');
  }
}
src/app/foobar.html
This should be dynamically required

Then, ng serve. I also tested this with ng eject and npm start (same behavior).

The log given by the failure.

ERROR Error: Cannot find module "."
Stack trace:
webpackMissingModule@http://localhost:4200/main.bundle.js:58:67
AppComponent@http://localhost:4200/main.bundle.js:58:34
createClass@http://localhost:4200/vendor.bundle.js:11809:26
createDirectiveInstance@http://localhost:4200/vendor.bundle.js:11643:37
createViewNodes@http://localhost:4200/vendor.bundle.js:13006:49
createRootView@http://localhost:4200/vendor.bundle.js:12911:5
callWithDebugContext@http://localhost:4200/vendor.bundle.js:14126:39
debugCreateRootView@http://localhost:4200/vendor.bundle.js:13586:12
ComponentFactory_.prototype.create@http://localhost:4200/vendor.bundle.js:10832:37
ComponentFactoryBoundToModule.prototype.create@http://localhost:4200/vendor.bundle.js:4368:16
ApplicationRef_.prototype.bootstrap@http://localhost:4200/vendor.bundle.js:5952:40
PlatformRef_.prototype._moduleDoBootstrap/<@http://localhost:4200/vendor.bundle.js:5741:72
PlatformRef_.prototype._moduleDoBootstrap@http://localhost:4200/vendor.bundle.js:5741:13
PlatformRef_.prototype._bootstrapModuleFactoryWithZone/</</<@http://localhost:4200/vendor.bundle.js:5703:21
ZoneDelegate.prototype.invoke@http://localhost:4200/polyfills.bundle.js:2835:17
onInvoke@http://localhost:4200/vendor.bundle.js:5069:28
ZoneDelegate.prototype.invoke@http://localhost:4200/polyfills.bundle.js:2834:17
Zone.prototype.run@http://localhost:4200/polyfills.bundle.js:2585:24
scheduleResolveOrReject/<@http://localhost:4200/polyfills.bundle.js:3262:52
ZoneDelegate.prototype.invokeTask@http://localhost:4200/polyfills.bundle.js:2868:17
onInvokeTask@http://localhost:4200/vendor.bundle.js:5060:28
ZoneDelegate.prototype.invokeTask@http://localhost:4200/polyfills.bundle.js:2867:17
Zone.prototype.runTask@http://localhost:4200/polyfills.bundle.js:2635:28
drainMicroTaskQueue@http://localhost:4200/polyfills.bundle.js:3028:25

ERROR CONTEXT Object { view: Object, nodeIndex: 1, nodeDef: Object, elDef: Object, elView: Object }

Desired functionality.

localhost:4200 should show:

app works!
This should be dynamically required 

Mention any other details that might be useful.

The really weird thing about this bug is that it only occurs on the first build of the file. While ng serve or npm start is running, I can open src/app/app.component/ts and save it (without any changes). The app compiles and works just fine.


Edit:

I tried using System.import instead of require, but the same problem still exists. Doesn't work on first build, works after saving the file while ng is watching.

Edit 2:

Explicitly disabling aot doesn't help either (using ng serve --no-aot, ng serve --aot false, or ng serve --aot=false

Edit 3:

I experimented a bit more with System.import. My component looks like this:

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

declare var System:any;

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  foobar:string;

  constructor() {
    let filename = 'foobar';
    System.import('./' + filename + '.html').then((file) => {
      this.foobar = file;
    });
  }
}

On first compilation, the output is:

** NG Live Development Server is running on http://localhost:4200 **
Hash: 9b75b2c6426cd3555cd8
Time: 12211ms
chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 160 kB {4} [initial] [rendered]
chunk    {1} main.bundle.js, main.bundle.js.map (main) 4.32 kB {3} [initial] [rendered]
chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 9.77 kB {4} [initial] [rendered]
chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.4 MB [initial] [rendered]
chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]
webpack: Compiled successfully.

Then, if I open the file and save it, the output of webpack is:

webpack: Compiling...
Hash: d72262bdb116e279e8db
Time: 455ms
chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 160 kB {4} [initial]
chunk    {1} main.bundle.js, main.bundle.js.map (main) 4.32 kB {3} [initial] [rendered]
chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 9.77 kB {4} [initial]
chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.4 MB [initial]
chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry]
chunk    {5} 5.chunk.js, 5.chunk.js.map 58 bytes {1} [rendered]
webpack: Compiled successfully.

As you can see, the incremental build includes a chunk file (and everything works as expected).

Edit 4:

ng build and serving it using a standalone server doesn't solve the problem.

Edit 5:

Since System.import is deprecated in webpack, I tried using require.ensure, however I get the exact behavior as previously with System.import

Edit 6:

I tried using typescript 2.4.0, since it supports native import statements. I had to change the module compiler option in src/tsconfig.app.json for it to compile, but didn't have much luck - it appears to be broken in a different way than this bug and I couldn't get it to work at all.

Edit 7:

Same behavior using require.context.

@Brocco
Copy link
Contributor

Brocco commented Jun 14, 2017

require('./' + filename + '.html') isn't analyzable because it can't determine the value of filename at build time.

There may be something I'm missing here, but it doesn't appear to be logical.

@Brocco Brocco self-assigned this Jun 14, 2017
@Brocco Brocco added P5 The team acknowledges the request but does not plan to address it, it remains open for discussion severity1: confusing labels Jun 14, 2017
@pierrebeaucamp
Copy link
Author

I can see how the dynamic require can be a bit confusing, but as per #6164 it seems like angular cli accounts for System.import.

@TheLarkInn
Copy link
Member

TheLarkInn commented Jun 14, 2017

It would probably help if there was a more hinting context also.

For example your context is specified as "./", instead you you should try for something more like

require(`./templates/${fileName}.html`)

Additionally @Brocco is correct, this isn't really the recommended way to use ContextModules with webpack. Instead you should be writing something like this.

const getLazyFile = (fileName) => System.import(`./templates/${fileName}.html`);

//.....

constructor() {
  getLazyFile("seanThomasLarkin").then((module) => {
     doSomethingWithThis(module);
  });
}

Also, System.import isn't officially dropped from webpack 3 yet. So I would recommend using System.import while you can, and then migrate to import('').

It would be an easy enough to write a friendly codemod or script to convert them all when the time comes. Even the cli team could write one for when TS 2.4 becomes the default.

@pierrebeaucamp
Copy link
Author

A different context doesn't help, I used ./ above to have small, simple example to replicate the behavior.

Further, I tried System.import and similar flavors of already, and all suffer from the same problem. Even with your implementation:

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

declare var System:any;
const getLazyFile = (filename) => System.import(`./${filename}.html`);

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  foobar:string;

  constructor() {
    getLazyFile("foobar").then((file) => {
      this.foobar = file;
    });
  }
}
  1. ng serve
❱ ng serve
** NG Live Development Server is running on http://localhost:4200 **
Hash: 63b02d33648a134691b8
Time: 13663ms
chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 160 kB {4} [initial] [rendered]
chunk    {1} main.bundle.js, main.bundle.js.map (main) 4.24 kB {3} [initial] [rendered]
chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 9.77 kB {4} [initial] [rendered]
chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.4 MB [initial] [rendered]
chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]
webpack: Compiled successfully.
  1. Page doesn't work, error messages regarding "module not found" in the browser console.
  2. Open the component file while ng serve is still watching and save it again, thus triggering an incremental build:
webpack: Compiling...
Hash: da590111e6d74ee0e301
Time: 1078ms
chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 160 kB {4} [initial]
chunk    {1} main.bundle.js, main.bundle.js.map (main) 4.24 kB {3} [initial] [rendered]
chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 9.77 kB {4} [initial]
chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.4 MB [initial]
chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry]
chunk    {5} 5.chunk.js, 5.chunk.js.map 58 bytes {1} [rendered]
webpack: Compiled successfully.

This time an additional chunk for lazy loading was correctly created and the app works just fine.

So why was the issue closed? It still exists using System.import?

@pierrebeaucamp
Copy link
Author

@Brocco and/or @TheLarkInn any chance this issue is getting re-evaluated? If no, could you please give me a clear answer on why this is not being considered an issue? I don't want to appear as rude but if I don't hear back in this thread, I will file the issue again, as it's blocking our team from migrating to angular-cli.

@smasala
Copy link

smasala commented Oct 13, 2017

@pierrebeaucamp We can confirm a similiar issue (#6629 (comment)). When trying to lazy load .json files, AoT compiler finds only a limited number of files. However, any changes made after that which the watcher is aware of and the changed lazy loaded files are included in the re-compilation.

@smasala
Copy link

smasala commented Oct 13, 2017

We think we've (@markuskeunecke) identified the problem. If you want to load dynamic files with AoT such as .json - the file which is to be loaded dynamically is not allowed to have any other .ts file in the same directory or any of it's child directories. Our assumption is that tree-shaking occurs first on all directories where .ts files are found, then the dynamic imports are evaluated by which point any previously iterated directories are ignored. This is why after a hot reload the file is found because no metadata for that current directory has been created and the AoT doesn't tree-shake for the new file/directory.

@pierrebeaucamp
Copy link
Author

Thanks for reporting a possible solution!

@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 Sep 7, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
P5 The team acknowledges the request but does not plan to address it, it remains open for discussion severity1: confusing
Projects
None yet
Development

No branches or pull requests

4 participants