Angular 2, @ngtools/webpack, АОТ

Я пытаюсь использовать AOT в Angular 2, используя webpack и @ngtools/webpack.

У меня нет ошибки при компиляции, но когда я открываю сайт в браузере, я получаю ошибку консоли:

No NgModule metadata found for 'AppModule'

Мой AotPlugin в конфигурации веб-пакета таков:

new AotPlugin({
    tsConfigPath: 'tsconfig.json',
    entryModule: helpers.root('src/app/app.module.ts#AppModule')
})

Мой app.module.ts:

@NgModule({
bootstrap: [ App ],
imports: [ // import Angular's modules
    BrowserModule,
    RouterModule.forRoot(ROUTES),
    SharedModule
],
declarations: [
    App,
    HomeComponent,
],
providers: providers
})
export class AppModule {}

и мой main.browser.ts:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from "./app/app.module";

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

Мой tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "noEmitHelpers": true,
    "removeComments": true,
    "lib": ["es6", "dom"],
    "strictNullChecks": false,
    "baseUrl": "./src",
    "paths": [],
    "types": [
      "hammerjs",
      "jasmine",
      "node",
      "protractor",
      "selenium-webdriver",
      "source-map",
      "webpack"
    ]
  },
  "exclude": [
    "node_modules",
    "dist"
  ],
  "awesomeTypescriptLoaderOptions": {
    "forkChecker": true,
    "useWebpackText": true
  },
  "compileOnSave": false,
  "buildOnSave": false,
  "atom": { "rewriteTsconfig": false },
    "angularCompilerOptions": {
    "genDir": "dist",
    "entryModule": "src/app/app.module#AppModule"
  }
}

Webpack.config.js:

switch (process.env.NODE_ENV) {
  case 'prod':
  case 'production':
    module.exports = require('./config/webpack.prod')({env: 'production'});
    break;
  case 'test':
  case 'testing':
    module.exports = require('./config/webpack.test')({env: 'test'});
    break;
  case 'dev':
  case 'development':
  default:
    module.exports = require('./config/webpack.dev')({env: 'development'});
}

Мой /config/webpack.prod.js:

const webpack = require('webpack');
const helpers = require('./helpers');
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
    const commonConfig = require('./webpack.common.js').config; // the settings that are common to prod and dev
const hashName = require('./random-hash.js')(); // the settings that are common to prod and dev

    /**
     * Webpack Plugins
     */
    const ProvidePlugin = require('webpack/lib/ProvidePlugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const NormalModuleReplacementPlugin = require('webpack/lib/NormalModuleReplacementPlugin');
const IgnorePlugin = require('webpack/lib/IgnorePlugin');
const DedupePlugin = require('webpack/lib/optimize/DedupePlugin');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');

/**
 * Webpack Constants
 */
const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
const HOST = process.env.HOST || 'localhost';
const PORT = process.env.PORT || 8080;
const METADATA = webpackMerge(require('./webpack.common.js').metadata, {
    host: HOST,
    port: PORT,
    ENV: ENV
});

module.exports = function (env) {
    return webpackMerge(commonConfig({ env: ENV }), {

        devtool: 'hidden-source-map',

        output:{
            path: helpers.root('dist'),
            filename: '[name].bundle.js?ver='+ hashName,
            sourceMapFilename: '../maps/' + '[name].map?ver='+ hashName,
            chunkFilename: '[id].chunk.js?ver='+ hashName
        },


        plugins: [
            // NOTE: when adding more properties make sure you include them in custom-typings.d.ts
            new DefinePlugin({
                'ENV': JSON.stringify(METADATA.ENV),
                'process.env': {
                    'ENV': JSON.stringify(METADATA.ENV),
                    'NODE_ENV': JSON.stringify(METADATA.ENV)
                }
            }),
            new webpack.LoaderOptionsPlugin({
                optons: {
                    debug: false,
                    htmlLoader: {
                        minimize: true,
                        removeAttributeQuotes: false,
                        caseSensitive: true,
                        customAttrSurround: [
                            [/#/, /(?:)/],
                            [/\*/, /(?:)/],
                            [/\[?\(?/, /(?:)/]
                            ],
                            customAttrAssign: [/\)?\]?=/]
                    },
                    tslint: {
                        emitErrors: true,
                        failOnHint: true,
                        resourcePath: 'src'
                    },
                    postLoaders: [
                        {
                            test: /\.js$/,
                            loader: 'string-replace-loader',
                            query: {
                                search: 'var sourceMappingUrl = extractSourceMappingUrl\\(cssText\\);',
                                    replace: 'var sourceMappingUrl = "";',
                                        flags: 'g'
                            }
                        }
                    ],
                    preLoaders: [
                    {
                        test: /\.ts$/,
                        loader: 'string-replace-loader',
                        query: {
                            search: '(System|SystemJS)(.*[\\n\\r]\\s*\\.|\\.)import\\((.+)\\)',
                            replace: '$1.import($3).then(mod => (mod.__esModule && mod.default) ? mod.default : mod)',
                            flags: 'g'
                        },
                        include: [helpers.root('src')]
                    },
                    {
                        test: /\.ts$/,
                        loader: 'tslint-loader',
                        options: {
                            configFile: 'tslint.json'
                        },
                        exclude: [/\.(spec|e2e)\.ts$/]
                    }

                ]
                }
            })
        ],

        node: {
            global: true,
            crypto: 'empty',
            process: false,
            module: false,
            clearImmediate: false,
            setImmediate: false
        }

    });
};

И config/webpack.common.js:

const webpack = require('webpack');
const helpers = require('./helpers');

/*
 * Webpack Plugins
 */
// problem with copy-webpack-plugin
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin;
const HtmlElementsPlugin = require('./html-elements-plugin');
const AssetsPlugin = require('assets-webpack-plugin');
const ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin');
const AotPlugin = require('@ngtools/webpack').AotPlugin;

/*
 * Webpack Constants
 */
/*
 * Static metadata for index.html
 *
 * See: (custom attribute)
 */
const METADATA = {
    title: '',
    baseUrl: '/',
    isDevServer: helpers.isWebpackDevServer()
};

/*
 * Webpack configuration
 *
 * See: http://webpack.github.io/docs/configuration.html#cli
 */
module.exports = {
    metadata: METADATA,
    config: function(options) {
        isProd = options.env === 'production';
        return {

            /*
             * Cache generated modules and chunks to improve performance for multiple incremental builds.
             * This is enabled by default in watch mode.
             * You can pass false to disable it.
             *
             * See: http://webpack.github.io/docs/configuration.html#cache
             */
            //cache: false,

            /*
             * The entry point for the bundle
             * Our Angular.js app
             *
             * See: http://webpack.github.io/docs/configuration.html#entry
             */
            entry: {

                'polyfills': './src/polyfills.browser.ts',
                'vendor':    './src/vendor.browser.ts',
                'main':      './src/main.browser.ts'

            },
            // output: {
            //   // Make sure to use [name] or [id] in output.filename
            //   //  when using multiple entry points
            //   //    filename: "[name].bundle.js",
            //   //  chunkFilename: "[id].bundle.js"
            //   publicPath: "https://d3ohoc4mai6ihm.cloudfront.net"
            // },

            /*
             * Options affecting the resolving of modules.
             *
             * See: http://webpack.github.io/docs/configuration.html#resolve
             */
            resolve: {

                /*
                 * An array of extensions that should be used to resolve modules.
                 *
                 * See: http://webpack.github.io/docs/configuration.html#resolve-extensions
                 */
                extensions: ['.ts', '.js', '.json'],

                // An array of directory names to be resolved to the current directory
                modules: [helpers.root('src'), 'node_modules'],

            },

            /*
             * Options affecting the normal modules.
             *
             * See: http://webpack.github.io/docs/configuration.html#module
             */
            module: {

                /*
                 * An array of automatically applied loaders.
                 *
                 * IMPORTANT: The loaders here are resolved relative to the resource which they are applied to.
                 * This means they are not resolved relative to the configuration file.
                 *
                 * See: http://webpack.github.io/docs/configuration.html#module-loaders
                 */
                loaders: [

                    /* Ahead of time plugin */
                    {
                        test: /\.ts$/,
                        loader: '@ngtools/webpack',
                    },

                    /*
                     * Json loader support for *.json files.
                     *
                     * See: https://github.com/webpack/json-loader
                     */
                    {
                        test: /\.json$/,
                        loader: 'json-loader'
                    },

                    /*
                     * to string and css loader support for *.css files
                     * Returns file content as string
                     *
                     */
                    {
                        test: /\.css$/,
                        loaders: ['to-string-loader', 'css-loader']
                    },

                    /* Raw loader support for *.html
                     * Returns file content as string
                     *
                     * See: https://github.com/webpack/raw-loader
                     */
                    {
                        test: /\.html$/,
                        loader: 'raw-loader',
                        exclude: [helpers.root('src/index.html')]
                    },

                    /* File loader for supporting images, for example, in CSS files.
                     */
                    {
                        test: /\.(jpg|png|gif)$/,
                        loader: 'url-loader?limit=100000000000'
                    },

                    // Support for SCSS as raw text
                    {
                        test: /\.scss$/,
                        loaders: ['raw-loader', 'sass-loader']
                    },

                    //{ test: /\.scss$/, loaders: ['style', 'css', 'postcss', 'sass'] },

                    {test: /\.(woff2?|ttf|eot|svg)$/, loader: 'url-loader?limit=100000000000'}

                ],


            },

            /*
             * Add additional plugins to the compiler.
             *
             * See: http://webpack.github.io/docs/configuration.html#plugins
             */
            plugins: [
                new AssetsPlugin({
                    path: helpers.root('dist'),
                    filename: 'webpack-assets.json',
                    prettyPrint: true
                }),

                /*
                 * Plugin: ForkCheckerPlugin
                 * Description: Do type checking in a separate process, so webpack don't need to wait.
                 *
                 * See: https://github.com/s-panferov/awesome-typescript-loader#forkchecker-boolean-defaultfalse
                 */
                new ForkCheckerPlugin(),
                /*
                 * Plugin: CommonsChunkPlugin
                 * Description: Shares common code between the pages.
                 * It identifies common modules and put them into a commons chunk.
                 *
                 * See: https://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin
                 * See: https://github.com/webpack/docs/wiki/optimization#multi-page-app
                 */
                new webpack.optimize.CommonsChunkPlugin({
                    name: ['polyfills', 'vendor'].reverse()
                }),

                /**
                 * Plugin: ContextReplacementPlugin
                 * Description: Provides context to Angular's use of System.import
                 *
                 * See: https://webpack.github.io/docs/list-of-plugins.html#contextreplacementplugin
                 * See: https://github.com/angular/angular/issues/11580
                 */
                new ContextReplacementPlugin(
                    // The (\\|\/) piece accounts for path separators in *nix and Windows
                    /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
                    helpers.root('src') // location of your src
                ),
                new ContextReplacementPlugin(
                    // The (\\|\/) piece accounts for path separators in *nix and Windows
                    /moment[\/\\]locale$/, /en-gb/
                ),

                /*
                 * Plugin: CopyWebpackPlugin
                 * Description: Copy files and directories in webpack.
                 *
                 * Copies project static assets.
                 *
                 * See: https://www.npmjs.com/package/copy-webpack-plugin
                 */
                new CopyWebpackPlugin([{
                    from: 'src/assets',
                    to: 'assets'
                }]),

                /*
                 * Plugin: HtmlWebpackPlugin
                 * Description: Simplifies creation of HTML files to serve your webpack bundles.
                 * This is especially useful for webpack bundles that include a hash in the filename
                 * which changes every compilation.
                 *
                 * See: https://github.com/ampedandwired/html-webpack-plugin
                 */
                new HtmlWebpackPlugin({
                    template: 'src/index.html',
                    chunksSortMode: 'dependency'
                }),

                /*
                 * Plugin: HtmlHeadConfigPlugin
                 * Description: Generate html tags based on javascript maps.
                 *
                 * If a publicPath is set in the webpack output configuration, it will be automatically added to
                 * href attributes, you can disable that by adding a "=href": false property.
                 * You can also enable it to other attribute by settings "=attName": true.
                 *
                 * The configuration supplied is map between a location (key) and an element definition object (value)
                 * The location (key) is then exported to the template under then htmlElements property in webpack configuration.
                 *
                 * Example:
                 *  Adding this plugin configuration
                 *  new HtmlElementsPlugin({
                     *    headTags: { ... }
                     *  })
                 *
                 *  Means we can use it in the template like this:
                 *  <%= webpackConfig.htmlElements.headTags %>
                 *
                 * Dependencies: HtmlWebpackPlugin
                 */
                new HtmlElementsPlugin({
                    headTags: require('./head-config.common')
                }),

                /* Ahead of time compilation plugin */
                new AotPlugin({
                    tsConfigPath: 'tsconfig.json',
                    entryModule: helpers.root('src', 'app', 'app.module.ts#AppModule')
                })
            ],

            /*
             * Include polyfills or mocks for various node stuff
             * Description: Node configuration
             *
             * See: https://webpack.github.io/docs/configuration.html#node
             */
            node: {
                global: 'window',
                crypto: 'empty',
                process: true,
                module: false,
                clearImmediate: false,
                setImmediate: false
            }

        };
    }
}

Полная трассировка ошибки:

Uncaught Error: No NgModule metadata found for 'AppModule'.
    at NgModuleResolver.resolve (vendor.bundle.js?ver=d1084b5…:18969) [<root>]
    at CompileMetadataResolver.getNgModuleMetadata (vendor.bundle.js?ver=d1084b5…:18172) [<root>]
    at JitCompiler._loadModules (vendor.bundle.js?ver=d1084b5…:50582) [<root>]
    at JitCompiler._compileModuleAndComponents (vendor.bundle.js?ver=d1084b5…:50542) [<root>]
    at JitCompiler.compileModuleAsync (vendor.bundle.js?ver=d1084b5…:50508) [<root>]
    at PlatformRef_._bootstrapModuleWithZone (vendor.bundle.js?ver=d1084b5…:39236) [<root>]
    at PlatformRef_.bootstrapModule (vendor.bundle.js?ver=d1084b5…:39211) [<root>]
    at Object.<anonymous> (main.bundle.js?ver=d1084b5…:34315) [<root>]
    at __webpack_require__ (polyfills.bundle.js?ver=d1084b5…:53) [<root>]
    at webpackJsonpCallback (polyfills.bundle.js?ver=d1084b5…:24) [<root>]
    at :8080/main.bundle.js?ver=d1084b5dbd73943cc778978401608960:1:1 [<root>]

Я действительно не знаю, что происходит. Я очень ценю ваше время и вашу помощь.


person Lucha    schedule 07.03.2017    source источник
comment
В настоящее время у меня такая же проблема, все еще пытаюсь найти ее причину. Однако, если я изменяю const platform = platformBrowserDynamic(); platform.bootstrapModule(AppModule); на одну строку с platformBrowserDynamic().bootstrapModule(AppModule), сгенерированный вывод работает, но больше не использует встряхивание дерева.   -  person GeorgDangl    schedule 27.03.2017


Ответы (2)


Не знаю, поможет ли, но у меня была похожая проблема. Я предполагаю, что вы используете какой-то шаблон, потому что у вас есть эта функция helpers.root.

Прежде всего попробуйте изменить эту часть:

new AotPlugin({
    tsConfigPath: 'tsconfig.json',
    entryModule: helpers.root('src/app/app.module.ts#AppModule')
})

к этому (добавление части helpers.root в файл tsconfig.json):

new AotPlugin({
    tsConfigPath: helpers.root('tsconfig.json'),
    entryModule: helpers.root('src/app/app.module.ts#AppModule')
})

Во-вторых дважды проверьте, настроен ли ваш tsconfig.json для компиляции AoT. Это мой tsconfig-aot.json, который я использую в одном из своих проектов Angular 2:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": ["es2015", "dom"],
    "noImplicitAny": false,
    "sourceRoot": ".",
    "suppressImplicitAnyIndexErrors": true,
    "typeRoots": [
      "node_modules/@types"
    ]
  },

  "awesomeTypescriptLoaderOptions": {
    "useWebpackText": true,
    "forkChecker": true,
    "useCache": true
  },

  "angularCompilerOptions": {
    "genDir": "aot",
    "skipMetadataEmit" : true
  }
}

Надеюсь, это поможет!

person lomboboo    schedule 07.03.2017
comment
Спасибо за ответ! К сожалению, это не решило мою проблему, и я получаю ту же ошибку. И... Я думал, что с помощью @ngtools/webpack можно получить AOT без изменения кода. - person Lucha; 07.03.2017
comment
нет, вы должны изменить некоторые части. tsconfig.json должен выглядеть как в моем ответе. "module": "commonjs", "moduleResolution": "node", и "angularCompilerOptions" — самые важные настройки. Кроме того, если вы используете ngtools, вам не следует изменять main.browser.ts. Я рекомендую вам прочитать этот поток - person lomboboo; 07.03.2017
comment
Кроме того, вам нужно продолжить работу с файлами webpack .ts с помощью ngtools. Могу я увидеть ваш файл webpack.config.js? А какой шаблон вы используете? - person lomboboo; 07.03.2017
comment
Я обновил свой вопрос запрошенными вами файлами. Спасибо за ваши усилия! - person Lucha; 08.03.2017
comment
@Lucha, я не вижу, чтобы ты изменил tsConfigPath: 'tsconfig.json', на tsConfigPath: helpers.root('tsconfig.json'),. И еще одно: когда вы создаете свой скрипт, используя файл webpack.prod.config.js, верно? - person lomboboo; 08.03.2017
comment
@Lucha Я заметил еще одну тонкость. Здесь entryModule: helpers.root('src/app/app.module.ts#AppModule') изменить на entryModule: helpers.root('src/app/app.module#AppModule') без .ts - person lomboboo; 08.03.2017
comment
Да, оба варианта выдают мне одну и ту же ошибку, с helpers.root, без него, и с этим .ts и без него. - person Lucha; 08.03.2017
comment
Хорошо, вставил уже в свой исходный пост. Спасибо за время и терпение. - person Lucha; 09.03.2017

Попробуйте откатиться до версии ngtools/webpack 1.2.4. В более поздних версиях есть проблемы с разрешением пути, связанные с этой проблемой 5329.

person David Newall    schedule 27.03.2017