dotnet / AspNetCore.Docs

Documentation for ASP.NET Core
https://docs.microsoft.com/aspnet/core
Creative Commons Attribution 4.0 International
12.59k stars 25.3k forks source link

Why does Angular template not support HRM? #8547

Closed sebtack closed 6 years ago

sebtack commented 6 years ago

The process of using HMR with the template is a bit fuzzy. Why does this template not support it natively for development?

Rick-Anderson commented 6 years ago

@SteveSandersonMS please respond.

SteveSandersonMS commented 6 years ago

The client-side part is based on the standard Angular CLI template, so it has the same functionality.

sebtack commented 6 years ago

Indeed, but the problem I'm facing is that the new Angular CLI doesn't support ng eject anymore. So I can't (or maybe better: I don't know how to) create the webpack.config.js that WebpackDevMiddleware rely on.

SteveSandersonMS commented 6 years ago

eject is/was/maybe-will-be an Angular CLI feature, so I'd recommend raising this with the Angular CLI community. The good news is that, according to Angular CLI's console output (as per https://github.com/angular/angular-cli/issues/10618):

Ejection will be re-enabled in a future release of the CLI.

... so at least it sounds like they have a plan to bring it back.

alexbenitez commented 6 years ago

so... I ejected the project using the old CLI "~1.7.0".... Now I see the webpack.config.js, etc... I followed the steps from this link https://www.javascriptstuff.com/webpack-hmr-tutorial/ but still I can't manage to make HMR work with the the Angular template 2.1... Is there any article somewhere detailing how to enable HMR using the latest template?

@SteveSandersonMS ..., other than setting the HMR flag

      app.UseWebpackDevMiddleware(new Microsoft.AspNetCore.SpaServices.Webpack.WebpackDevMiddlewareOptions()
                {
                    HotModuleReplacement = true
                });

Do I need to set the HotModuleReplacementEndpoint? (I tried several values but none work..).

I keep getting an exception AggregateException: One or more errors occurred. (Webpack dev middleware failed because of an error while loading 'aspnet-webpack'. Error was: Error: Cannot find module 'aspnet-webpack'

This is my modified webpack.config.js after following steps in the tutorial that I'm referencing.

const fs = require('fs');
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const rxPaths = require('rxjs/_esm5/path-mapping');
const autoprefixer = require('autoprefixer');
const postcssUrl = require('postcss-url');
const postcssImports = require('postcss-import');

const { NoEmitOnErrorsPlugin, SourceMapDevToolPlugin, NamedModulesPlugin } = require('webpack');
const { NamedLazyChunksWebpackPlugin, BaseHrefWebpackPlugin, PostcssCliResources } = require('@angular/cli/plugins/webpack');
const { CommonsChunkPlugin } = require('webpack').optimize;
const { AngularCompilerPlugin } = require('@ngtools/webpack');

const nodeModules = path.join(process.cwd(), 'node_modules');
const realNodeModules = fs.realpathSync(nodeModules);
const genDirNodeModules = path.join(process.cwd(), 'src', '$$_gendir', 'node_modules');
const entryPoints = ["inline","polyfills","sw-register","styles","vendor","main"];
const hashFormat = {"chunk":"","extract":"","file":".[hash:20]","script":""};
const baseHref = "";
const deployUrl = "";
const projectRoot = process.cwd();
const maximumInlineSize = 10;
const postcssPlugins = function (loader) {
        return [
            postcssImports({
                resolve: (url, context) => {
                    return new Promise((resolve, reject) => {
                        let hadTilde = false;
                        if (url && url.startsWith('~')) {
                            url = url.substr(1);
                            hadTilde = true;
                        }
                        loader.resolve(context, (hadTilde ? '' : './') + url, (err, result) => {
                            if (err) {
                                if (hadTilde) {
                                    reject(err);
                                    return;
                                }
                                loader.resolve(context, url, (err, result) => {
                                    if (err) {
                                        reject(err);
                                    }
                                    else {
                                        resolve(result);
                                    }
                                });
                            }
                            else {
                                resolve(result);
                            }
                        });
                    });
                },
                load: (filename) => {
                    return new Promise((resolve, reject) => {
                        loader.fs.readFile(filename, (err, data) => {
                            if (err) {
                                reject(err);
                                return;
                            }
                            const content = data.toString();
                            resolve(content);
                        });
                    });
                }
            }),
            postcssUrl({
                filter: ({ url }) => url.startsWith('~'),
                url: ({ url }) => {
                    const fullPath = path.join(projectRoot, 'node_modules', url.substr(1));
                    return path.relative(loader.context, fullPath).replace(/\\/g, '/');
                }
            }),
            postcssUrl([
                {
                    // Only convert root relative URLs, which CSS-Loader won't process into require().
                    filter: ({ url }) => url.startsWith('/') && !url.startsWith('//'),
                    url: ({ url }) => {
                        if (deployUrl.match(/:\/\//) || deployUrl.startsWith('/')) {
                            // If deployUrl is absolute or root relative, ignore baseHref & use deployUrl as is.
                            return `${deployUrl.replace(/\/$/, '')}${url}`;
                        }
                        else if (baseHref.match(/:\/\//)) {
                            // If baseHref contains a scheme, include it as is.
                            return baseHref.replace(/\/$/, '') +
                                `/${deployUrl}/${url}`.replace(/\/\/+/g, '/');
                        }
                        else {
                            // Join together base-href, deploy-url and the original URL.
                            // Also dedupe multiple slashes into single ones.
                            return `/${baseHref}/${deployUrl}/${url}`.replace(/\/\/+/g, '/');
                        }
                    }
                },
                {
                    // TODO: inline .cur if not supporting IE (use browserslist to check)
                    filter: (asset) => {
                        return maximumInlineSize > 0 && !asset.hash && !asset.absolutePath.endsWith('.cur');
                    },
                    url: 'inline',
                    // NOTE: maxSize is in KB
                    maxSize: maximumInlineSize,
                    fallback: 'rebase',
                },
                { url: 'rebase' },
            ]),
            PostcssCliResources({
                deployUrl: loader.loaders[loader.loaderIndex].options.ident == 'extracted' ? '' : deployUrl,
                loader,
                filename: `[name]${hashFormat.file}.[ext]`,
            }),
            autoprefixer({ grid: true }),
        ];
    };

module.exports = {
  "resolve": {
    "extensions": [
      ".ts",
      ".js"
    ],
    "symlinks": true,
    "modules": [
      "./src",
      "./node_modules"
    ],
    "alias": rxPaths(),
    "mainFields": [
      "browser",
      "module",
      "main"
    ]
  },
  "resolveLoader": {
    "modules": [
      "./node_modules"
    ],
    "alias": rxPaths()
  },
  "entry": {
    "main": [
      "./src\\main.ts"
      'webpack/hot/dev-server',
      'webpack-dev-server/client?http://localhost:8080/',
    ],
    "polyfills": [
      "./src\\polyfills.ts"
    ],
    "styles": [
      "./src\\styles.css",
      "./node_modules\\bootstrap\\dist\\css\\bootstrap.min.css"
    ]
  },
  "output": {
    "path": path.join(process.cwd(), "dist"),
    "filename": "[name].bundle.js",
    "chunkFilename": "[id].chunk.js",
    "crossOriginLoading": false
  },
  "module": {
    "rules": [
      {
        "test": /\.html$/,
        "loader": "raw-loader"
      },
      {
        "test": /\.(eot|svg|cur)$/,
        "loader": "file-loader",
        "options": {
          "name": "[name].[hash:20].[ext]",
          "limit": 10000
        }
      },
      {
        "test": /\.(jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/,
        "loader": "url-loader",
        "options": {
          "name": "[name].[hash:20].[ext]",
          "limit": 10000
        }
      },
      {
        "exclude": [
          path.join(process.cwd(), "src\\styles.css"),
          path.join(process.cwd(), "node_modules\\bootstrap\\dist\\css\\bootstrap.min.css")
        ],
        "test": /\.css$/,
        "use": [
          {
            "loader": "raw-loader"
          },
          {
            "loader": "postcss-loader",
            "options": {
              "ident": "embedded",
              "plugins": postcssPlugins,
              "sourceMap": true
            }
          }
        ]
      },
      {
        "exclude": [
          path.join(process.cwd(), "src\\styles.css"),
          path.join(process.cwd(), "node_modules\\bootstrap\\dist\\css\\bootstrap.min.css")
        ],
        "test": /\.scss$|\.sass$/,
        "use": [
          {
            "loader": "raw-loader"
          },
          {
            "loader": "postcss-loader",
            "options": {
              "ident": "embedded",
              "plugins": postcssPlugins,
              "sourceMap": true
            }
          },
          {
            "loader": "sass-loader",
            "options": {
              "sourceMap": true,
              "precision": 8,
              "includePaths": []
            }
          }
        ]
      },
      {
        "exclude": [
          path.join(process.cwd(), "src\\styles.css"),
          path.join(process.cwd(), "node_modules\\bootstrap\\dist\\css\\bootstrap.min.css")
        ],
        "test": /\.less$/,
        "use": [
          {
            "loader": "raw-loader"
          },
          {
            "loader": "postcss-loader",
            "options": {
              "ident": "embedded",
              "plugins": postcssPlugins,
              "sourceMap": true
            }
          },
          {
            "loader": "less-loader",
            "options": {
              "sourceMap": true
            }
          }
        ]
      },
      {
        "exclude": [
          path.join(process.cwd(), "src\\styles.css"),
          path.join(process.cwd(), "node_modules\\bootstrap\\dist\\css\\bootstrap.min.css")
        ],
        "test": /\.styl$/,
        "use": [
          {
            "loader": "raw-loader"
          },
          {
            "loader": "postcss-loader",
            "options": {
              "ident": "embedded",
              "plugins": postcssPlugins,
              "sourceMap": true
            }
          },
          {
            "loader": "stylus-loader",
            "options": {
              "sourceMap": true,
              "paths": []
            }
          }
        ]
      },
      {
        "include": [
          path.join(process.cwd(), "src\\styles.css"),
          path.join(process.cwd(), "node_modules\\bootstrap\\dist\\css\\bootstrap.min.css")
        ],
        "test": /\.css$/,
        "use": [
          "style-loader",
          {
            "loader": "raw-loader"
          },
          {
            "loader": "postcss-loader",
            "options": {
              "ident": "embedded",
              "plugins": postcssPlugins,
              "sourceMap": true
            }
          }
        ]
      },
      {
        "include": [
          path.join(process.cwd(), "src\\styles.css"),
          path.join(process.cwd(), "node_modules\\bootstrap\\dist\\css\\bootstrap.min.css")
        ],
        "test": /\.scss$|\.sass$/,
        "use": [
          "style-loader",
          {
            "loader": "raw-loader"
          },
          {
            "loader": "postcss-loader",
            "options": {
              "ident": "embedded",
              "plugins": postcssPlugins,
              "sourceMap": true
            }
          },
          {
            "loader": "sass-loader",
            "options": {
              "sourceMap": true,
              "precision": 8,
              "includePaths": []
            }
          }
        ]
      },
      {
        "include": [
          path.join(process.cwd(), "src\\styles.css"),
          path.join(process.cwd(), "node_modules\\bootstrap\\dist\\css\\bootstrap.min.css")
        ],
        "test": /\.less$/,
        "use": [
          "style-loader",
          {
            "loader": "raw-loader"
          },
          {
            "loader": "postcss-loader",
            "options": {
              "ident": "embedded",
              "plugins": postcssPlugins,
              "sourceMap": true
            }
          },
          {
            "loader": "less-loader",
            "options": {
              "sourceMap": true
            }
          }
        ]
      },
      {
        "include": [
          path.join(process.cwd(), "src\\styles.css"),
          path.join(process.cwd(), "node_modules\\bootstrap\\dist\\css\\bootstrap.min.css")
        ],
        "test": /\.styl$/,
        "use": [
          "style-loader",
          {
            "loader": "raw-loader"
          },
          {
            "loader": "postcss-loader",
            "options": {
              "ident": "embedded",
              "plugins": postcssPlugins,
              "sourceMap": true
            }
          },
          {
            "loader": "stylus-loader",
            "options": {
              "sourceMap": true,
              "paths": []
            }
          }
        ]
      },
      {
        "test": /\.ts$/,
        "loader": "@ngtools/webpack"
      }
    ]
  },
  "plugins": [
    new webpack.HotModuleReplacementPlugin(),
    new NoEmitOnErrorsPlugin(),
    new CopyWebpackPlugin([
      {
        "context": "src",
        "to": "",
        "from": {
          "glob": "assets\\**\\*",
          "dot": true
        }
      }
    ], {
      "ignore": [
        ".gitkeep",
        "**/.DS_Store",
        "**/Thumbs.db"
      ],
      "debug": "warning"
    }),
    new ProgressPlugin(),
    new CircularDependencyPlugin({
      "exclude": /(\\|\/)node_modules(\\|\/)/,
      "failOnError": false,
      "onDetected": false,
      "cwd": projectRoot
    }),
    new NamedLazyChunksWebpackPlugin(),
    new HtmlWebpackPlugin({
      "template": "./src\\index.html",
      "filename": "./index.html",
      "hash": false,
      "inject": true,
      "compile": true,
      "favicon": false,
      "minify": false,
      "cache": true,
      "showErrors": true,
      "chunks": "all",
      "excludeChunks": [],
      "title": "Webpack App",
      "xhtml": true,
      "chunksSortMode": function sort(left, right) {
        let leftIndex = entryPoints.indexOf(left.names[0]);
        let rightindex = entryPoints.indexOf(right.names[0]);
        if (leftIndex > rightindex) {
            return 1;
        }
        else if (leftIndex < rightindex) {
            return -1;
        }
        else {
            return 0;
        }
    }
    }),
    new BaseHrefWebpackPlugin({}),
    new CommonsChunkPlugin({
      "name": [
        "inline"
      ],
      "minChunks": null
    }),
    new CommonsChunkPlugin({
      "name": [
        "vendor"
      ],
      "minChunks": (module) => {
                return module.resource
                    && (module.resource.startsWith(nodeModules)
                        || module.resource.startsWith(genDirNodeModules)
                        || module.resource.startsWith(realNodeModules));
            },
      "chunks": [
        "main"
      ]
    }),
    new SourceMapDevToolPlugin({
      "filename": "[file].map[query]",
      "moduleFilenameTemplate": "[resource-path]",
      "fallbackModuleFilenameTemplate": "[resource-path]?[hash]",
      "sourceRoot": "webpack:///"
    }),
    new CommonsChunkPlugin({
      "name": [
        "main"
      ],
      "minChunks": 2,
      "async": "common"
    }),
    new NamedModulesPlugin({}),
    new AngularCompilerPlugin({
      "mainPath": "main.ts",
      "platform": 0,
      "hostReplacementPaths": {
        "environments\\environment.ts": "environments\\environment.ts"
      },
      "sourceMap": true,
      "tsConfigPath": "src\\tsconfig.app.json",
      "skipCodeGeneration": true,
      "compilerOptions": {}
    })
  ],
  "node": {
    "fs": "empty",
    "global": true,
    "crypto": "empty",
    "tls": "empty",
    "net": "empty",
    "process": true,
    "module": false,
    "clearImmediate": false,
    "setImmediate": false
  },
  "devServer": {
    "historyApiFallback": true
  }
};
SteveSandersonMS commented 6 years ago

Error was: Error: Cannot find module 'aspnet-webpack'

This suggests that you haven't installed the module aspnet-webpack. Try npm install --save aspnet-webpack in your ClientApp dir.

alexbenitez commented 5 years ago

Thanks @SteveSandersonMS. My problem was that I needed to correct the ProjectPath to point ClientApp (and therefore unable to find the modules).

e.g

      {
                    HotModuleReplacement = true,
                    ConfigFile = "webpack.config.js",
                    ProjectPath = System.IO.Path.Combine(env.ContentRootPath, @"ClientApp\")
                });