angulartypescriptwebpackwebpack-html-loader

Webpack Html-loader does not add template in bundle


I am having trouble creating a starter project with Angular (4.4.4) and Webpack (3.6.0). My configuration is working with AOT build and template declaration in my component, but as soon as I add a html template (and replace template with templateUrl) Webpack comes into an infinite loop and gets stuck at 95% emtitting. I am using html-loader V0.5.1 for the template loading

webpack.common.js

module.exports = {
    entry: {
        app: './src/main.ts',
        vendor: getVendorPackages()
    },
    output: {
        path: __dirname + '/dist',
        filename: "app.bundle.js"
    },
    module: {
        loaders: [
            {
                test: /\.ts$/,
                loaders: ['awesome-typescript-loader', 'angular2-template-loader']
            },
            {
                test: /\.html$/,
                loader: 'html-loader'
            }
        ]
    },
    plugins: [
        new webpack.ContextReplacementPlugin( //Resolve Angular warnings
            /angular(\\|\/)core(\\|\/)@angular/,
            path.resolve(__dirname, '../src')
        ),
        new webpack.optimize.CommonsChunkPlugin({ //Create secondary bundle containing dependencies
            name: 'vendor',
            filename: 'vendor.bundle.js',
            minChunks(module) {
                const context = module.context;
                return context && context.indexOf('node_modules') >= 0;
            },
        }),
        new htmlWebpackPlugin({ //Generate index.html
            template: './src/index.html'
        }),
        new webpack.ContextReplacementPlugin( //Only export the locals we need | https://github.com/moment/moment/issues/2517
            /moment[\/\\]locale$/, /en|nl/
        )
    ],
    resolve: {
        extensions: ['.js', '.ts']
    }
};

app.component.ts

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

@Component({
    selector: 'hn-root',
    // template: `<h1>Hello world</h1>`
    templateUrl: 'app.html',
    // styleUrls: ['./app.scss']
})
export class AppComponent {
}

app.html

<h1>Hello big world</h1>

package.json

{
  "name": "angular-webpack-starter",
  "version": "0.1.0",
  "scripts": {
    "build": "webpack --config webpack.dev.js",
    "serve": "webpack-dev-server --config webpack.dev.js",
    "build --prod": "webpack -p --config webpack.prod.js --progress"
  },
  "dependencies": {
    "@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",
    "reflect-metadata": "0.1.10",
    "rxjs": "5.4.3",
    "zone.js": "0.8.18"
  },
  "devDependencies": {
    "@angular/compiler-cli": "4.4.4",
    "@ngtools/webpack": "1.7.2",
    "@types/core-js": "0.9.43",
    "@types/node": "8.0.31",
    "angular2-template-loader": "0.6.2",
    "awesome-typescript-loader": "3.2.3",
    "codelyzer": "3.2.0",
    "html-loader": "0.5.1",
    "html-webpack-plugin": "2.30.1",
    "tslint": "5.7.0",
    "typescript": "2.5.3",
    "webpack": "3.6.0",
    "webpack-bundle-analyzer": "2.9.0",
    "webpack-dev-server": "2.9.1",
    "webpack-merge": "4.1.0"
  }
}

I am clearly doing something wrong, but I cannot figure out what that is..


I've tried

I also checked the webpack.config.js of most angular starters (including angular-cli) and they are using raw-loader instead of html-loader. Makes me wondering, can html-loader be used in combination with AOT compilation, since it creates inline html?


Solution

  • To answer my own question, I made a mistake in my webpack.common.js. I used the key loaders where I should have used the key rules.

    So the new code would be:

    module.exports = {
        entry: {
            app: './src/main.ts',
            vendor: getVendorPackages()
        },
        output: {
            path: __dirname + '/dist',
            filename: "website.bundle.js"
        },
        module: {
            rules: [
                {
                    test: /\.html$/,
                    loader: 'html-loader'
                }
            ]
        },
        plugins: [
            new webpack.ContextReplacementPlugin( //Resolve Angular warnings
                /angular(\\|\/)core(\\|\/)@angular/,
                path.resolve(__dirname, '../src')
            ),
            new webpack.optimize.CommonsChunkPlugin({ //Create secondary bundle containing dependencies
                name: 'vendor',
                filename: 'vendor.bundle.js',
                minChunks(module) {
                    const context = module.context;
                    return context && context.indexOf('node_modules') >= 0;
                },
            }),
            new htmlWebpackPlugin({ //Generate index.html
                template: './src/index.html'
            }),
            new webpack.ContextReplacementPlugin( //Only export the locals we need | https://github.com/moment/moment/issues/2517
                /moment[\/\\]locale$/, /en|nl/
            )
        ],
        resolve: {
            extensions: ['.js', '.ts']
        }
    };
    

    Or more specific:

     module: {
        rules: [
            {
                test: /\.html$/,
                loader: 'html-loader'
            }
        ]
    },
    

    you'l might also notice that the /\.ts$/ rule is gone. This is because AOT compilation uses it's own typescript compiler and webpack-merge does not override rules. Due to this I had to move the compilation down to the dev configuration:

    webpack.dev.ts

    module.exports = merge(common, {
        module: {
          rules: [
              {
                  test: /\.ts$/, //Prod uses AOT for .ts and webpack does not override the rules, so they need to be separated
                  loaders: ['awesome-typescript-loader', 'angular2-template-loader']
              },
          ]
        },
        plugins: [
            new BundleAnalyzerPlugin({ //Make bundle sizes visible
                analyzerMode: 'static',
                openAnalyzer: false
            }),
            new webpack.DefinePlugin({ //Set the node env so that the project knows what to enable or disable
                'process.env': {
                    'NODE_ENV': JSON.stringify('development')
                }
            })
        ],
        devtool: 'eval-source-map',
    });
    

    To extend my answer and make a working example (with AOT compilation) I also had to add some data to my tsconfig.json (see AOT documentation):

     "files": [
        "src/app/app.module.ts",
        "src/main.ts"
      ],
    
      "angularCompilerOptions": {
        "genDir": "aot",
        "skipMetadataEmit" : true
      }
    

    Combined this made the following tsconfig.json:

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