web-dev-qa-db-fra.com

Aucune métadonnée n'a été trouvée pour 'AppModule' Error in Angular 5 rendu côté serveur avec --env.prod

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:

  • Webpack construit les packages en utilisant le commutateur --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:

  • Ce n'est pas quelque chose lié à IIS, cela se passe au niveau Kestrel.
  • Ce n'est pas lié à la machine du serveur Web car je peux même le reproduire localement en lançant manuellement Webpack avec le commutateur --end.prod juste avant une exécution de Debug _ ou Release
  • Cela ne semble pas avoir de rapport avec le code source, car je peux le reproduire même avec un exemple d'application à un seul composant avec des fichiers AppModule très basiques et un code trivial.
  • Le projet fonctionnait parfaitement avec .NET Core 2.0.0 et Angular 4.3.x.
  • La seule chose importante que j'ai modifiée dans le fichier 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

  • .NET Core 2.0.3
  • Angulaire 5.0.2
  • @ ngtools/webpack 1.8.2 (également essayé avec 1.8.1 - même résultat)
  • WebPack 2.6.1 (également essayé avec 2.5.6 - même résultat)

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];
};
5
Darkseal

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 :)

5
Fiyaz Hasan

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>
3
Juan Carlos Puerto

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
1
MMiebach

solution de mak0t0san travaillant pour moi et SSR travaillant toujours (angle 5.2.0),

  1. mettre à jour ngtoolwebpack à 1.10.2
  2. TypeScript 2.6.2

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];
};
0
Dwi Yanuar Ilham

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 .

0
Sourcerer