Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular + AOT + Webpack + NgTools - Issue Generating ngFactory

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!

like image 583
NColey Avatar asked May 03 '17 20:05

NColey


1 Answers

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}!`);
});
like image 65
William Lahti Avatar answered Sep 24 '22 04:09

William Lahti