Reputation: 9113
Firstly, this project is based on famous .Net Core and ng2 template for Visual Studio 2015 Link to VS 2015 Template Tutorial
This template is very good and everything's working fine as expected. Webpack/HMR is working too and I can see the changes straightaway whenever I change in .html or .ts files.
But, it's using the very old versions of libraries. The problem started when I decided to upgrade all libraries to latest version (WebPack to 2.2.1). I got so many errors coz of major breaking changes in this upgrade journey. I managed to sort out almost all and got the app up and running as usual except this final issue.
It doesn't load the changes anymore by using HotModuleReplacement (HMR)
. All the changes reflect on the page, when I refresh (F5) on the browser.
We can see here that it does know the changes, compiled and returned the latest (correct) html codes, but it couldn't load it back on the page. It keeps saying that Selector 'app' did not match any elements.
package.json
"dependencies": {
"@angular/common": "^2.4.8",
"@angular/compiler": "^2.4.8",
"@angular/core": "^2.4.8",
"@angular/forms": "^2.4.8",
"@angular/http": "^2.4.8",
"@angular/platform-browser": "^2.4.8",
"@angular/platform-browser-dynamic": "^2.4.8",
"@angular/platform-server": "^2.4.8",
"@angular/router": "^3.4.8",
"@types/node": "^7.0.5",
"angular2-platform-node": "^2.1.0-rc.1",
"angular2-universal": "^2.1.0-rc.1",
"angular2-universal-polyfills": "^2.1.0-rc.1",
"aspnet-prerendering": "^2.0.3",
"aspnet-webpack": "^1.0.27",
"bootstrap": "^3.3.7",
"css": "^2.2.1",
"css-loader": "^0.26.1",
"es6-shim": "^0.35.1",
"expose-loader": "^0.7.3",
"extract-css-block-webpack-plugin": "^1.3.0",
"extract-text-webpack-plugin": "^2.0.0-beta",
"file-loader": "^0.10.0",
"isomorphic-fetch": "^2.2.1",
"jquery": "^3.1.1",
"preboot": "^4.5.2",
"raw-loader": "^0.5.1",
"rxjs": "^5.2.0",
"style-loader": "^0.13.1",
"to-string-loader": "^1.1.5",
"ts-loader": "^2.0.1",
"typescript": "^2.2.1",
"url-loader": "^0.5.7",
"webpack": "^2.2.1",
"webpack-externals-plugin": "^1.0.0",
"webpack-hot-middleware": "^2.17.0",
"webpack-merge": "^3.0.0",
"zone.js": "^0.7.7"
}
For a well known angular-universal issue, I have already copied the 2 workaround ts files and added in both boot-server and boot-client files.
webpack.config.vendor.js
As suggested here, I have included aspnet-prerendering
in the vendor entry.
var isDevBuild = process.argv.indexOf('--env.prod') < 0;
var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
resolve: {
extensions: [ '*', '.js' ]
},
module: {
loaders: [
{ test: /\.(png|woff|woff2|eot|ttf|svg)(\?|$)/, loader: 'url-loader?limit=100000' },
{ test: /\.css(\?|$)/, loader: ExtractTextPlugin.extract("css-loader") }
]
},
entry: {
vendor: [
'@angular/common',
'@angular/compiler',
'@angular/core',
'@angular/http',
'@angular/platform-browser',
'@angular/platform-browser-dynamic',
'@angular/router',
'@angular/platform-server',
'angular2-universal',
'angular2-universal-polyfills',
'bootstrap',
'bootstrap/dist/css/bootstrap.css',
'es6-shim',
'es6-promise',
'jquery',
'zone.js',
'aspnet-prerendering'
]
},
output: {
path: path.join(__dirname, 'wwwroot', 'dist'),
filename: '[name].js',
library: '[name]_[hash]',
},
plugins: [
//extractCSS,
new ExtractTextPlugin({
filename: "vendor.css",
allChunks: true
}),
new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }), // Maps these identifiers to the jQuery package (because Bootstrap expects it to be a global variable)
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.DllPlugin({
path: path.join(__dirname, 'wwwroot', 'dist', '[name]-manifest.json'),
name: '[name]_[hash]'
})
].concat(isDevBuild ? [] : [
new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } })
])
};
webpack.config.js
var isDevBuild = process.argv.indexOf('--env.prod') < 0;
var path = require('path');
var webpack = require('webpack');
var nodeExternals = require('webpack-node-externals');
var merge = require('webpack-merge');
var allFilenamesExceptJavaScript = /\.(?!js(\?|$))([^.]+(\?|$))/;
// Configuration in common to both client-side and server-side bundles
var sharedConfig = {
resolve: { extensions: [ '*', '.js', '.ts' ] },
output: {
filename: '[name].js',
publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
loaders: [
{ // TypeScript files
test: /\.ts$/,
include: /ClientApp/,
exclude: [/\.(spec|e2e)\.ts$/], // Exclude test files | end2end test spec files etc
loaders: [
'ts-loader?silent=true'
]
},
{ test: /\.html$/, loader: 'raw-loader' },
{ test: /\.css$/, loader: 'raw-loader' },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, loader: 'url-loader', query: { limit: 25000 } }
]
}
};
// Configuration for client-side bundle suitable for running in browsers
var clientBundleConfig = merge(sharedConfig, {
entry: { 'main-client': './ClientApp/boot-client.ts' },
output: { path: path.join(__dirname, './wwwroot/dist') },
devtool: isDevBuild ? 'inline-source-map' : null,
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [] : [
// Plugins that apply in production builds only
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin()
])
});
// Configuration for server-side (prerendering) bundle suitable for running in Node
var serverBundleConfig = merge(sharedConfig, {
entry: { 'main-server': './ClientApp/boot-server.ts' },
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist')
},
target: 'node',
devtool: 'inline-source-map',
externals: [nodeExternals({ whitelist: [allFilenamesExceptJavaScript] })] // Don't bundle .js files from node_modules
});
module.exports = [clientBundleConfig, serverBundleConfig];
boot.server.ts
import 'angular2-universal-polyfills';
import 'zone.js';
import './__workaround.node'; // temporary until 2.1.1 things are patched in Core
import { enableProdMode } from '@angular/core';
import { platformNodeDynamic } from 'angular2-universal';
import { AppModule } from './app/app.module';
//enableProdMode();
const platform = platformNodeDynamic();
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
export default createServerRenderer(params => {
// Our Root application document
const doc = '<app></app>';
return new Promise<RenderResult>((resolve, reject) => {
const requestZone = Zone.current.fork({
name: 'Angular-Universal Request',
properties: {
ngModule: AppModule,
baseUrl: '/',
requestUrl: params.url,
originUrl: params.origin,
preboot: false,
document: doc
},
onHandleError: (parentZone, currentZone, targetZone, error) => {
// If any error occurs while rendering the module, reject the whole operation
reject(error);
return true;
}
});
return requestZone.run<Promise<string>>(() => platform.serializeModule(AppModule)).then(html => {
resolve({ html: html });
}, reject);
});
});
It's very clear that <app></app>
is on the page when the page is loaded. Otherwise, the page won't be loaded at the first place. But it suddenly couldn't find it anymore when there is a change in the underlying files.
boot.client.ts
import 'angular2-universal-polyfills/browser';
import './__workaround.browser'; // temporary until 2.1.1 things are patched in Core
import { enableProdMode } from '@angular/core';
import { platformUniversalDynamic } from 'angular2-universal';
import { AppModule } from './app/app.module';
import 'bootstrap';
// Enable either Hot Module Reloading or production mode
if (module['hot']) {
module['hot'].accept();
module['hot'].dispose(() => { platform.destroy(); });
} else {
enableProdMode();
}
// Boot the application, either now or when the DOM content is loaded
const platform = platformUniversalDynamic();
const bootApplication = () => { platform.bootstrapModule(AppModule); };
if (document.readyState === 'complete') {
bootApplication();
} else {
document.addEventListener('DOMContentLoaded', bootApplication);
}
Index.html
@{
ViewData["Title"] = "Home Page";
}
<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>
<script src="~/dist/vendor.js" asp-append-version="true"></script>
@section scripts {
<script src="~/dist/main-client.js" asp-append-version="true"></script>
}
Could you guys please help me to solve this error? Thanks.
I did try to put this one on Plunkr but I don't know how to upload .NetCore files onto Plunkr.
Upvotes: 2
Views: 1184
Reputation: 1
You can replace the following line:
module['hot'].dispose(() => { platform.destroy(); });
with this:
module['hot'].dispose(() => { platform.onDestroy(() => { bootApplication() });
Upvotes: 0
Reputation: 9113
It needs to be changed in boot.client.ts to work with latest webpack-hmr library.
// Enable either Hot Module Reloading or production mode
if (module['hot']) {
module['hot'].accept();
module['hot'].dispose(() => {
// Before restarting the app, we create a new root element and dispose the old one
const oldRootElem = document.querySelector(rootElemTagName);
const newRootElem = document.createElement(rootElemTagName);
oldRootElem.parentNode.insertBefore(newRootElem, oldRootElem);
platform.destroy();
});
} else {
enableProdMode();
}
The problem is that this template is very old and it needs a lot of changes to be suitable with latest angular2 and webpack libraries.
I would suggest to use these commands to install Angular2Spa and create ng2 project rather than using the template in AspNetCore Template Pack.
npm i -g generator-aspnetcore-spa
yo aspnetcore-spa
Upvotes: 1