16

The latest release of Angular2 allows for Ahead of time (AOT) compilation, using this code in your app.bootstrap.ts file:

// The browser platform without a compiler
import { platformBrowser } from '@angular/platform-browser';

// The app module factory produced by the static offline compiler
import { AppModuleNgFactory } from './app.module.ngfactory';

// Launch with the app module factory.
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);

Angular2 Official Documentation

How can we integrate Webpack and Typescript loaders with Angular2's AOT compiler?

It seems there might not be an option to do so yet, but I'm asking the question on Stack overflow so when it is available, the answer can be easily found.

UPDATE 10/12/16 - I got it working, see my answer below.

Community
  • 1
  • 1
TetraDev
  • 16,074
  • 6
  • 60
  • 61
  • I'm having same issues - found a solution?? – Spock Sep 16 '16 at 16:29
  • Here's another reference, you'll note that these guys are all using Webpack 2 beta github.com/manfredsteyer/angular2-aot-webpack2-rollup I am not doing AoT but I am using Webpack 2 and toying with the notion of AoT for my prod build. Also https://www.softwarearchitekt.at/post/2016/09/18/angular-2-aot-compiler-and-tree-shaking-with-webpack2-and-or-rollup-step-by-step.aspx – silentsod Oct 12 '16 at 21:08
  • If you don't need .NET here is a barebones solution https://github.com/blacksonic/angular2-aot-webpack , and an alternative one with Webpack plugins (no need to modify entry point) https://github.com/blacksonic/angular2-aot-cli-webpack-plugin (internally uses plugin og Angular CLI) – VuesomeDev Mar 10 '17 at 08:30

2 Answers2

8

I got it working finally, see my repo Angular2 Webpack2 DotNET Starter

There are several tricks necessary. Note that AOT compilation does not support any require() statements in your Angular 2 components. They will need to be converted to import statements.

First, you need to have a second tsconfig.json file, with special options for AOT compilation. I designate this with .aot.json extension.

tsconfig.aot.json :

{
   "compilerOptions": {
      "target": "es5",
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true,
      "allowSyntheticDefaultImports": false,
      "noEmitHelpers": true,
      "pretty": true,
      "strictNullChecks": false,
      "baseUrl": ".",
      "sourceMap": true,
      "sourceRoot": ".",
      "lib": [
         "es6",
         "dom"
      ],
      "types": [
         "lodash",
         "hammerjs",
         "jasmine",
         "node",
         "selenium-webdriver",
         "source-map",
         "uglify-js",
         "webpack",
         "materialize-css",
         "jquery",
         "kendo-ui"
      ],
      "typeRoots": [
         "./node_modules/@types"
      ],
      "outDir": "./compiled/src"
   },
   "exclude": [
      "./node_modules",
      "./**/*.e2e.ts",
      "./**/*.spec.ts",
   ],
   "awesomeTypescriptLoaderOptions": {
      "useWebpackText": true,
      "forkChecker": true,
      "useCache": true
   },
   "compileOnSave": false,
   "buildOnSave": false,
   "atom": {
      "rewriteTsconfig": false
   },
   "angularCompilerOptions": {
      "genDir": "./compiled/aot",
      "debug": true
   }
}

You'll also need the exact right combination of verions of Angular2. @angular/core@2.0.2 and @angular/common@2.0.2 did NOT work for me, I had to use 2.0.0 for both or ngc failed to compile the AOT files. Here's what I'm using successfully:

package.json :

  "dependencies": {
    "@angular/core": "2.0.0",
    "@angular/common": "2.0.0",
    "@angular/compiler": "2.0.0",
    "@angular/compiler-cli": "0.6.2",
    "@angular/forms": "^2.0.1",
    "@angular/http": "2.0.0",
    "@angular/platform-browser": "2.0.0",
    "@angular/platform-browser-dynamic": "2.0.0",
    "@angular/platform-server": "2.0.0",
    "@angular/router": "3.0.0",
    "@angular/tsc-wrapped": "0.3.0"
}

In addition, you'll need a couple nifty webpack loaders, while also allowing webpack to look in the ./src folder as well as the folder your AOT compiled files are output to. (*.component.ngfactory.ts)

That last part is very important! If you don't tell webpack to include it those folders, it won't work. In this example, the AOT files are output to /aot-compiled in the root folder.

webpack.common.js

  loaders: [
     {
        test: /\.ts$/,
        include: [helpers.paths.appRoot, helpers.root('./compiled/aot')],
        exclude: [/\.(spec|e2e)\.ts$/],
        loaders: [
           '@angularclass/hmr-loader',
           'awesome-typescript-loader',
           'angular2-template-loader',
           'angular2-router-loader?loader=system',
           "angular2-load-children-loader" // this loader replaces loadChildren value to work with AOT/JIT
        ],
     },
  ]

To generate your AOT files, you'll need an NPM script to do it for you

package.json

   "scripts": {
      "compile:aot": "./node_modules/.bin/ngc -p ./tsconfig.aot.json",
   }

You'll also need to make your webpack config read the AOT version of app.bootstrap.ts - which is different from the JIT version. I differentiate it with .aot.ts extension so that in production, webpack uses AOT (app.bootstrap.aot.ts), but in dev mode, it uses JIT with webpack-dev-server (app.bootstrap.ts).

Finally, you run npm run compile:aot FIRST. Once your AOT files are output to disk, you run your webpack build with either webpack or webpack-dev-server.

For a working example, see my repo Angular2 Webpack2 DotNET Starter. It's integrated with .NET Core 1.0, but for those not using .NET, you can still see how Webpack 2 and Angular 2 are configured.

Community
  • 1
  • 1
TetraDev
  • 16,074
  • 6
  • 60
  • 61
  • 1
    Quick tip: in *package.json* scripts there's no need to specify `./node_modules/.bin/` path, so the following should be enough: `"compile:aot": "ngc -p ./tsconfig.aot.json",` – superjos Oct 14 '16 at 09:37
  • Thanks, thats true for some cases. However if I call the `compile:aot` script in a chain from another `npm run` script, then it says "don't know what ngc is" so I had to tell it. But in this pure example, you're right :-) – TetraDev Oct 14 '16 at 21:53
1

tsconfig.json

    {
      "compilerOptions": {
        "target": "es5",
        "module": "es2015",
        "moduleResolution": "node",
        "sourceMap": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "lib": ["es2015", "dom"],
        "noImplicitAny": true,
        "suppressImplicitAnyIndexErrors": true,
        "typeRoots": [
          "./node_modules/@types/"
        ]
      },
      "angularCompilerOptions": {
        "genDir": "aot",
        "skipMetadataEmit" : true
      }
    }

config/webpack-aot.config.js

/**
 * webpack2 config file for ng2 AOT compile
 * location : config/webpack.aot.js
 */
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var helpers = require('./helpers');

const ngToolsWebpack = require('@ngtools/webpack');
const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
console.log(helpers.root('src', 'app', 'app.module#AppModule'));
module.exports = {
  devtool: 'source-map',
  entry: {
    'polyfills': './src/polyfills.ts',
    'app': './src/main.ts'
  },
  resolve: {
    extensions: ['*', '.ts', '.js']
  },
  output: {
    path: helpers.root('dist'),
    publicPath: '/',
    filename: '[name].[hash].js',
    chunkFilename: '[id].[hash].chunk.js'
  },

  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: '@ngtools/webpack'
      },
      {
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
        test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
        include: helpers.root('public', 'images'),
        loader: 'file-loader',
        options: {
          //name: '/assets/[name].[hash].[ext]'  <-file-loaderError
          name: 'assets/[name].[ext]'
        }
      },
      {
        test: /\.css$/,
        exclude: helpers.root('src', 'app'),
        loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' })
      },
      {
        test: /\.css$/,
        include: helpers.root('src', 'app'),
        loader: 'raw-loader'
      },
      {
        test: /\.scss$/,
        exclude: /node_modules/,
        loaders: ['raw-loader', 'sass-loader']
      },
      {
        test: /\.sass$/,
        exclude: /node_modules/,
        loaders: ['raw-loader', 'sass-loader']
      }
    ]
  },

  plugins: [
    // If you want to use jquery in ng2 uncomment this
    /*
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery"
    }),*/
    new ngToolsWebpack.AotPlugin({
      tsConfigPath: helpers.root('tsconfig-aot.json'),
      basePath: helpers.root(''),
      entryModule: helpers.root('src', 'app', 'app.module#AppModule'),
      mainPath: helpers.root('src', 'main.ts')
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: ['app', 'polyfills']
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true,
      options: {
        htmlLoader: {
          minimize: false
        }
      }
    }),
    new HtmlWebpackPlugin({
      template: 'public/index.html'
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
          warnings: false,
          drop_console: true
      },
      output: {
          comments: false
      }
    }),
    new ExtractTextPlugin('[name].[hash].css'),
    new webpack.DefinePlugin({
      'process.env': {
        'ENV': JSON.stringify(ENV)
      }
    })
  ]
};

I used this webpack config and I did AOT compile with angular2 lazy loading. You can see example app for AOT / JIT compile with angular2 lazy load for production mode and dev mode in this git.

angular2-webpack2-aot

MiraGe
  • 311
  • 4
  • 8
  • I am a bit confuse @MiraGe, in your **tsconfig-aot.json** file, the configuration **"genDir": "aot"** will generate an **aot** folder with the compiled files, but how is that folder used later on? Because if you run ``npm run build:aot`` seems like it just build a new distribution within the **dist** folder right? What is exactly the point of generating that aot folder? – Ander Mar 14 '17 at 08:36
  • 1
    You are right. With @ngtools/webpack, "getDir": "aot" is useless. @ngtools/webpack will auto gen aot file in memory and auto replace app.module#AppModule. I have to fix it:) – MiraGe Mar 15 '17 at 05:32
  • Then the only necessary configuration on the **tsconfig-aot.json** file is just the ``"target": "es5", "module": "es2015"`` part? – Ander Mar 15 '17 at 12:15
  • 1
    Yes. Only when you use @ngtools/webpack. – MiraGe Mar 17 '17 at 01:48
  • @MiraGe I'm not totally sure your example is actually invoking AOT build, at least as of today's [main.ts](https://github.com/TK-one/angular2-webpack2-aot/blob/1cbb128af53dd8852513e6fc748ff6a57e93f731/src/main.ts): that is still using `platformBrowserDynamic().bootstrapModule(AppModule)`, while it should instead use `platformBrowser().bootstrapModuleFactory(AppModuleNgFactory)` – superjos May 09 '17 at 23:45