PatrickJS-starter: Angular 5 upgrade can't get lazy loading to work

Note: for support questions, please use one of these channels: Chat: AngularClass.slack or Twitter: @AngularClass

  • I’m submitting a … [x] bug report [ ] feature request [ ] question about the decisions made in the repository

  • What is the current behavior? I’ve managed to upgrade most of the repository to reflect this one, and get a compiling built out of it. When I try to get lazyloading to work in the way it used to, I run into multiple issues that break the app.

  • If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem package.json

dependencies: [
    "@angular/animations": "^5.0.5",
    "@angular/common": "^5.0.5",
    "@angular/compiler": "^5.0.5",
    "@angular/core": "^5.0.5",
    "@angular/forms": "^5.0.5",
    "@angular/http": "^5.0.5",
    "@angular/platform-browser": "^5.0.5",
    "@angular/platform-browser-dynamic": "^5.0.5",
    "@angular/platform-server": "^5.0.5",
    "@angular/router": "^5.0.5",
    "@angularclass/hmr": "^2.1.3",
    "@angularclass/hmr-loader": "~3.0.2",
    ............
],
devDependencies: [
   "@angular-devkit/build-optimizer": "^0.0.32",
    "@angular/compiler-cli": "^5.0.5",
    "@ngtools/webpack": "^1.8.0",
    .......
]

webpack.common.config

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

/*
 * Webpack Plugins
 */
// problem with copy-webpack-plugin
var ExtractTextPlugin = require('extract-text-webpack-plugin');
const AssetsPlugin = require('assets-webpack-plugin');
const NormalModuleReplacementPlugin = require('webpack/lib/NormalModuleReplacementPlugin');
const ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin');
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
const HtmlElementsPlugin = require('./html-elements-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin');
const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
// const ngcWebpack = require('ngc-webpack'); --> Replaced by @ngtools/webpack
const AngularCompilerPlugin = require('@ngtools/webpack').AngularCompilerPlugin;
const MergeJsonWebpackPlugin = require('merge-jsons-webpack-plugin');
const WriteFilePlugin = require('write-file-webpack-plugin');
/*
 * Webpack Constants
 */
const HMR = helpers.hasProcessFlag('hot');
const AOT = process.env.BUILD_AOT || helpers.hasNpmFlag('aot');

/*
 * Webpack configuration
 *
 * See: http://webpack.github.io/docs/configuration.html#cli
 */
module.exports = function (options) {

  const METADATA = {
    baseUrl: '/',
    title: options.title,
    isDevServer: helpers.isWebpackDevServer(),
    HMR: HMR
  };

  console.log('webpack common - METADATA.title: ', METADATA.title);

    const 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',
            'main':      AOT ? './src/main.browser.aot.ts' :
                './src/main.browser.ts'

        },

        /**
         * 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'), helpers.root('node_modules')],

            alias: {
                'jquery': __dirname + '/../node_modules/jquery/dist/jquery.js'
            }

        },

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

            rules: [

                 // ...ngcWebpackConfig.loaders,
                {
                    test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
                    loaders:
                        ['@ngtools/webpack', 'angular-router-loader?genDir=compiled&aot=false']
                },
                /**
                 * Json loader support for *.json files.
                 *
                 * See: https://github.com/webpack/json-loader
                 */
                {
                    test: /\.json$/,
                    use: 'json-loader'
                },
                
                /**
                 * To string and css loader support for *.css files (from Angular components)
                 * Returns file content as string
                 *
                 */
                {
                    test: /\.css$/,
                    use: ['to-string-loader', 'css-loader'],
                    exclude: [helpers.root('src', 'styles')]
                },

                /**
                 * To string and sass loader support for *.scss files (from Angular components)
                 * Returns compiled css content as string
                 *
                 */
                {
                    test: /\.scss$/,
                    include: helpers.root('src', 'app'),
                    use: ['raw-loader', 'sass-loader']
                    // use: ['to-string-loader', 'css-loader', 'sass-loader'],
                    // exclude: [helpers.root('src', 'styles')]
                },

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

                /**
                 * File loader for supporting images, for example, in CSS files.
                 */
                {
                    test: /\.(jpg|png|gif)$/,
                    use: 'file-loader'
                },

                /* See https://github.com/gowravshekar/font-awesome-webpack
                 */
                { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&mimetype=application/font-woff" },
                { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" }

            ],

        },

        /**
         * Add additional plugins to the compiler.
         *
         * See: http://webpack.github.io/docs/configuration.html#plugins
         */
        plugins: [

            new webpack.ProvidePlugin({
                jQuery: 'jquery',
                $: 'jquery',
                jquery: 'jquery'
            }),

            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 CheckerPlugin(),
            /**
             * 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 CommonsChunkPlugin({
                name: 'polyfills',
                chunks: ['polyfills']
            }),
            /**
             * This enables tree shaking of the vendor modules
             */
            new CommonsChunkPlugin({
                name: 'vendor',
                chunks: ['main'],
                minChunks: module => /node_modules/.test(module.resource)
            }),
            /**
             * Specify the correct order the scripts will be injected in
             */
            new CommonsChunkPlugin({
                name: ['polyfills', 'vendor'].reverse()
            }),
            new CommonsChunkPlugin({
                name: ['manifest'],
                minChunks: Infinity,
            }),

            /**
             * 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(\\|\/)@angular/,
                helpers.root('src'), // location of your src
                {
                    /**
                     * Your Angular Async Route paths relative to this root directory
                     */
                }
            ),

            /**
             * 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',
                title: METADATA.title,
                chunksSortMode: 'dependency',
                metadata: METADATA,
                inject: 'head'
            }),

            /**
             * Plugin: ScriptExtHtmlWebpackPlugin
             * Description: Enhances html-webpack-plugin functionality
             * with different deployment options for your scripts including:
             *
             * See: https://github.com/numical/script-ext-html-webpack-plugin
             */
            new ScriptExtHtmlWebpackPlugin({
                defaultAttribute: 'defer'
            }),

            /**
             * Plugin: HtmlElementsPlugin
             * 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')
            }),

            /**
             * Plugin LoaderOptionsPlugin (experimental)
             *
             * See: https://gist.github.com/sokra/27b24881210b56bbaff7
             */
            new LoaderOptionsPlugin({}),

            // new ngcWebpack.NgcWebpackPlugin(ngcWebpackConfig.plugin),

            new AngularCompilerPlugin({
                tsConfigPath: METADATA.tsConfigPath,
                // entryModule: entryModule,
                mainPath: entry.main,
                sourceMap: true,
                skipCodeGeneration: true,

            }),

            /**
             * Plugin: InlineManifestWebpackPlugin
             * Inline Webpack's manifest.js in index.html
             *
             * https://github.com/szrenwei/inline-manifest-webpack-plugin
             */
            new InlineManifestWebpackPlugin(),


                /**
                 * 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: '.' },
                        { from: 'src/meta'}
                    ],
                    isProd ? { ignore: [ 'mock-data/**/*' ] } : undefined
                )
            ],

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

    };
}

app.routes, being loaded through app.module using RootModule.forRoot(appRoutes)

import { Routes } from '@angular/router';
import { PATH_NO_CONTENT } from '../error/error.routes';

export const APP_ROUTES: Routes = [
    { path: 'help', loadChildren: '../help/help.module#HelpModule'},
    { path: 'error', loadChildren: '../error/error.module#ErrorModule'},
    { path: 'demo', loadChildren: '../demo/demo.module#DemoModule' },
    { path: '**', redirectTo: 'error/' + PATH_NO_CONTENT }
];

This one gives me the Error: Cannot find module '../demo/demo.module'.

If I switch the lazy loading loadChildren: to loadChildren: () => HelpModule, it doesn’t compile.

  • What is the expected behavior? Lazy loading to work, given either of the two solutions.

  • Please tell us about your environment:

  • Angular version: 5.0.5,
  • Browser: all.
  • Other information (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, gitter, etc)

I’ll add as I go…

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 10
  • Comments: 52

Most upvoted comments

@WillooWisp do you have your app compiling properly through webpack?

What I did in order to get it to work is change the routes to:

{ path: 'help', loadChildren: () => HelpModule }

That causes a compile error, but when you trigger a recompile it works fine. Does this work for you?

To clarify; I’m stuck at this exact point myself, but at least this way I got to see if the app worked fine after the lazyloading issue was fixed. Would like to see if the same works for you, cause we may have a bug in the way Lazyloading modules are being loaded.

global.error-handler.ts:43 Error: Uncaught (in promise): Error: Cannot find module ‘./login/login.module#LoginModule’. Error: Cannot find module ‘./login/login.module#LoginModule’. at eval (eval at ./$$_lazy_route_resource lazy recursive (main.bundle.js:7), <anonymous>:5:9)

I can confirm that the string path syntax works fine as long as your imports, declarations and routes are static. If code contains conditionals, where some routes are added based on some condition, chunks will not be generated for those upon first compilation, using start:hmr or start:aot. Making any change affecting the main chunk will cause a recompile and chunks will be generated for the lazy loaded routes as well. If those routes in turn contain lazy loaded child routes a recompile must be triggered once again from this parent chunk.

It used to work fine with conditionals, both when it came to adding routes more dynamically, but also adding declarations and module imports based on some condition. This is also broken now, so declarations and imports added inside conditional statements are ignored unfortunately.

@Bjeaurn loadChildren with a function () => Module, does not work with AOT. And according to this, https://github.com/angular/angular-cli/issues/4192, importing the Module that way breaks lazy loading from its own chunk. For some reason lazy loading with string path syntax works for smaller projects, but not for larger ones, or it might be loading modules that in turn have lazy loaded modules in their routes and so on.

The strange thing is that some modules work and some does not to lazy load with the string path syntax, but they did work to load this way before. And it varies from time to time when doing a rebuild.

I have the same issue loading

{
  path: 'child',
  loadChildren: '../node_modules/module-upgrade/src/app/app.module#UpgradeModule'
},

I the case up here the error is Error: Could not resolve "../node_modules/module-upgrade/src...." from /Users/username/project/src/app/app/module.ts and it works when I apply the method @Bjeaurn explained here

{
  path: 'child',
  loadChildren: UpgradeModule.getUpgradeModule
},

Compare your webpack config with the one from this repo, make sure you do not have any significant changes. Then it should work as long as you do not have any conditional include of routes, modules or declarations in your code. Unfortunately conditionals do not work anymore, why I do not know.

I’m also having the same issue. App is getting quite large now and needs lazy loading to keep the initial load time down for the user. I’ve tried every syntax:

{path:'properties', loadChildren:'./components/properties/properties.module#PropertiesModule'} {path:'properties', loadChildren:'components/properties/properties.module#PropertiesModule'} {path:'properties', loadChildren: ()=> PropertiesModule }

Webpack compiles fine and app works for all of the root-UI and smaller components not using loadChildren, but when nav to ‘/properties’ getting 404 inside the router-outlet

Angular CLI: 1.6.3 Node: 6.11.4 OS: linux x64 Angular: 5.2.0

Also I should note one of the ‘/properties’ child links are using :id but even commenting out all these dynamic links the route never loads.

I finally found the problem. I am using conditionals in my app module to include different routes depending on some variable, which has worked perfectly before, but doing that with latest configuration causes the chunks to not get generated for lazy loaded modules.