Closed sebtack closed 6 years ago
@SteveSandersonMS please respond.
The client-side part is based on the standard Angular CLI template, so it has the same functionality.
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.
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.
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
}
};
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.
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\")
});
The process of using HMR with the template is a bit fuzzy. Why does this template not support it natively for development?