J'essaie de mettre à niveau un projet ASP.NET Core + Angular 4 de .NET Core 2.0.0/Angular4 vers .NET Core 2.0.3/Angular5. J'ai réussi à tout faire fonctionner correctement, à l'exception du rendu côté serveur dans un environnement de production, lorsque je publie l'application:
Une exception non gérée est survenue: aucune métadonnée NgModule n'a été trouvée pour 'AppModule'.
Erreur: Aucune métadonnée NgModule trouvée pour 'AppModule'.
Le problème ne se produit que lorsque ces deux conditions sont remplies:
--env.prod
Le fichier de vue Index.cshtml
contient le paramètre asp-prerender-module
, comme dans l'exemple suivant:
<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>
Si je retire l'interrupteur et/ou le paramètre, le problème disparaît (avec le SSR).
Il y a un tas d'autres informations que je peux donner:
--end.prod
juste avant une exécution de Debug _ ou Release. webpack.config.js
est de remplacer la AotPlugin
par la nouvelle, spécifique à Angular5AngularCompilerPlugin
fournie par le @ ngtools/webpack package: Je pense que cela pourrait aussi bien être la cause, le commutateur --env.prod
utilise ce compilateur AOT au lieu du compilateur JIT. Cela, ou quelque chose lié au paquet .NET SpaServices - peut-être pas à égalité avec le nouveau Angular5 et/ou le nouveau compilateur AoT? Malheureusement, je ne peux pas revenir à l’ancienne AotPlugin
car elle renvoie également des erreurs - ce qui est parfaitement compréhensible, car elle n’a pas été conçue pour être utilisée avec Angular5.
Versions du logiciel
webpack.config.js
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const AotPlugin = require('@ngtools/webpack').AngularCompilerPlugin;
const CheckerPlugin = require('awesome-TypeScript-loader').CheckerPlugin;
module.exports = (env) => {
// Configuration in common to both client-side and server-side bundles
const isDevBuild = !(env && env.prod);
const sharedConfig = {
stats: { modules: false },
context: __dirname,
resolve: { extensions: [ '.js', '.ts' ] },
output: {
filename: '[name].js',
publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
rules: [
{ test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/, include: /ClientApp/, use: isDevBuild ? ['awesome-TypeScript-loader?silent=true', 'angular2-template-loader'] : '@ngtools/webpack' },
{ test: /\.html$/, use: 'html-loader?minimize=false' },
{ test: /\.css$/, use: [ 'to-string-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize' ] },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},
plugins: [new CheckerPlugin()]
};
// Configuration for client-side bundle suitable for running in browsers
const clientBundleOutputDir = './wwwroot/dist';
const clientBundleConfig = merge(sharedConfig, {
entry: { 'main-client': './ClientApp/boot.browser.ts' },
output: { path: path.join(__dirname, clientBundleOutputDir) },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin(),
new AotPlugin({
tsConfigPath: './tsconfig.json',
entryModule: path.join(__dirname, 'ClientApp/app/app.module.browser#AppModule'),
exclude: ['./**/*.server.ts']
})
])
});
// Configuration for server-side (prerendering) bundle suitable for running in Node
const serverBundleConfig = merge(sharedConfig, {
resolve: { mainFields: ['main'] },
entry: { 'main-server': './ClientApp/boot.server.ts' },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./ClientApp/dist/vendor-manifest.json'),
sourceType: 'commonjs2',
name: './vendor'
})
].concat(isDevBuild ? [] : [
// Plugins that apply in production builds only
new AotPlugin({
tsConfigPath: './tsconfig.json',
entryModule: path.join(__dirname, 'ClientApp/app/app.module.server#AppModule'),
exclude: ['./**/*.browser.ts']
})
]),
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist')
},
target: 'node',
devtool: 'inline-source-map'
});
return [clientBundleConfig, serverBundleConfig];
};
La configuration @ngtool/webpack
pour Angular 5 est un peu différente de la forme Angular 2/4. Donc, j'ai changé le webpack.config.js
comme suit,
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const AngularCompilerPlugin = require('@ngtools/webpack').AngularCompilerPlugin;
const CheckerPlugin = require('awesome-TypeScript-loader').CheckerPlugin;
module.exports = (env) => {
// Configuration in common to both client-side and server-side bundles
const isDevBuild = !(env && env.prod);
const sharedConfig = {
stats: { modules: false },
context: __dirname,
resolve: { extensions: [ '.js', '.ts' ] },
output: {
filename: '[name].js',
publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
rules: [
{ test: /\.ts$/, include: /ClientApp/, use: isDevBuild ? ['awesome-TypeScript-loader?silent=true', 'angular2-template-loader'] : '@ngtools/webpack' },
{ test: /\.html$/, use: 'html-loader?minimize=false' },
{ test: /\.css$/, use: [ 'to-string-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize' ] },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' },
{
test: /(?:\.ngfactory\.js|\.ngstyle\.js)$/,
loader: '@ngtools/webpack',
options: {
tsConfigPath: '/tsconfig.json',
}
}
]
},
plugins: [new CheckerPlugin()]
};
// Configuration for client-side bundle suitable for running in browsers
const clientBundleOutputDir = './wwwroot/dist';
const clientBundleConfig = merge(sharedConfig, {
entry: { 'main-client': './ClientApp/boot.browser.ts' },
output: { path: path.join(__dirname, clientBundleOutputDir) },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin(),
new AngularCompilerPlugin({
tsConfigPath: './tsconfig.json',
entryModule: path.join(__dirname, 'ClientApp/app/app.module.browser#AppModule'),
exclude: ['./**/*.server.ts']
})
])
});
// Configuration for server-side (prerendering) bundle suitable for running in Node
const serverBundleConfig = merge(sharedConfig, {
resolve: { mainFields: ['main'] },
entry: { 'main-server': './ClientApp/boot.server.ts' },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./ClientApp/dist/vendor-manifest.json'),
sourceType: 'commonjs2',
name: './vendor'
})
].concat(isDevBuild ? [] : [
// Plugins that apply in production builds only
new AngularCompilerPlugin({
tsConfigPath: './tsconfig.json',
entryModule: path.join(__dirname, 'ClientApp/app/app.module.server#AppModule'),
exclude: ['./**/*.browser.ts']
})
]),
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist')
},
target: 'node',
devtool: 'inline-source-map'
});
return [clientBundleConfig, serverBundleConfig];
};
Et voici mon fichier package.json
,
{
"dependencies": {
"@angular/animations": "^5.0.2",
"@angular/cdk": "^5.0.0-rc0",
"@angular/cli": "^1.6.0-beta.2",
"@angular/common": "^5.0.2",
"@angular/compiler": "^5.0.2",
"@angular/compiler-cli": "^5.0.2",
"@angular/core": "^5.0.2",
"@angular/forms": "^5.0.2",
"@angular/http": "^5.0.2",
"@angular/material": "^5.0.0-rc0",
"@angular/platform-browser": "^5.0.2",
"@angular/platform-browser-dynamic": "^5.0.2",
"@angular/platform-server": "^5.0.2",
"@angular/router": "^5.0.2",
"@ngtools/webpack": "^1.8.3",
"@types/webpack-env": "^1.13.2",
"angular2-template-loader": "0.6.2",
"aspnet-prerendering": "^3.0.1",
"aspnet-webpack": "^2.0.1",
"awesome-TypeScript-loader": "^3.4.0",
"bootstrap": "3.3.7",
"css": "2.2.1",
"css-loader": "^0.28.7",
"es6-shim": "0.35.3",
"event-source-polyfill": "0.0.12",
"expose-loader": "0.7.4",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.5",
"html-loader": "^0.5.1",
"isomorphic-fetch": "2.2.1",
"jquery": "3.2.1",
"json-loader": "^0.5.7",
"preboot": "^5.1.7",
"raw-loader": "0.5.1",
"reflect-metadata": "0.1.10",
"request": "^2.83.0",
"rxjs": "^5.5.2",
"style-loader": "^0.19.0",
"to-string-loader": "1.1.5",
"TypeScript": "^2.6.1",
"url-loader": "^0.6.2",
"webpack": "^3.8.1",
"webpack-hot-middleware": "^2.20.0",
"webpack-merge": "^4.1.1",
"zone.js": "^0.8.18"
},
"devDependencies": {
"@types/chai": "^4.0.5",
"@types/jasmine": "^2.8.2",
"@types/node": "^8.0.53",
"chai": "^4.1.2",
"jasmine-core": "2.8.0",
"karma": "1.7.1",
"karma-chai": "0.1.0",
"karma-chrome-launcher": "2.2.0",
"karma-cli": "1.0.1",
"karma-jasmine": "1.1.0",
"karma-webpack": "2.0.6"
},
"name": "aspnetcoreangularspa",
"private": true,
"scripts": {
"test": "karma start ClientApp/test/karma.conf.js"
},
"version": "0.0.0"
}
Si vous rencontrez toujours des problèmes pour exécuter la solution, veuillez consulter ce référentiel pour une solution opérationnelle.
J'espère que ça aide :)
Comme d'autres l'ont suggéré, la solution temporaire consiste à supprimer le SSR (Server Side Rendering). Cela signifie qu’il faut ouvrir le fichier Home/Index.cshtml et changer
<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>
à
<app>Loading...</app>
J'ai trouvé votre problème en cherchant une solution à mon problème décrit ici: https://github.com/aspnet/JavaScriptServices/issues/1388 J'ai donc essayé votre webpack config et également essayé d'exécuter l'application à partir du référentiel, mais quand je fais "Publier dotnet" puis exécutez l'application, j'obtiens cette erreur:
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[0]
An unhandled exception has occurred: No NgModule metadata found for 'AppModule'.
Error: No NgModule metadata found for 'AppModule'.
at NgModuleResolver.module.exports.NgModuleResolver.resolve
solution de mak0t0san travaillant pour moi et SSR travaillant toujours (angle 5.2.0),
et changez webpack.config.js avec ça
const path = require('path');
const webpack = require('webpack');
const { DllReferencePlugin, SourceMapDevToolPlugin} = require('webpack');
const merge = require('webpack-merge');
const {AngularCompilerPlugin, PLATFORM} = require('@ngtools/webpack');
const CheckerPlugin = require('awesome-TypeScript-loader').CheckerPlugin;
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = (env) => {
// Configuration in common to both client-side and server-side bundles
const isDevBuild = !(env && env.prod);
const sharedConfig = {
stats: {modules: false},
context: __dirname,
resolve: {extensions: ['.js', '.ts']},
output: {
filename: '[name].js',
chunkFilename:'[id].chunk.js',
publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
rules: [
{test: /\.html$/, use: 'html-loader?minimize=false'},
{test: /\.css$/, use: ['to-string-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize']},
{test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000'},
{
test: /\.(scss)$/,
use: [{
loader: 'style-loader', // inject CSS to page
}, {
loader: 'css-loader', // translates CSS into CommonJS modules
}, {
loader: 'postcss-loader', // Run post css actions
options: {
plugins: function () { // post css plugins, can be exported to postcss.config.js
return [
require('precss'),
require('autoprefixer')
];
}
}
}, {
loader: 'sass-loader' // compiles Sass to CSS
}]
}
]
},
plugins: [new CheckerPlugin()]
};
// Configuration for client-side bundle suitable for running in browsers
const clientBundleOutputDir = './wwwroot/dist';
const clientBundleConfig = merge(sharedConfig, {
module:{
rules:[
{
test: /\.ts$/,
use: ['@ngtools/webpack']
},
]
},
entry: {'main-client': './ClientApp/boot.browser.ts'},
output: {path: path.join(__dirname, clientBundleOutputDir)},
plugins: [
new DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
}),
new AngularCompilerPlugin({
"mainPath": path.join(__dirname, 'ClientApp/boot.browser.ts'),
platform: PLATFORM.Browser,
tsConfigPath: './tsconfig.json',
entryModule: path.join(__dirname, 'ClientApp/app/app.browser.module#AppModule'),
sourceMap: true
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new UglifyJsPlugin({
sourceMap: true
})
])
});
// Configuration for server-side (prerendering) bundle suitable for running in Node
const serverBundleConfig = merge(sharedConfig, {
module:{
rules:[
{ test: /\.ts$/, use: ['awesome-TypeScript-loader?silent=true', 'angular2-template-loader', 'angular2-router-loader'] },
]
},
resolve: { mainFields: ['main'] },
entry: { 'main-server': './ClientApp/boot.server.ts' },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./ClientApp/dist/vendor-manifest.json'),
sourceType: 'commonjs2',
name: './vendor'
})
],
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist')
},
target: 'node',
devtool: 'inline-source-map'
});
return [clientBundleConfig, serverBundleConfig];
};
Comme écrit sur le @/MMiebach's link :
Angular 5 inclut des modifications importantes (par rapport à Angular 4), ce qui signifie que le code pour le rendu côté serveur doit être très différent.
Heureusement, Microsoft a publié leur nouveau modèle de projet SPA pour Angular 5 . Il peut être installé avec
dotnet new --install Microsoft.DotNet.Web.Spa.ProjectTemplates::2.0.0
Après cette commande, dotnet new angular
utilisera le 5ème Angular au lieu du 4ème.
Par défaut, le rendu côté serveur n'est pas activé, mais il existe des instructions pour le faire dans Documents par MS .