Angular + AOT + Webpack + NgTools - Issue Generating ngFactory
Asked Answered
Z

2

7

I've been trying to set up AOT with ngTools working in my Angular application (using Webpack for module loading) and having an absolute nightmare of a time of it. I've gone through the various resources in the docs here and here as well as read through the ngTools readme, looked at the example buried inside the Angular CLI (this app was not built with the Angular CLI but I thought it would be at least a useful reference) found via this resource on Angular + Webpack + Sass + ngTools (I use SASS as well but that compiles fine with my webpack.config.aot.js - it does block me from switching to ngc though, at least as far as I've found, which is what I've seen used in some examples), as well as every github issue or stackoverflow post on it I have found over the past few days and no matter what I do trying to build aot produces the following error:

 ERROR in window is not defined

 ERROR in /Users/nataliecoley/development/company-app-name/src/main-aot.ts (2,36): Cannot find module '../aot/src/app/app.module.ngfactory'.

 ERROR in ./src/main-aot.ts

Module not found: Error: Can't resolve '../aot/src/app/app.module.ngfactory' in '/Users/nataliecoley/development/company-app-name/src' @ ./src/main-aot.ts 2:0-73

For the time being I have been ignoring the first issue (in part because I can't seem to find anyone else online having that error and in part because I searched my entire codebase, excluding node modules, and window doesn't appear anywhere in the code) but I am open to suggestions if anyone knows where that might be coming from (or if you think its related to errors 2 & 3).

The second and third error messages though I think are the main source of my problems as it's pretty clear that app.module.ts isn't being compiled before main-aot.ts so by the time main-aot.ts goes to compile it cannot find the file. However, I set it up according to the documentation I've found specifically so that it will be already compiled and app.module.ngfactory ready to go when the compiler reaches main-aot.ts.

Clearly there is some piece missing in my setup and because every solution I find online is set up slightly differently I can't seem to narrow down the issue. All I know is that nothing I've tried so far removes this error (or produces any other new error) and I'd love some help from anyone who has successfully setup AOT + ngTools + webpack + Angular to narrow down what is going on here.

Here is my file structure (simplified):

.
+-- config/
|   +-- helpers.js
|   +-- webpack.common.js
|   +-- webpack.dev.js
|   +-- webpack.prod.js
+-- src/
|   +-- app/
|   +-- assets/
|   +-- vendor/
|   +-- index.html
|   +-- main.ts
|   +-- main-aot.ts
|   +-- polyfills.ts
|   +-- tsconfig.json
+-- package.json
+-- server.js
+-- tsconfig-aot.json
+-- webpack.config.aot.js
+-- webpack.config.js (only contains: module.exports = require('./config/webpack.dev.js');)

The command I am using to run my aot build (which I pulled from the setup in the first link I shared to the angular docs) is "build:aot": "webpack --config webpack.config.aot.js", in my package.json. As far as relevant package versions I am on 4.1.0 for all angular packages and I am using @ngtools/webpack v1.3.1, and webpack v 2.3.3 and typescript v2.3.0

Note: I have seen some issues brought up about aot and certain typescript/angular versions but we would really like to stick with these in part to stay in line with our other Angular app (in production for over a year now and currently on v4.1.0 with no interest in downgrading) as well as for things like support for strictnullchecks, etc. I know I've heard of people getting AOT working with these versions and it seems ridiculous that anyone would have to work with an older version of Angular just to use AOT so I feel like there has to be a way to get it working.

Here is my tsconfig-aot.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": [ "es2015", "dom" ],
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true,
    "removeComments": false,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "strictNullChecks": true,
    "skipLibCheck": true,
    "skipCodeGeneration": false,
    "typeRoots": [
      "../node_modules/@types/"
    ]
 },
  "files": [
    "src/app/app.module.ts",
    "src/main-aot.ts"
  ],
  "compileOnSave": true,
  "angularCompilerOptions": {
    "genDir": "aot",
    "entryModule": "src/app/app.module#AppModule",
    "skipMetadataEmit": true
  }
}

Here is my webpack.config.aot.js:

const ngtools = require('@ngtools/webpack');
const webpack = require('webpack');
const helpers = require('./config/helpers');


module.exports = {
  devtool: 'source-map',
  entry: {
    main: './src/main-aot.ts'
  },
  resolve: {
     extensions: ['.ts', '.js']
  },
  target: 'node',
  output: {
    path: helpers.root('dist'),
    filename: 'build.js'
  },
  plugins: [
    new ngtools.AotPlugin({
      tsConfigPath: helpers.root('tsconfig-aot.json'),
      entryModule: helpers.root('src', 'app', 'app.module#AppModule')
    }),
    new webpack.optimize.UglifyJsPlugin({ sourceMap: true })
  ],
  module: {
    rules: [
      {
         test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
         loader: 'file-loader?name=assets/[name].[hash].[ext]'
      },
      { test: /\.css$/, loader: 'raw-loader' },
      {
        test: /\.scss$/,
        loaders: ['to-string-loader', 'style-loader', 'css-loader', 'resolve-url-loader', 'sass-loader?sourceMap']
      },
      { test: /\.html$/, loader: 'raw-loader' },
      { test: /\.ts$/, loader: '@ngtools/webpack' }
    ]
  }
}

A note about the entryModule. In some resources I've seen state that the entryModule option must be in the tsconfig-aot.json, not inside the webpack.config.aot.js, other examples I've seen put it only in the webpack.config.aot.js. I have tried with it just in the tsconfig-aot.json just the webpack.config.aot.js and above in both - none of those options seem to resolve the issue. I've also tried editing the file paths of various things (like the files array in my tsconfig-aot.json) in case I set it up so it's looking in the wrong place for my files and got nowhere with that.

Here is my main-aot.ts:

import { platformBrowser }    from '@angular/platform-browser';
import { AppModuleNgFactory } from '../aot/src/app/app.module.ngfactory';

platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);

I'm worried it's just going to be a stupid little piece I missed over but I've been trying everything so far and it seems like a lot of other people online are also struggling with this setup and there doesn't seem to be any clear info online on how to do it right so I thought I'd post here in the hopes that someone who has actually successfully got aot working in an Angular + Webpack application can point me in the right direction or share a resource that helped them figure it out. Thanks in advance!

Zeta answered 3/5, 2017 at 20:33 Comment(5)
Hi @Aluan Haddad, I'm a little confused about what you mean by that? I am writing in typescript, the app I am trying to get AOT ready is an Angular + Typescript application. I was also under the impression that AOT was the recommendation in the Angular community to get more aligned with Google's build process. We would also like to use it for things like performance and reduced bundle size. Can you clarify what you mean about my IDE lying to me? I didn't specify which IDE I was using and quite frankly I don't totally understand your comment. Thanks.Zeta
AOT does not follow typescript semantics. TypeScript is a superset of ECMAScript. Decorators, as implemented in TypeScript are based on the ECMAScript decorators proposal. AOT changes the meaning of decorators in a breaking manner that is incompatible with TS and ES semantics.Cleruchy
I appreciate your follow up @AluanHaddad but it is my understanding that there are a number of people out there who have successfully gotten AOT to work with Angular & Typescript. I'm going to continue exploring a solution to my setup issues and hopefully someone will come along who can help me debug the issue that lead to my original question posted here. Thanks anyway.Zeta
Best of luck to you, just remember it's not typescriptCleruchy
Were you able to fix this? I have a similar issue.Watersoak
M
5

Note, from your resource:

I had some trouble with the path to the ngfactory code when compiling with ngc: Can't resolve './../path/to/app/app.module.ngfactory' This seems related to this issue. The solution for me was to define the genDir in the webpack config rather than the tsconfig.js file.

However, ngtools does all of this for you, as it is intended to be a complete blackbox AOT mechanism. You are blending ngc (ie ngfactory generation) with Angular CLI's AOT frontend (ngtools).

If you want to use ngfactories you have to build your app with ngc. Personally, I use ngtools for development builds and then use ngc along with an Angular Universal server-side renderer.

Note that ngtools has a bug that makes development rather painful- changes will not be recognized until the second recompilation: https://github.com/angular/angular-cli/issues/5137

How I use this stuff?

My webpack configs are much the same as yours, and then I use ngc in the production build to generate factories...

package.json:

"aot": "./node_modules/.bin/ngc -p tsconfig-aot.json",

And use the factories along with server-side rendering...

server.ts:

import 'reflect-metadata';
import 'zone.js/dist/zone-node';
import { renderModuleFactory } from '@angular/platform-server'
import { enableProdMode } from '@angular/core'
import { AppServerModuleNgFactory } from './dist/ngfactory/src/app/app.server.module.ngfactory'
import * as express from 'express';
import { readFileSync } from 'fs';
import { join } from 'path';

const PORT = process.env.PORT || 8081;

enableProdMode();

const app = express();

let template = readFileSync(join(__dirname, 'dist', 'index.html')).toString();

app.engine('html', (_, options, callback) => {
  const opts = { document: template, url: options.req.url };

  renderModuleFactory(AppServerModuleNgFactory, opts)
    .then(html => callback(null, html));
});

app.set('view engine', 'html');
app.set('views', 'src')

app.get('*.*', express.static(join(__dirname, 'dist')));

app.get('*', (req, res) => {
  res.render('index', { req });
});

app.listen(PORT, () => {
  console.log(`listening on http://localhost:${PORT}!`);
});
Mitch answered 7/5, 2017 at 10:36 Comment(0)
D
0

I can understand your struggle in generating the build via Webpack. I, Myself did a lot of research in the last few days, just After the release of Angular 5 As it is clear Angular 5 is now totally concentrating towards AOT Builds, and we should as well, as it is way to good to use for Production Builds.

I am not that accustomed to Angular yet, but have founds few key points regarding it's Build Procedure...

Typescript (and so does tsconfig.json) plays a key role in the Angular Builds...

NOTE: ANGULAR 4 Specific only

You can check this Angular-CLI video, though it will also not help u much to understand the build setup with Webpack...

First of all, few key points to remember while creating your own Build System using Webpack:

  • Version Numbers for Typescript/Angular Libraries matter very much, Check Angular Upgrade to find the suitable Version Numbers for your Build. I will suggest to go with Angular v4.4 and Typscript v2.5.3 exact Build Versions
  • Download the Latest @ngtools/webpack from NPM
  • Have a proper Configuration file for tsconfig.json for AOT Builds...
  • Imports in ur Applications should be using Relative Paths, or should use index.ts Barrels, (though using Barrels is very much Confusing, took a whole day of mine :( )

There are basically two ways of getting AOT builds from Webpack:

  • First, Using Angular-CLI Command Tool ngc -p tsconfig.json
  • Second, to Use Angulars Official @ngtools/webpack v^1.7.0. Latest NgTools are pretty good loaders, but for Old Builds of Angular (i.e., Angular versions below 4), It is better to use ngc-webpack, as loader (Though I have never tried this Loader, but it's thorough Documentation helped me a lot to understand Angular AOT Compilations)...

FIRST METHOD (I):

Check out this Youtube tutorial for understanding the setup...

TLDR; (Required File Gists can be found here)

  1. Create a bootstrap.aot.ts file in your Source Directory: bootstrap.aot.ts
  2. Create a proper tsconfig-aot.json file: tsconfig-aot.json
  3. Setup ur webpack.config.js for AOT, I will be sharing just a sample, not the whole file... Following File is webpack.prod.js and gets merged into webpack.common.js
  4. Setting Up ur package.json scripts properly, to run synchronously, few commands that will help generate the build... package.json

    npm run start:prod:test, is the command that u need from above to generate AOT Build... Explaination:

    1. Clean All Build Directories...
    2. Copy ur src/ files to build/
    3. Generate CSS from SASS, this is the main issue with ngc, even though in there Docs they state that they support SASS/LESS, they don't yet, Not Properly at least...
    4. Change Internal File TEXT '.scss' to '.css', ./config/replace-scss.js is as follows:
  5. Generate *.ngfactory.ts files. Generated files goto dist/ folder, which tsconfig.json decides. Here the Path for ngfactory files is crucial for bootstrap.aot.ts. If u get an error here, do remeber to check your folder structure....

Mine here was:

/
|---src/                             // Main Source Folder
    |---bootstrap.aot.ts
|---build/                           // Copied files and Sass->Css in here
|---dist/
  1. Run Webpack Build with typescript loaders, as it is, as till now, ngc has done the AOT Compilation...

NOTE: This method is quite hacky, and may break. Second method is more appropriate to work on.

SECOND METHOD (II):

DO Remeber to set relative path imports... Path to *.ngfactory.ts should be proper, check above 5. point

No changes required for bootstrap.aot.ts, tsconfig-aot.json, (though in tsconfig we can now remove files property)

webpack.prod.ts

package.json

Also, do confirm your Angular, ngtools and typescript versions... Mine were

"@angular/animations": "~4.4.4",
"@angular/common": "~4.4.4",
"@angular/compiler": "~4.4.4",
"@angular/core": "~4.4.4",
"@angular/forms": "~4.4.4",
"@angular/http": "~4.4.4",
"@angular/platform-browser": "~4.4.4",
"@angular/platform-browser-dynamic": "~4.4.4",
"@angular/router": "~4.4.4",
"core-js": "^2.5.1",
"immutable": "^3.8.2",
"rxjs": "^5.4.3",
"zone.js": "~0.8.18"
"@angular-devkit/build-optimizer": "^0.0.32",
"@angular/cli": "^1.5.5",
"@angular/compiler-cli": "^4.4.4",
"@ngtools/webpack": "^1.7.0",
"@types/jasmine": "2.5.45",
"@types/jquery": "^3.2.15",
"@types/node": "~6.0.60",
"angular2-template-loader": "^0.6.2",
"autoprefixer": "^7.1.4",
"awesome-typescript-loader": "^3.2.3",
"chalk": "^2.3.0",
"clean-webpack-plugin": "^0.1.16",
"codelyzer": "~4.0.1",
"compodoc": "0.0.41",
"concurrently": "^3.5.1",
"copy-webpack-plugin": "^4.0.0",
"cpx": "^1.5.0",
"cross-env": "^5.0.5",
"css-loader": "^0.28.7",
"extract-text-webpack-plugin": "^3.0.1",
"file-loader": "^0.11.2",
"html-webpack-plugin": "^2.30.1",
"node-sass": "^4.5.3",
"npm-run-all": "^4.1.1",
"postcss": "^5.2.11",
"postcss-loader": "^1.2.2",
"progress-bar-webpack-plugin": "^1.10.0",
"raw-loader": "^0.5.1",
"replace": "^0.3.0",
"rimraf": "^2.6.2",
"sass-loader": "^6.0.6",
"source-map-loader": "^0.2.1",
"style-loader": "^0.18.2",
"to-string-loader": "^1.1.5",
"ts-node": "~3.0.4",
"tslint": "~5.3.2",
"tslint-loader": "^3.5.3",
"typescript": "~2.5.3",
"uglify-js": "^3.0.28",
"webpack": "^3.8.1",
"webpack-bundle-analyzer": "^2.9.1",
"webpack-combine-loaders": "^2.0.3",
"webpack-dev-server": "^2.9.3",
"webpack-merge": "^4.1.0",
"webpack-notifier": "^1.5.0"

Above all setup is just for Production Builds, I have never done Testing properly, so not sure about webpack.test.js setup yet

I hope this helped u, If still u got some issue, u can give a sample repo to replicate the issue.

Further References: (Which I guess u have already looked into, but might be helpfull for somebody else, but they are all repos, anybody will have to go through there sources to understand what is happening)

Angular 5

I tried, but was getting this issue, which is still an open Issue. Though there are Hack to work on it, but I cannot mess with Production builds.

Will update the Answer whenever I get more details.

Above link will guide u to more links :P

Decemvirate answered 3/12, 2017 at 14:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.